Wie kann ich verbessern / ersetzen sprintf, die ich gemessen habe eine Performance-Hotspot sein?

StackOverflow https://stackoverflow.com/questions/271971

Frage

Durch Profilierung Ich habe entdeckt, dass der sprintf hier eine lange Zeit in Anspruch nimmt. Gibt es eine bessere Leistung Alternative, die immer noch die führenden Nullen in der y Griffe / m / d h / m / s Felder?

SYSTEMTIME sysTime;
GetLocalTime( &sysTime );
char buf[80];
for (int i = 0; i < 100000; i++)
{

    sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d",
        sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
        sysTime.wHour, sysTime.wMinute, sysTime.wSecond);

}

Hinweis: Die OP erklärt in den Kommentaren, dass dies ein abgespeckte Beispiel. Die „echte“ Schleife enthält zusätzlichen Code, die unterschiedlichen Zeitwert aus einer Datenbank verwendet. Profilieren hat aufgezeigt sprintf() als Täter.

War es hilfreich?

Lösung

Wenn Sie eine eigene Funktion schrieben die Arbeit zu tun, eine Lookup-Tabelle der String-Werte von 0 .. 61 würde vermieden, für alles, was jede Arithmetik zu tun haben, abgesehen vom Jahr.

edit: Beachten Sie, dass mit Schaltsekunden zu bewältigen (und strftime() ) sollten Sie Sekunden der Lage sein, Werte von 60 und 61.

drucken
char LeadingZeroIntegerValues[62][] = { "00", "01", "02", ... "59", "60", "61" };

Alternativ, wie über strftime() ? Ich habe keine Ahnung, wie die Leistung vergleicht (es könnte auch nur sprintf ()) aufrufen, aber es ist einen Blick wert (und es könnte dabei die oben Lookup selbst sein).

Andere Tipps

Sie könnten versuchen, jedes Zeichen in der Ausgabe wiederum füllen.

buf[0] = (sysTime.wYear / 1000) % 10 + '0' ;
buf[1] = (sysTime.wYear / 100) % 10 + '0';
buf[2] = (sysTime.wYear / 10) % 10 + '0';
buf[3] = sysTime.wYear % 10 + '0';
buf[4] = '-';

... etc ...

Nicht schön, aber Sie erhalten das Bild. Wenn nichts anderes, kann es helfen, zu erklären, warum sprintf wird nicht so schnell sein.

OTOH, vielleicht könnten Sie das letzte Ergebnis zwischenzuspeichern. Auf diese Weise werden Sie brauchen nur ein in jeder Sekunde zu erzeugen.

Printf muss mit vielen verschiedenen Formaten beschäftigen. Sie könnten sicherlich die Quelle für printf rel="nofollow und es als Grundlage verwenden, um zu rollen Ihre eigene Version, die speziell mit der sysTime Struktur befaßt. Auf diese Weise können in einem Argument übergeben, und es funktioniert einfach genau die Arbeit, die getan werden muss, und nichts weiter.

Was Sie von einer „langen“ Zeit bedeuten - da die sprintf() die einzige Anweisung in der Schleife ist und die „Sanitär“ die Schleife (Zuwachs, Vergleich) vernachlässigbar ist, die sprintf() hat die meiste Zeit verbrauchen.

Denken Sie daran, den alten Witz über den Mann, der seinen Ehering an 3rd Street einer Nacht verloren, sah aber am 5. für es, weil das Licht heller da war? Sie haben ein Beispiel gebaut, die zu „beweisen“ Ihre Annahme entworfen ist, dass sprintf() ist effizient ist.

Ihre Ergebnisse werden genauer sein, wenn Sie „tatsächlichen“ Code profilieren, die sprintf() zusätzlich zu allen anderen Funktionen und Algorithmen Sie verwenden enthält. Alternativ versuchen Sie Ihre eigene Version zu schreiben, die die spezifische Nullen aufgefüllt numerische Umwandlung Adressen, die Sie benötigen.

Sie können an den Ergebnissen überrascht sein.

Sieht aus wie Jaywalker ein sehr ähnliches Verfahren ist darauf hindeutet (schlägt mich um weniger als eine Stunde).

