Ersatz für vsnprinft --> ohne Zeichenanzahlbegrenzung, std::string, boot::format, ...
-
Hallo.
Bei uns im Framework gibt es historisch bedingt eine Log-Methode, welche man im printf-Stil die Parameter übergibt, also einen format-string und eine Parameterliste.
Eigentlich läuft alles ohne Problme, jedoch kommt nun der Wunsch, dass es keine Beschränkung hinsichtlich der Log-Zeichenlänge geben soll, mittels vsnprintf wird das ganze auf 250-Zeichen beschränkt.Welche Möglichkeiten gibt es, dass man hier das ganze dynmisch angeht? Kann man boost::format auch eine va_list übergeben?
Ich habe das ganze schon auf auf folgende Arten getestet, jedoch bin ich da um "Häuser" langsamer als mit der normalen vsnprintf-Funktion
Meine simple Funktion wo mittels fixen buffer und vsnprintf ein string erzeugt wird.
std::string vsnchar(const char* format, ...) { va_list args; std::string retval; short strsize(250); char str[strsize]; memset(str, '\0', strsize); va_start (args, format); vsnprintf (str, strsize, format, args); va_end (args); retval = str; }Es wird mittels vsnprinft der benötige String-Länge ermittelt. Für die Länge gibt es noch eine andere Funktion, leider finde ich diese Alternative nicht mehr, vermutlich wird die performance nicht viel schneller/langsamer werden.
std::string vsnstring(const char* format, ...) { va_list args; std::string retval; va_start (args, format); retval.resize (vsnprintf (0, 0, format, args)); vsnprintf (&retval[0], retval.capacity() + 1, format, args); va_end (args); return retval; }Ich habe auch bei meiner suche im Internet eine Möglichkeit gefunden, dass man alles mittels vector machen soll, auch dies habe ich getestet.
std::string vsnvector(const char* format, ...) { va_list args; std::string str; va_start (args, format); std::vector<char> buffer(vsnprintf (0, 0, format, args)); vsnprintf(&*buffer.begin(), buffer.size(), format, args); va_end (args); std::string result(buffer.begin(), buffer.end()-1); return result; }Obwohl hier nicht unbedingt Linux-Themen angesprochen werden sollen, jedoch ich unter Linux entwickle, habe ich auch open_memstream getestet.
std::string vsnfile(const char* format, ...) { va_list args; std::string retval; FILE *stream; char *buf; size_t len; stream = open_memstream (&buf, &len); if (stream == NULL) return retval; va_start (args, format); vfprintf(stream, format, args); va_end (args); retval.append(buf, len); fclose (stream); free (buf); return retval; }Fazit:
Leider habe ich keine direkt gute Alternative gefunden. vsnfile ist nach vsnchar am schnellsten (1.000.000x werden die Funktionen aufgerufen)
vsnfile benötigt ~1,8sec, vsnstring ~3,2sec, vsnvector ~3,3sec und vsnfile ~2,9sec
Je größer die Logzeichenlänge wird, desto schneller nähern sich vsnfile und vsnchar an.
Interessant ist auch, wenn vsnchar mit dem fixen buffer nicht auskommt (zb. buffer[3]), dann benötigt vsnchar nicht mehr ~1,8sec, sondern ~2,2secMEINE FRAGE:
Gibt es eine Möglichkeit, dass man hier das ganze ohne fixem buffer macht und trotzdem fast so schnell ist wie mit einem fixen buffer?
Unter boost gibt es ja boost::format, aber ich habe keine Möglichkeit gefunden, dass man hier eine va_list übergeben könnte.Danke für eure Hinweise und Hilfe
Johannes
-
Kannst du vasprintf verwenden?
-
Danke!

gestern bin ich bei die.net über diese Funktion gestolpert, aber irgendwie muss ich das ganze übersehen haben. DANKE
meine Testfunktion schaut nun so aus:
std::string vasf(const char* format, ...) { va_list args; std::string retval; int n(-1); char *pstr(NULL); va_start (args, format); n = vasprintf (&pstr, format, args); va_end (args); if(n <= 0) return retval; retval.append(pstr, n); free(pstr); return retval; }Interessant ist, dass bei Ergebnissen wo zeichenketten von rund 100 zeichen rauskommen, diese funktion zu 90% schneller ist als ein fixer buffer und vnsprintf

