Wir sind täglich auf viele Situationen gestoßen, in denen wir mühsame und sehr viele Zeichenfolgenoperationen in unserem Code ausführen müssen. Wir alle wissen, dass String-Manipulationen teure Operationen sind. Ich würde gerne wissen, welche der verfügbaren Versionen die günstigste ist.
Die häufigste Operation ist die Verkettung (dies ist etwas, das wir bis zu einem gewissen Grad steuern können). Was ist der beste Weg, um std :: Strings in C ++ und verschiedene Problemumgehungen zu verketten, um die Verkettung zu beschleunigen?
Ich meine,
std::string l_czTempStr;
1).l_czTempStr = "Test data1" + "Test data2" + "Test data3";
2). l_czTempStr = "Test data1";
l_czTempStr += "Test data2";
l_czTempStr += "Test data3";
3). using << operator
4). using append()
Erhalten wir auch einen Vorteil bei der Verwendung von CString gegenüber std :: string?
c++
string
concatenation
Bhupesh Pant
quelle
quelle
stringstream
ist für diesen Anwendungsfall gebaut,string
nicht. Es ist also wahrscheinlich eine gute Wette, um damit zu beginnenstringstream
.l_czTempStr = std::string("Test data1") + "Test data2" + "Test data3";
. Ansonsten ist die Antwort, die verschiedenen Techniken zeitlich zu bestimmen. Es gibt so viele Variablen, dass es unmöglich ist, die Frage zu beantworten. Die Antwort hängt von der Anzahl und Länge der Zeichenfolgen ab, mit denen Sie arbeiten, sowie von der Plattform, auf der Sie kompilieren, und der Plattform, für die Sie kompilieren.+
erstellt ein neues Objekt mit einigen Sonderfällen in C ++ 11). Optimieren Sie dies jedoch nur, wenn Sie dies benötigen oder Ihr Code nicht lesbar ist.std::ostringstream
ist für die Formatierung vorgesehen und sollte normalerweise nur verwendet werden, wenn Sie eine Formatierung benötigen. Alle seine Daten sind Zeichenfolgen, daher ist diestd::string
Verkettung die bevorzugte Lösung.Antworten:
Hier ist eine kleine Testsuite:
#include <iostream> #include <string> #include <chrono> #include <sstream> int main () { typedef std::chrono::high_resolution_clock clock; typedef std::chrono::duration<float, std::milli> mil; std::string l_czTempStr; std::string s1="Test data1"; auto t0 = clock::now(); #if VER==1 for (int i = 0; i < 100000; ++i) { l_czTempStr = s1 + "Test data2" + "Test data3"; } #elif VER==2 for (int i = 0; i < 100000; ++i) { l_czTempStr = "Test data1"; l_czTempStr += "Test data2"; l_czTempStr += "Test data3"; } #elif VER==3 for (int i = 0; i < 100000; ++i) { l_czTempStr = "Test data1"; l_czTempStr.append("Test data2"); l_czTempStr.append("Test data3"); } #elif VER==4 for (int i = 0; i < 100000; ++i) { std::ostringstream oss; oss << "Test data1"; oss << "Test data2"; oss << "Test data3"; l_czTempStr = oss.str(); } #endif auto t1 = clock::now(); std::cout << l_czTempStr << '\n'; std::cout << mil(t1-t0).count() << "ms\n"; }
Auf coliru :
Kompilieren Sie mit folgendem:
21,6463 ms
6,61773 ms
6,7855 ms
102,015 ms
Es sieht so aus
2)
,+=
ist der Gewinner.(Auch das Kompilieren mit und ohne
-pthread
scheint das Timing zu beeinflussen)quelle
l_czTempStr
die Versionen 2 und 3 , wenn die Deklaration nicht in die Schleife aufgenommen wird, denselben Puffer immer wieder verwenden, während Version 1std::string{""}
jedes Mal einen neuen Puffer erstellt . Ihr Benchmark hat gezeigt, dass die Wiederverwendung des gleichen Puffers anstelle der Zuweisung / Freigabe eine 5-fache Beschleunigung ergibt (die Beschleunigung war offensichtlich, der Faktor hängt davon ab, wie lang die Teile sind und wie viele Neuzuweisungen erfolgen, wenn Sie nicht alles reservieren -Vorderseite). Ich bin mir nicht sicher, ob das OP den gleichen Puffer wiederverwenden wollte oder nicht.operator+
leidet jedoch immer noch unter einer großen internen Zuweisung / Freigabe.stringstream
Benchmark beinhaltet das (unglaublich) langsame Timing des Aufbaus des Streams..append()
. Und sie sollten es auch nicht: Eine davon wird in Bezug auf die andere umgesetzt. Ich verstehe nicht, warum sie sich signifikant unterscheiden würden.Neben anderen Antworten ...
Ich habe vor einiger Zeit umfangreiche Benchmarks zu diesem Problem erstellt und bin zu dem Schluss gekommen, dass die effizienteste Lösung (GCC 4.7 und 4.8 unter Linux x86 / x64 / ARM) in allen Anwendungsfällen zunächst
reserve()
die Ergebniszeichenfolge mit genügend Speicherplatz für alle ist die verketteten Zeichenfolgen und dann nurappend()
sie (oder verwendenoperator +=()
, das macht keinen Unterschied).Leider habe ich diesen Benchmark gelöscht, sodass Sie nur mein Wort haben (aber Sie können den Benchmark von Mats Petersson leicht anpassen, um dies selbst zu überprüfen, wenn mein Wort nicht ausreicht).
In einer Nussschale:
const string space = " "; string result; result.reserve(5 + space.size() + 5); result += "hello"; result += space; result += "world";
Abhängig vom genauen Anwendungsfall (Anzahl, Typ und Größe der verketteten Zeichenfolgen) ist diese Methode manchmal bei weitem die effizienteste und manchmal ist sie anderen Methoden ebenbürtig, aber niemals schlechter.
Das Problem ist, dass es sehr schmerzhaft ist, die erforderliche Gesamtgröße im Voraus zu berechnen, insbesondere beim Mischen von String-Literalen und
std::string
(das obige Beispiel ist in dieser Angelegenheit meiner Meinung nach klar genug). Die Wartbarkeit eines solchen Codes ist absolut schrecklich, sobald Sie eines der Literale ändern oder eine andere zu verkettende Zeichenfolge hinzufügen.Ein Ansatz wäre,
sizeof
die Größe der Literale zu berechnen, aber meiner Meinung nach verursacht es so viel Chaos, wie es löst, die Wartbarkeit ist immer noch schrecklich:#define STR_HELLO "hello" #define STR_WORLD "world" const string space = " "; string result; result.reserve(sizeof(STR_HELLO)-1 + space.size() + sizeof(STR_WORLD)-1); result += STR_HELLO; result += space; result += STR_WORLD;
Eine brauchbare Lösung (C ++ 11, verschiedene Vorlagen)
Ich entschied mich schließlich für eine Reihe variabler Vorlagen, die sich effizient um die Berechnung der Zeichenfolgengrößen kümmern (z. B. wird die Größe der Zeichenfolgenliterale zur Kompilierungszeit bestimmt)
reserve()
, und dann alles verkettet.Hier ist es, hoffe das ist nützlich:
namespace detail { template<typename> struct string_size_impl; template<size_t N> struct string_size_impl<const char[N]> { static constexpr size_t size(const char (&) [N]) { return N - 1; } }; template<size_t N> struct string_size_impl<char[N]> { static size_t size(char (&s) [N]) { return N ? strlen(s) : 0; } }; template<> struct string_size_impl<const char*> { static size_t size(const char* s) { return s ? strlen(s) : 0; } }; template<> struct string_size_impl<char*> { static size_t size(char* s) { return s ? strlen(s) : 0; } }; template<> struct string_size_impl<std::string> { static size_t size(const std::string& s) { return s.size(); } }; template<typename String> size_t string_size(String&& s) { using noref_t = typename std::remove_reference<String>::type; using string_t = typename std::conditional<std::is_array<noref_t>::value, noref_t, typename std::remove_cv<noref_t>::type >::type; return string_size_impl<string_t>::size(s); } template<typename...> struct concatenate_impl; template<typename String> struct concatenate_impl<String> { static size_t size(String&& s) { return string_size(s); } static void concatenate(std::string& result, String&& s) { result += s; } }; template<typename String, typename... Rest> struct concatenate_impl<String, Rest...> { static size_t size(String&& s, Rest&&... rest) { return string_size(s) + concatenate_impl<Rest...>::size(std::forward<Rest>(rest)...); } static void concatenate(std::string& result, String&& s, Rest&&... rest) { result += s; concatenate_impl<Rest...>::concatenate(result, std::forward<Rest>(rest)...); } }; } // namespace detail template<typename... Strings> std::string concatenate(Strings&&... strings) { std::string result; result.reserve(detail::concatenate_impl<Strings...>::size(std::forward<Strings>(strings)...)); detail::concatenate_impl<Strings...>::concatenate(result, std::forward<Strings>(strings)...); return result; }
Der einzig interessante Teil, was die öffentliche Oberfläche betrifft, ist die allerletzte
template<typename... Strings> std::string concatenate(Strings&&... strings)
Vorlage. Die Verwendung ist unkompliziert:int main() { const string space = " "; std::string result = concatenate("hello", space, "world"); std::cout << result << std::endl; }
Wenn die Optimierungen aktiviert sind, sollte jeder anständige Compiler in der Lage sein, den
concatenate
Aufruf auf denselben Code wie in meinem ersten Beispiel zu erweitern, in dem ich alles manuell geschrieben habe. In Bezug auf GCC 4.7 und 4.8 ist der generierte Code ebenso wie die Leistung ziemlich identisch.quelle
Das schlimmste mögliche Szenario ist die Verwendung von alt
strcat
(odersprintf
), dastrcat
eine C-Zeichenfolge verwendet wird, und diese muss "gezählt" werden, um das Ende zu finden. Für lange Saiten ist das ein echter Leistungseinbrecher. Zeichenfolgen im C ++ - Stil sind viel besser, und die Leistungsprobleme liegen wahrscheinlich eher in der Speicherzuweisung als in der Zählung der Längen. Andererseits wächst die Saite geometrisch (verdoppelt sich jedes Mal, wenn sie wachsen muss), also ist es nicht so schrecklich.Ich würde sehr vermuten, dass alle oben genannten Methoden die gleiche oder zumindest sehr ähnliche Leistung erbringen. Wenn überhaupt, würde ich erwarten, dass dies
stringstream
aufgrund des Overheads bei der Unterstützung der Formatierung langsamer ist - aber ich vermute auch, dass es marginal ist.Da so etwas "Spaß" macht, werde ich mit einem Benchmark zurückkommen ...
Bearbeiten:
Beachten Sie, dass diese Ergebnisse für MEINEN Computer gelten, auf dem x86-64 Linux ausgeführt wird und der mit g ++ 4.6.3 kompiliert wurde. Andere Betriebssysteme, Compiler und Implementierungen der C ++ - Laufzeitbibliothek können variieren. Wenn die Leistung für Ihre Anwendung wichtig ist, vergleichen Sie die für Sie kritischen Systeme anhand der von Ihnen verwendeten Compiler.
Hier ist der Code, den ich geschrieben habe, um dies zu testen. Es ist vielleicht nicht die perfekte Darstellung eines realen Szenarios, aber ich denke, es ist ein repräsentatives Szenario:
#include <iostream> #include <iomanip> #include <string> #include <sstream> #include <cstring> using namespace std; static __inline__ unsigned long long rdtsc(void) { unsigned hi, lo; __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi)); return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 ); } string build_string_1(const string &a, const string &b, const string &c) { string out = a + b + c; return out; } string build_string_1a(const string &a, const string &b, const string &c) { string out; out.resize(a.length()*3); out = a + b + c; return out; } string build_string_2(const string &a, const string &b, const string &c) { string out = a; out += b; out += c; return out; } string build_string_3(const string &a, const string &b, const string &c) { string out; out = a; out.append(b); out.append(c); return out; } string build_string_4(const string &a, const string &b, const string &c) { stringstream ss; ss << a << b << c; return ss.str(); } char *build_string_5(const char *a, const char *b, const char *c) { char* out = new char[strlen(a) * 3+1]; strcpy(out, a); strcat(out, b); strcat(out, c); return out; } template<typename T> size_t len(T s) { return s.length(); } template<> size_t len(char *s) { return strlen(s); } template<> size_t len(const char *s) { return strlen(s); } void result(const char *name, unsigned long long t, const string& out) { cout << left << setw(22) << name << " time:" << right << setw(10) << t; cout << " (per character: " << fixed << right << setw(8) << setprecision(2) << (double)t / len(out) << ")" << endl; } template<typename T> void benchmark(const char name[], T (Func)(const T& a, const T& b, const T& c), const char *strings[]) { unsigned long long t; const T s1 = strings[0]; const T s2 = strings[1]; const T s3 = strings[2]; t = rdtsc(); T out = Func(s1, s2, s3); t = rdtsc() - t; if (len(out) != len(s1) + len(s2) + len(s3)) { cout << "Error: out is different length from inputs" << endl; cout << "Got `" << out << "` from `" << s1 << "` + `" << s2 << "` + `" << s3 << "`"; } result(name, t, out); } void benchmark(const char name[], char* (Func)(const char* a, const char* b, const char* c), const char *strings[]) { unsigned long long t; const char* s1 = strings[0]; const char* s2 = strings[1]; const char* s3 = strings[2]; t = rdtsc(); char *out = Func(s1, s2, s3); t = rdtsc() - t; if (len(out) != len(s1) + len(s2) + len(s3)) { cout << "Error: out is different length from inputs" << endl; cout << "Got `" << out << "` from `" << s1 << "` + `" << s2 << "` + `" << s3 << "`"; } result(name, t, out); delete [] out; } #define BM(func, size) benchmark(#func " " #size, func, strings ## _ ## size) #define BM_LOT(size) BM(build_string_1, size); \ BM(build_string_1a, size); \ BM(build_string_2, size); \ BM(build_string_3, size); \ BM(build_string_4, size); \ BM(build_string_5, size); int main() { const char *strings_small[] = { "Abc", "Def", "Ghi" }; const char *strings_medium[] = { "abcdefghijklmnopqrstuvwxyz", "defghijklmnopqrstuvwxyzabc", "ghijklmnopqrstuvwxyzabcdef" }; const char *strings_large[] = { "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc" "defghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc", "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" "ghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdef" }; for(int i = 0; i < 5; i++) { BM_LOT(small); BM_LOT(medium); BM_LOT(large); cout << "---------------------------------------------" << endl; } }
Hier sind einige repräsentative Ergebnisse:
build_string_1 small time: 4075 (per character: 452.78) build_string_1a small time: 5384 (per character: 598.22) build_string_2 small time: 2669 (per character: 296.56) build_string_3 small time: 2427 (per character: 269.67) build_string_4 small time: 19380 (per character: 2153.33) build_string_5 small time: 6299 (per character: 699.89) build_string_1 medium time: 3983 (per character: 51.06) build_string_1a medium time: 6970 (per character: 89.36) build_string_2 medium time: 4072 (per character: 52.21) build_string_3 medium time: 4000 (per character: 51.28) build_string_4 medium time: 19614 (per character: 251.46) build_string_5 medium time: 6304 (per character: 80.82) build_string_1 large time: 8491 (per character: 3.63) build_string_1a large time: 9563 (per character: 4.09) build_string_2 large time: 6154 (per character: 2.63) build_string_3 large time: 5992 (per character: 2.56) build_string_4 large time: 32450 (per character: 13.87) build_string_5 large time: 15768 (per character: 6.74)
Gleicher Code, ausgeführt als 32-Bit:
build_string_1 small time: 4289 (per character: 476.56) build_string_1a small time: 5967 (per character: 663.00) build_string_2 small time: 3329 (per character: 369.89) build_string_3 small time: 3047 (per character: 338.56) build_string_4 small time: 22018 (per character: 2446.44) build_string_5 small time: 3026 (per character: 336.22) build_string_1 medium time: 4089 (per character: 52.42) build_string_1a medium time: 8075 (per character: 103.53) build_string_2 medium time: 4569 (per character: 58.58) build_string_3 medium time: 4326 (per character: 55.46) build_string_4 medium time: 22751 (per character: 291.68) build_string_5 medium time: 2252 (per character: 28.87) build_string_1 large time: 8695 (per character: 3.72) build_string_1a large time: 12818 (per character: 5.48) build_string_2 large time: 8202 (per character: 3.51) build_string_3 large time: 8351 (per character: 3.57) build_string_4 large time: 38250 (per character: 16.35) build_string_5 large time: 8143 (per character: 3.48)
Daraus können wir schließen:
Die beste Option ist das Anhängen eines Stücks (
out.append()
oderout +=
), wobei der "verkettete" Ansatz ziemlich nahe kommt.Das Vorabzuweisen der Zeichenfolge ist nicht hilfreich.
Die Verwendung
stringstream
ist eine ziemlich schlechte Idee (zwischen 2-4x langsamer).Die
char *
Verwendungennew char[]
. Die Verwendung einer lokalen Variablen in der aufrufenden Funktion macht es am schnellsten - aber etwas unfair, das zu vergleichen.Das Kombinieren von kurzen Zeichenfolgen ist mit einem gewissen Aufwand verbunden. Das Kopieren von Daten sollte höchstens einen Zyklus pro Byte umfassen [es sei denn, die Daten passen nicht in den Cache].
edit2
Hinzugefügt, wie in den Kommentaren:
string build_string_1b(const string &a, const string &b, const string &c) { return a + b + c; }
und
string build_string_2a(const string &a, const string &b, const string &c) { string out; out.reserve(a.length() * 3); out += a; out += b; out += c; return out; }
Welches gibt diese Ergebnisse:
build_string_1 small time: 3845 (per character: 427.22) build_string_1b small time: 3165 (per character: 351.67) build_string_2 small time: 3176 (per character: 352.89) build_string_2a small time: 1904 (per character: 211.56) build_string_1 large time: 9056 (per character: 3.87) build_string_1b large time: 6414 (per character: 2.74) build_string_2 large time: 6417 (per character: 2.74) build_string_2a large time: 4179 (per character: 1.79)
(Ein 32-Bit-Lauf, aber der 64-Bit-Lauf zeigt sehr ähnliche Ergebnisse).
quelle
operator +()
ist ein temporärer Puffer , in den verschoben (oder RVO-fähig) wird,out
sodass die Vorzuweisung nutzlos ist. Ein interessanter Benchmark wäre, 2a / 3a- Fälle zu erstellen, in denen Siereserve()
die Ergebniszeichenfolge vor sich haben und dannappend()
oder+=
alle Parameter Ihrer Ergebniszeichenfolge. Wie ich in meiner Antwort erläutere, habe ich vor einiger Zeit solche Benchmarks durchgeführt und bin zu dem Schluss gekommen, dass dies tatsächlich die effizienteste Lösung ist.build_string_1b
Funktion hinzugefügt , die gerade ausgeführt wurdereturn a + b + c;
und sich in einigen Läufen als die schnellste Funktion herausstellte (VS2012).a
dannreserve
), dies könnte weiter verbessert werden, indem Siereserve
eine leere Zeichenfolge und nur dann+=
alle Ihre Parameter verwenden (was Ihnen eine einzige Speicherzuweisung gibt, diereserve
). Ich würde das selbst bearbeiten, aber die Timings sind für Ihre Maschine, also lasse ich Sie es tun. ;)Wie bei den meisten Mikrooptimierungen müssen Sie den Effekt jeder Option messen, nachdem Sie zunächst durch Messung festgestellt haben, dass dies tatsächlich ein Flaschenhals ist, der optimiert werden sollte. Es gibt keine endgültige Antwort.
append
und+=
sollte genau das gleiche tun.+
ist konzeptionell weniger effizient, da Sie Provisorien erstellen und zerstören. Ihr Compiler kann dies möglicherweise so optimieren, dass es so schnell wie das Anhängen ist.Anrufe
reserve
mit der Gesamtgröße können die Anzahl der erforderlichen Speicherzuordnungen verringern - dies ist wahrscheinlich der größte Engpass.<<
(vermutlich auf astringstream
) kann schneller sein oder nicht; Sie müssen das messen. Es ist nützlich, wenn Sie Nicht-String-Typen formatieren müssen, aber wahrscheinlich nicht besonders gut oder schlechter mit Strings umgehen können.CString
hat den Nachteil, dass es nicht portabel ist und dass ein Unix-Hacker wie ich Ihnen nicht sagen kann, welche Vorteile es haben kann oder nicht.quelle
Ich beschloss, einen Test mit dem vom Benutzer Jesse Good bereitgestellten Code durchzuführen , der leicht modifiziert wurde, um die Beobachtung von Rapptz zu berücksichtigen , insbesondere die Tatsache, dass der Ostringstream in jeder einzelnen Iteration der Schleife konstruiert wurde. Daher habe ich einige Fälle hinzugefügt, von denen einige der Ostringstream sind, der mit der Sequenz " oss.str (" "); oss.clear () " gelöscht wurde.
Hier ist der Code
#include <iostream> #include <string> #include <chrono> #include <sstream> #include <functional> template <typename F> void time_measurement(F f, const std::string& comment) { typedef std::chrono::high_resolution_clock clock; typedef std::chrono::duration<float, std::milli> mil; std::string r; auto t0 = clock::now(); f(r); auto t1 = clock::now(); std::cout << "\n-------------------------" << comment << "-------------------\n" <<r << '\n'; std::cout << mil(t1-t0).count() << "ms\n"; std::cout << "---------------------------------------------------------------------------\n"; } inline void clear(std::ostringstream& x) { x.str(""); x.clear(); } void test() { std:: cout << std::endl << "----------------String Comparison---------------- " << std::endl; const int n=100000; { auto f=[](std::string& l_czTempStr) { std::string s1="Test data1"; for (int i = 0; i < n; ++i) { l_czTempStr = s1 + "Test data2" + "Test data3"; } }; time_measurement(f, "string, plain addition"); } { auto f=[](std::string& l_czTempStr) { for (int i = 0; i < n; ++i) { l_czTempStr = "Test data1"; l_czTempStr += "Test data2"; l_czTempStr += "Test data3"; } }; time_measurement(f, "string, incremental"); } { auto f=[](std::string& l_czTempStr) { for (int i = 0; i < n; ++i) { l_czTempStr = "Test data1"; l_czTempStr.append("Test data2"); l_czTempStr.append("Test data3"); } }; time_measurement(f, "string, append"); } { auto f=[](std::string& l_czTempStr) { for (int i = 0; i < n; ++i) { std::ostringstream oss; oss << "Test data1"; oss << "Test data2"; oss << "Test data3"; l_czTempStr = oss.str(); } }; time_measurement(f, "oss, creation in each loop, incremental"); } { auto f=[](std::string& l_czTempStr) { std::ostringstream oss; for (int i = 0; i < n; ++i) { oss.str(""); oss.clear(); oss << "Test data1"; oss << "Test data2"; oss << "Test data3"; } l_czTempStr = oss.str(); }; time_measurement(f, "oss, 1 creation, incremental"); } { auto f=[](std::string& l_czTempStr) { std::ostringstream oss; for (int i = 0; i < n; ++i) { oss.str(""); oss.clear(); oss << "Test data1" << "Test data2" << "Test data3"; } l_czTempStr = oss.str(); }; time_measurement(f, "oss, 1 creation, plain addition"); } { auto f=[](std::string& l_czTempStr) { std::ostringstream oss; for (int i = 0; i < n; ++i) { clear(oss); oss << "Test data1" << "Test data2" << "Test data3"; } l_czTempStr = oss.str(); }; time_measurement(f, "oss, 1 creation, clearing calling inline function, plain addition"); } { auto f=[](std::string& l_czTempStr) { for (int i = 0; i < n; ++i) { std::string x; x = "Test data1"; x.append("Test data2"); x.append("Test data3"); l_czTempStr=x; } }; time_measurement(f, "string, creation in each loop"); } }
Hier sind die Ergebnisse:
/* g++ "qtcreator debug mode" ----------------String Comparison---------------- -------------------------string, plain addition------------------- Test data1Test data2Test data3 11.8496ms --------------------------------------------------------------------------- -------------------------string, incremental------------------- Test data1Test data2Test data3 3.55597ms --------------------------------------------------------------------------- -------------------------string, append------------------- Test data1Test data2Test data3 3.53099ms --------------------------------------------------------------------------- -------------------------oss, creation in each loop, incremental------------------- Test data1Test data2Test data3 58.1577ms --------------------------------------------------------------------------- -------------------------oss, 1 creation, incremental------------------- Test data1Test data2Test data3 11.1069ms --------------------------------------------------------------------------- -------------------------oss, 1 creation, plain addition------------------- Test data1Test data2Test data3 10.9946ms --------------------------------------------------------------------------- -------------------------oss, 1 creation, clearing calling inline function, plain addition------------------- Test data1Test data2Test data3 10.9502ms --------------------------------------------------------------------------- -------------------------string, creation in each loop------------------- Test data1Test data2Test data3 9.97495ms --------------------------------------------------------------------------- g++ "qtcreator release mode" (optimized) ----------------String Comparison---------------- -------------------------string, plain addition------------------- Test data1Test data2Test data3 8.41622ms --------------------------------------------------------------------------- -------------------------string, incremental------------------- Test data1Test data2Test data3 2.55462ms --------------------------------------------------------------------------- -------------------------string, append------------------- Test data1Test data2Test data3 2.5154ms --------------------------------------------------------------------------- -------------------------oss, creation in each loop, incremental------------------- Test data1Test data2Test data3 54.3232ms --------------------------------------------------------------------------- -------------------------oss, 1 creation, incremental------------------- Test data1Test data2Test data3 8.71854ms --------------------------------------------------------------------------- -------------------------oss, 1 creation, plain addition------------------- Test data1Test data2Test data3 8.80526ms --------------------------------------------------------------------------- -------------------------oss, 1 creation, clearing calling inline function, plain addition------------------- Test data1Test data2Test data3 8.78186ms --------------------------------------------------------------------------- -------------------------string, creation in each loop------------------- Test data1Test data2Test data3 8.4034ms --------------------------------------------------------------------------- */
Jetzt ist die Verwendung von std :: string immer noch schneller und das Anhängen ist immer noch der schnellste Weg zur Verkettung, aber ostringstream ist nicht mehr so unglaublich schrecklich wie zuvor.
quelle
Da die akzeptierte Antwort dieser Frage ziemlich alt ist, habe ich beschlossen, die Benchmarks mit dem modernen Compiler zu aktualisieren und beide Lösungen von @ jesse-good und die Vorlagenversion von @syam zu vergleichen
Hier ist der kombinierte Code:
#include <iostream> #include <string> #include <chrono> #include <sstream> #include <vector> #include <cstring> #if VER==TEMPLATE namespace detail { template<typename> struct string_size_impl; template<size_t N> struct string_size_impl<const char[N]> { static constexpr size_t size(const char (&) [N]) { return N - 1; } }; template<size_t N> struct string_size_impl<char[N]> { static size_t size(char (&s) [N]) { return N ? strlen(s) : 0; } }; template<> struct string_size_impl<const char*> { static size_t size(const char* s) { return s ? strlen(s) : 0; } }; template<> struct string_size_impl<char*> { static size_t size(char* s) { return s ? strlen(s) : 0; } }; template<> struct string_size_impl<std::string> { static size_t size(const std::string& s) { return s.size(); } }; template<typename String> size_t string_size(String&& s) { using noref_t = typename std::remove_reference<String>::type; using string_t = typename std::conditional<std::is_array<noref_t>::value, noref_t, typename std::remove_cv<noref_t>::type >::type; return string_size_impl<string_t>::size(s); } template<typename...> struct concatenate_impl; template<typename String> struct concatenate_impl<String> { static size_t size(String&& s) { return string_size(s); } static void concatenate(std::string& result, String&& s) { result += s; } }; template<typename String, typename... Rest> struct concatenate_impl<String, Rest...> { static size_t size(String&& s, Rest&&... rest) { return string_size(s) + concatenate_impl<Rest...>::size(std::forward<Rest>(rest)...); } static void concatenate(std::string& result, String&& s, Rest&&... rest) { result += s; concatenate_impl<Rest...>::concatenate(result, std::forward<Rest>(rest)...); } }; } // namespace detail template<typename... Strings> std::string concatenate(Strings&&... strings) { std::string result; result.reserve(detail::concatenate_impl<Strings...>::size(std::forward<Strings>(strings)...)); detail::concatenate_impl<Strings...>::concatenate(result, std::forward<Strings>(strings)...); return result; } #endif int main () { typedef std::chrono::high_resolution_clock clock; typedef std::chrono::duration<float, std::milli> ms; std::string l_czTempStr; std::string s1="Test data1"; auto t0 = clock::now(); #if VER==PLUS for (int i = 0; i < 100000; ++i) { l_czTempStr = s1 + "Test data2" + "Test data3"; } #elif VER==PLUS_EQ for (int i = 0; i < 100000; ++i) { l_czTempStr = "Test data1"; l_czTempStr += "Test data2"; l_czTempStr += "Test data3"; } #elif VER==APPEND for (int i = 0; i < 100000; ++i) { l_czTempStr = "Test data1"; l_czTempStr.append("Test data2"); l_czTempStr.append("Test data3"); } #elif VER==STRSTREAM for (int i = 0; i < 100000; ++i) { std::ostringstream oss; oss << "Test data1"; oss << "Test data2"; oss << "Test data3"; l_czTempStr = oss.str(); } #elif VER=TEMPLATE for (int i = 0; i < 100000; ++i) { l_czTempStr = concatenate(s1, "Test data2", "Test data3"); } #endif #define STR_(x) #x #define STR(x) STR_(x) auto t1 = clock::now(); //std::cout << l_czTempStr << '\n'; std::cout << STR(VER) ": " << ms(t1-t0).count() << "ms\n"; }
Die Testanweisung:
for ARGTYPE in PLUS PLUS_EQ APPEND STRSTREAM TEMPLATE; do for i in `seq 4` ; do clang++ -std=c++11 -O3 -DVER=$ARGTYPE -Wall -pthread -pedantic main.cpp && ./a.out ; rm ./a.out ; done; done
Und Ergebnisse (verarbeitet über eine Tabelle, um die durchschnittliche Zeit anzuzeigen):
PLUS 23.5792 PLUS 23.3812 PLUS 35.1806 PLUS 15.9394 24.5201 PLUS_EQ 15.737 PLUS_EQ 15.3353 PLUS_EQ 10.7764 PLUS_EQ 25.245 16.773425 APPEND 22.954 APPEND 16.9031 APPEND 10.336 APPEND 19.1348 17.331975 STRSTREAM 10.2063 STRSTREAM 10.7765 STRSTREAM 13.262 STRSTREAM 22.3557 14.150125 TEMPLATE 16.6531 TEMPLATE 16.629 TEMPLATE 22.1885 TEMPLATE 16.9288 18.09985
Die Überraschung
strstream
scheint, dass C ++ 11 und spätere Verbesserungen viel Nutzen bringen. Wahrscheinlich hat das Entfernen der erforderlichen Zuordnung aufgrund der Einführung der Verschiebungssemantik einen gewissen Einfluss.Sie können es selbst auf coliru testen
Bearbeiten: Ich habe den Test auf coliru aktualisiert, um g ++ - 4.8 zu verwenden: http://coliru.stacked-crooked.com/a/593dcfe54e70e409 . Ergebnisse in Grafik hier:
(Erklärung - "stat. Durchschnitt" bedeutet Durchschnitt über alle Werte mit Ausnahme von zwei extremen - einem minimalen und einem maximalen Wert)
quelle
Es gibt einige wichtige Parameter, die potenzielle Auswirkungen auf die Entscheidung über den "optimiertesten Weg" haben. Einige davon sind - Zeichenfolgen- / Inhaltsgröße, Anzahl der Operationen, Compileroptimierung usw.
In den meisten Fällen
string::operator+=
scheint es am besten zu funktionieren. Bei einigen Compilern wird jedoch manchmal auch beobachtet, dass dies amostringstream::operator<<
besten funktioniert [wie - MingW g ++ 3.2.3, 1,8 GHz Einzelprozessor Dell PC ]. Wenn der Compilerkontext kommt, sind es hauptsächlich die Optimierungen beim Compiler, die sich auswirken würden. Zu erwähnen ist auch, dass diesstringstreams
im Vergleich zu einfachen Zeichenfolgen komplexe Objekte sind und daher den Overhead erhöhen.Für weitere Informationen - Diskussion , Artikel .
quelle