Neben der bereits vorgeschlagenen Lookup-Tabelle Methode (n2s [] Array unten), wie etwa Ihre Formatpuffer zu erzeugen, so dass die übliche sprintf weniger intensiv ist? Der folgende Code muss nur in der Minute füllen und zweiten jedes Mal durch die Schleife, wenn das Jahr / Monat / Tag / Stunde geändert haben. Selbstverständlich, wenn irgendwelche von denen geändert haben Sie das anderes tun sprintf Hit nehmen, aber insgesamt kann es nicht mehr sein als das, was Sie gerade erleben (wenn sie mit dem Array-Lookup kombiniert).


static char fbuf[80];
static SYSTEMTIME lastSysTime = {0, ..., 0};  // initialize to all zeros.

for (int i = 0; i < 100000; i++)
{
    if ((lastSysTime.wHour != sysTime.wHour)
    ||  (lastSysTime.wDay != sysTime.wDay)
    ||  (lastSysTime.wMonth != sysTime.wMonth)
    ||  (lastSysTime.wYear != sysTime.wYear))
    {
        sprintf(fbuf, "%4d-%02s-%02s %02s:%%02s:%%02s",
                sysTime.wYear, n2s[sysTime.wMonth],
                n2s[sysTime.wDay], n2s[sysTime.wHour]);

        lastSysTime.wHour = sysTime.wHour;
        lastSysTime.wDay = sysTime.wDay;
        lastSysTime.wMonth = sysTime.wMonth;
        lastSysTime.wYear = sysTime.wYear;
    }

    sprintf(buf, fbuf, n2s[sysTime.wMinute], n2s[sysTime.wSecond]);

}

Wie wäre es, die Ergebnisse zwischenspeichern? Ist das nicht eine Möglichkeit? Bedenkt man, dass diese besondere sprintf () -Aufruf in Ihrem Code zu oft gemacht wird, ich gehe davon aus, dass das Jahr, Monat und Tag zwischen den meisten dieser aufeinanderfolgenden Anrufe, nicht ändern.

So können wir so etwas wie die folgenden umsetzen. Deklarieren Sie eine alte und eine aktuelle Struktur SYSTEM:

SYSTEMTIME sysTime, oldSysTime;

Auch erklären getrennte Teile das Datum und die Zeit zu halten:

char datePart[80];
char timePart[80];

Für die erste Zeit werden Sie in beiden sysTime, oldSysTime sowie Datumsteil und timePart füllen müssen. Aber die nachfolgenden sprintf () 'kann s recht schneller gemacht werden, wie unten angegeben:

sprintf (timePart, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
if (oldSysTime.wYear == sysTime.wYear && 
  oldSysTime.wMonth == sysTime.wMonth &&
  oldSysTime.wDay == sysTime.wDay) 
  {
     // we can reuse the date part
     strcpy (buff, datePart);
     strcat (buff, timePart);
  }
else {
     // we need to regenerate the date part as well
     sprintf (datePart, "%4d-%02d-%02d", sysTime.wYear, sysTime.wMonth, sysTime.wDay);
     strcpy (buff, datePart);
     strcat (buff, timePart);
}

memcpy (&oldSysTime, &sysTime, sizeof (SYSTEMTIME));

Vor-Code hat eine gewisse Redundanz der Code leichter zu verstehen. Sie können ganz einfach ausklammern. Sie können weiter beschleunigen, wenn Sie wissen, dass sogar Stunden und Minuten nicht schneller ändern als Ihr Aufruf der Routine.

ich tun würde, ein paar Dinge ...

  • Cache die aktuelle Zeit, so dass Sie müssen den Zeitstempel nicht regenerieren jedes Mal
  • Sie manuell die Zeitumstellung. Der langsamste Teil der printf-Familie Funktionen ist das Format-String-Parsing, und es ist dumm, sich auf jeder Schleife die Ausführung dieser Analyse widmen Zyklen zu sein.
  • versuchen Sie es mit 2-Byte-Lookup-Tabellen für alle Konvertierungen ({ "00", "01", "02", ..., "99" }). Dies liegt daran, Sie moduluar Arithmetik und eine 2-Byte-Tabelle bedeutet, dass Sie nur verwenden müssen eine Modulo, für das Jahr.
  • vermeiden wollen

Sie würden wahrscheinlich w perf Anstieg von Hand bekommen eine Routine rollen, die die Ziffern in der Rück buf legt, da man immer wieder Parsen eine Format-String vermeiden könnte und würde nicht mit einer Menge der komplexeren Fälle sprintf Griffe zu tun haben . Ich bin verabscheuen tatsächlich empfehlen tun, dass though.

Ich würde empfehlen, zu versuchen, herauszufinden, ob Sie irgendwie den Betrag, den Sie diese Zeichenfolge zu erzeugen, müssen reduzieren können, sind sie wahlweise frei somegtimes, können sie im Cache gespeichert werden, etc.

Ich bin auf ein ähnliches Problem im Moment arbeiten.

Ich brauche Debug-Anweisungen mit Zeitstempeln protokollieren, Dateinamen, Zeilennummer usw. auf einem Embedded-System. Wir haben bereits einen Logger an Ort und Stelle, aber wenn ich den Regler auf ‚vollständige Protokollierung‘ drehen, es frisst alle unsere proc Zyklen und stellt unser System in der entsetzlichen Zuständen besagt, dass kein Computergerät jemals erfahren haben sollte.

Jemand sagte „Man kann nicht etwas messen / beobachten, ohne dass eine Änderung, die Sie messen / zu beobachten.“

So Dinge Ich ändere die Leistung zu verbessern. Der aktuelle Stand der Dinge ist, dass Im 2x schneller als der Originals Funktionsaufruf (der Engpass in diesem Erfassungssystem ist in dem Funktionsaufruf nicht aber in dem Log-Leser, die eine separate ausführbare Datei, die ich verwerfen wenn ich meinen eigenen Logging-Stack schreiben).

Die Schnittstelle I zur Verfügung zu stellen brauchen, ist etwas wie-void log(int channel, char *filename, int lineno, format, ...). Ich brauche den Kanalnamen anhängen (das gegenwärtig der Fall ist ein lineare Suche in einer Liste! Für jede einzelne Debug-Anweisung!) Und Zeitstempel einschließlich Millisekunden-Zähler. Hier sind einige der Dinge Im tun dies schneller zu machen -

  • Stringify Kanalnamen, so kann ich strcpy anstatt die Liste zu suchen. definieren Makro LOG(channel, ...etc) als log(#channel, ...etc). Sie können memcpy verwenden, wenn Sie die Länge der Zeichenfolge durch die Definition LOG(channel, ...) log("...."#channel - sizeof("...."#channel) + *11*) erhalten feste 10 Byte Kanallängen
  • fix
  • Generieren Zeitstempel Zeichenfolge ein paar Mal pro Sekunde. Sie können asctime oder etwas verwenden. Dann MEMCPY die feste Zeichenfolge zu jeder Debug-Anweisung.
  • Wenn Sie den Zeitstempel-String in Echtzeit erzeugen wollen, dann eine Nachschlagtabelle mit einer Zuordnung (nicht MEMCPY!) Ist perfekt. Aber das funktioniert nur für 2-stellige Zahlen und vielleicht für das Jahr.
  • Was ist dreistellig (Millisekunden) und fünf Ziffern (lineno)? Ich mag nicht itoa und ich nicht wie die benutzerdefinierte itoa (digit = ((value /= value) % 10)) entweder weil divs und Mods sind langsam . Ich schrieb die folgenden Funktionen und später entdeckt, dass etwas Ähnliches in der Optimierung manuelle AMD ist (in der Montage), die mir das Vertrauen gibt, dass diese über die schnellsten C-Implementierungen.

    void itoa03(char *string, unsigned int value)
    {
       *string++ = '0' + ((value = value * 2684355) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

    In ähnlicher Weise für die Zeilennummern,

    void itoa05(char *string, unsigned int value)
    {
       *string++ = ' ';
       *string++ = '0' + ((value = value * 26844 + 12) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28);
       *string++ = ' ';/* null terminate here if thats what you need */
    }
    

Insgesamt mein Code ist ziemlich schnell jetzt. Die vsnprintf() Ich brauche dauert etwa 91% der Zeit zu verwenden und den Rest meines Codes dauert nur 9% (während der Rest des Codes das heißt außer vsprintf() verwendet 54% nehmen früher)

Die beiden schnellen Formatierer ich getestet habe sind FastFormat und :: generieren (Teil von Boost-Geist ).

Das könnte Sie auch finden es Benchmark nützlich es oder zumindest sucht bestehenden Benchmarks.

Zum Beispiel dieses (obwohl es fehlt FastFormat):

string ist der Vorschlag, die ich von Google bekam.

http://bytes.com/forum/thread132583.html

Es ist schwer vorstellbar, dass Sie sprintf bei der Formatierung ganze Zahlen schlagen werden. Sind Sie sicher, dass sprintf ist dein Problem?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top