Jedoch ist es auch so, wenn das Zeichenkettenergebnis eine Länge von 600 Zeichen hat, vsnprintf mit fixem buffer schneller ist im gegensatz zu vasprintf . grund ist jedoch hier folgender Befehl *retval.append(pstr, n);*ich möchte trotzdem mit std::string weiter arbeiten, gibt es noch einen trick, damit das kopieren schneller funktioniert?Da ich nun schon einige Möglichkeiten getestet habe, stellt sich für mich noch immer die frage, bietet boost hier auch einen Lösungsweg?
DANKElg
lg
-
taff schrieb:
ich möchte trotzdem mit std::string weiter arbeiten, gibt es noch einen trick, damit das kopieren schneller funktioniert?
Probier mal das std::string objekt erst zu erstellen wenn du es brauchst:
string retval(pstr,n);Ansonsten verwendet man für sowas auch öfters noch eine nocopy-string Klasse die eben nicht kopiert sondern den ownership von pstr übernimmt und am ende selber löscht.
Schau auch ob hier bei dir NRVO zieht - wenn nicht, dann das free uU in einen Destruktor verlagern.
Da ich nun schon einige Möglichkeiten getestet habe, stellt sich für mich noch immer die frage, bietet boost hier auch einen Lösungsweg?
Mir ist nichts bekannt. boost bietet eine alternative an, aber eben auch mit anderer syntax...
-
Shade Of Mine schrieb:
Probier mal das std::string objekt erst zu erstellen wenn du es brauchst
spielt eigentlich keine rolle -> das ganze wird um 0,01 sec schneller.
Shade Of Mine schrieb:
Ansonsten verwendet man für sowas auch öfters noch eine nocopy-string Klasse die eben nicht kopiert sondern den ownership von pstr übernimmt und am ende selber löscht.
schade, dass sowas std::string nicht kann.
Shade Of Mine schrieb:
Schau auch ob hier bei dir NRVO zieht - wenn nicht, dann das free uU in einen Destruktor verlagern.
ich bin der meinung, dass NRVO zieht, außer es reicht wenn
void vasf(std::string &str, const char* format, ...)um 0,01-0,03sec schneller ist bei 500.000 aufrufe (stringlänge=600);
Shade Of Mine schrieb:
Mir ist nichts bekannt. boost bietet eine alternative an, aber eben auch mit anderer syntax...
ok, schade, hätte noch gehofft, dass boost::format hier irgendwo doch noch was versteckt hat

-
taff schrieb:
schade, dass sowas std::string nicht kann.
Bist du wirklich auf std::string festgelegt?
Deine Beschreibung klang so als ob es um die interna einer Log Funktion geht...Prinzipiell kannst du ansonsten ja auch eine std::string-interface-kompatible Variante eines nocopy-strings schreiben und den verwenden. Oder wird der string irgendwann nach aussen gegeben? Das wäre dann natürlich doof.
Prinzipiell ist es aber komisch dass das kopieren in den String so lange dauert. hätte eher auf die allokationen in vasprintf getippt...
Notfalls den source code von vasprintf suchen und adaptieren dass direkt in den string allokiert wird...
-
direkt festgelegt bin ich nicht, jedoch sind alle sachen die zusätzlich noch gemacht werden müssen mit dieser logzeile schon jetzt auf std::string aufgebaut bzw. es wird jetzt schon das ergebnis von vsnprinft+fixer buffer in einen std::string kopiert.
hab mir nun, so wie von dir empfohlen, den sourcecode von vasprintf rausgesucht
ich hoffe, dass ist der richtige
http://www.google.com/codesearch/p?hl=de#qoCVjtE_vOw/gcc/trunk/gcc-3.4.6/libiberty/vasprintf.c
vasprintf berechnet zuerst den größten möglichen speicher und reserviert diesen.beim return wird die eigentlich verwendete speicheranzahl zurückgegeben.int int_vasprintf (char **result, const char* format, va_list args) { const char *p = format; /* Add one to make sure that it is never zero, which might cause malloc to return NULL. */ int total_width = strlen (format) + 1; va_list ap; va_copy (ap, args); while (*p != '\0') { if (*p++ == '%') { while (strchr ("-+ #0", *p)) ++p; if (*p == '*') { ++p; total_width += abs (va_arg (ap, int)); } else total_width += strtoul (p, (char **) &p, 10); if (*p == '.') { ++p; if (*p == '*') { ++p; total_width += abs (va_arg (ap, int)); } else total_width += strtoul (p, (char **) &p, 10); } while (strchr ("hlL", *p)) ++p; /* Should be big enough for any format specifier except %s and floats. */ total_width += 30; switch (*p) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': case 'c': (void) va_arg (ap, int); break; case 'f': case 'e': case 'E': case 'g': case 'G': (void) va_arg (ap, double); /* Since an ieee double can have an exponent of 307, we'll make the buffer wide enough to cover the gross case. */ total_width += 307; break; case 's': total_width += strlen (va_arg (ap, char *)); break; case 'p': case 'n': (void) va_arg (ap, char *); break; } p++; } } va_end (ap); *result = (char *) malloc (total_width); if(*result != NULL) return vsprintf (*result, format, args); else return -1; }interessant ist, wenn ich eben statt
*result = (char *) malloc (total_width); if(*result != NULL) return vsprintf (*result, format, args); else return -1;meine std::string ausführung mache:
result.resize(total_width); if (result.size() > 0) return vsprintf (&result[0], format, args); else return -1;die std::string variante langsamer ist als eben int_vasprintf + str.append(pstr, n);
ist das speicherreservieren beim std::string um so viel langsamer als ein malloc?
bei meinem test ist somit ein malloc von 937 + std::string.append von der länge von 515 schneller wie das resize von 937