Geschwindigkeit bei Schreiben auf Speicher
-
@hustbaer: muss ich den vector mit reserve vorher allozieren umd danach mit assign bzw nem constructor mit der iterator range zu kopieren oder geht das auch mit nem defaukt-initialized vector?
und auf der ersten Seite: Sind die drei von mir aufgeführten Möglichkeiten so korrekt? und wenn ja, sind die gleich performant?
-
Sewing schrieb:
warum resize und nicht reserve? Ich kopiere doch sowieso andere Werte unmittelbar danach in den Container
reserve
+push_back
ist nicht optimal, weil der Compiler vermutlich nicht schlau genug ist dabei den "ist nocht genug Platz" Check inpush_back
wegzuoptimieren. Dazu müsste er sehen können dass auf Grund desreserve
davor der Test nie negativ ausgehen kann, und das ist was was ich als extrem schwer für den Compiler einschätzen würde. So schwer dass ich schätzen würde dass kein aktueller Compiler es schafft. Verglichen mit 1xmemcpy
für alle 4000 Elemente wird das dadurch ordentlich viel langsamer werden. Wenn man komplexere Elemente einfügt ist dann allerdings schnell der Punkt erreicht wo der prozentuelle Unterschied sehr klein wird.Was du machen kannst ist
v.reserve(elementCount)
+v.insert(v.begin(), elementsBegin, elementsEnd)
. Bzw. ebenv.reserve(elementCount)
+v.assign(elementsBegin, elementsEnd)
wenn der vector davor sowieso leer ist. Dasreserve
davor sollte nicht nötig sein, aber ich vermute dass es laut Standard nicht vorgeschrieben ist dass vector das selbst macht, und wenn du sicher gehen willst kannst du es explizit hinschreiben.
-
hustbaer schrieb:
Arcoth schrieb:
Du kannst dieselbe Performance erreichen, wenn du den
vector
zuvor auf 4000 resized und dannstd::copy
verwendest,Nicht ganz, da muss ja erstmal alles mit 0 vollgeschrieben werden und danach kommt erst das Kopieren. memcpy auf un-initialisierten Speicher ist schneller. Und dass der Compiler erkennt dass das unnötig ist und es wegoptimieren kann, davon würde ich nicht ausgehen.
Man kann sich nicht darauf verlassen, aber mindestens eine Implementierung ist ausgeklügelt genug:
auto f(int* arr) { std::vector<int> v(1000); std::copy_n(arr, 1000, v.data()); return v; }
GCC Godbolt: https://godbolt.org/g/vgDA9d
Clang 4.0 mit -O2:f(int*): # @f(int*) push r14 push rbx push rax mov r14, rsi mov rbx, rdi xorps xmm0, xmm0 movups xmmword ptr [rbx], xmm0 mov qword ptr [rbx + 16], 0 mov edi, 4000 call operator new(unsigned long) mov qword ptr [rbx], rax lea rcx, [rax + 4000] mov qword ptr [rbx + 16], rcx mov qword ptr [rbx + 8], rcx mov edx, 4000 mov rdi, rax mov rsi, r14 call memcpy mov rax, rbx add rsp, 8 pop rbx pop r14 ret
GCC rafft es leider nicht (siehe das verlinkte Fenster, -O3 zeigt immer noch
rep stosq
zum Ausnullen). Selbes gilt für ICC. Bei VC++ erkenne ich nichts mehr, der Assembler ist irgendwie ungekürzt.
-
Sewing schrieb:
@hustbaer: muss ich den vector mit reserve vorher allozieren umd danach mit assign bzw nem constructor mit der iterator range zu kopieren oder geht das auch mit nem defaukt-initialized vector?
Wenn du sicher gehen willst dass es ohne reserve optimal läuft, dann step durch den Code und/oder profile es. Ansonsten: schreib das reserve davor um sicherzugehen.
Sewing schrieb:
und auf der ersten Seite: Sind die drei von mir aufgeführten Möglichkeiten so korrekt? und wenn ja, sind die gleich performant?
Also unter der Annahme dass es 4000
uint16_t
sind (und nicht 4000 Byte)...Sewing schrieb:
std::vector<uint16_t> _AplitudeImg{}; void setAmplitudeImg(uint16_t* data) { _AmplitudeImg.assign(data, data + 4000); }
Korrekt. Optimal unter der Annahme dass vector schlau genug ist und selbst passend
reserve()
d.Sewing schrieb:
std::vector<uint16_t> _AplitudeImg{}; void setAmplitudeImg(uint16_t* data) { std::copy(data, data+4000, _AmplitudeImg.begin()); }
Falsch. Du musst vor dem
copy()
noch die Elemente erzeugen,copy()
selbst erzeugt keine sondern überschreibt sie nur (assignment operator). Also vorherresize()
machen. Alternativ könnte man einenback_insert_iterator
verwenden, aber da würde ich wetten dass das wieder ordentlich langsamer ist.Sewing schrieb:
std::vector<uint16_t> _AplitudeImg{}; void setAmplitudeImg(uint16_t* data) { memcpy(_AmplitudeImg.data(), data, sizeof(uint16_t)); }
Auch falsch. Muss
memcpy(_AmplitudeImg.data(), data, sizeof(uint16_t) * 4000)
heissen wenn du 4000 Elemente kopieren willst. Und auch hier wieder: memcpy erzeugt dir keine Elemente, es kopiert bloss bytes. Also auch hier einresize()
vorher nötig.Sewing schrieb:
macht es Sinn, vorher den vector auf irgendeinen Wert zu reserven im constructor?
mMn. ja. Entweder im Konstruktor oder in
setAmplitudeImg()
. Wenn eh schon genug Platz ist ist reserve sehr billig, kann man also ruhig insetAmplitudeImg()
machen. Wenn öfter mal Objekte konstruiert werden auf die dann niesetAmplitudeImg()
aufgerufen wird, würde ich sogar wärmstens empfehlen es nur insetAmplitudeImg()
und nicht im Konstruktor zu machen.Sewing schrieb:
Gibt es eine Möglichkeit einen leeren container mit einer bestimmten capazität anzulegen? Denn wenn ich schreibe (...)
oder gibt es es wirklich nurstd::vector<int> v{}; v.reserve(4000);
Ich kenne nur diese Variante.
Wenn der Platz für die 4000 Elemente in dem Objekt immer oder fast immer benötigt wird, und es selten (oder nie) mehr werden, kannst du allerdings
boost::small_vector<int, 4000>
verwenden. Der hat dann Platz für 4000 Elemente direkt eingebaut. Bzw. wenn es immer genau 4000 Elemente sind kannst du natürlich auchstd::array<int, 4000>
verwenden. Bzw.unique_ptr<std::array<int, 4000>>
wenn manchmal gar keine und ansonsten immer genau 4000 benötigt werden.
-
std::vector<uint16_t> v(1000); void foo(uint16_t* data) { v.assign(data, data + 1000); }
std::vector<uint16_t> v(1000); void foo(uint16_t* data) { std::copy(data, data+1000, v.begin()); }
std::vector<uint16_t> v(1000); void foo(uint16_t* data) { memcpy(v.data(), data, 4000*sizeof(uint16_t)); }
std::vector<uint16_t> v(1000); void foo(uint16_t* data) { std::copy_n(arr, 1000, v.data()); }
std::vector<uint16_t> v(1000); void foo(uint16_t* data) { v.insert(v.begin(), data, data+1000) }
Alle Varianten korrekt?
-
Danke für die Erläuterungen : )
Was genau ist der vorteil von std::array gegenüber std::vector?
Dachte, dass array default auf dem stack erzeugt wird, wenn ich aber den aber mit nem smart ptr auf dem Heap erzeuge, ist diese Tatsache aber auch nicht mehr gegeben
-
Sewing schrieb:
Was genau ist der vorteil von std::array gegenüber std::vector?
Der Vorteil ist dass es viel weniger kann. Daher benötigt es weniger Code. Weniger Arbeit für den Optimizer und geringere Chancen dass Dinge nicht wegoptimiert werden können.
Und wenn die Grösse (compile-time) konstant ist, dann dokumentiert std::array diesen Umstand auch gleich schön.
Sewing schrieb:
Dachte, dass array default auf dem stack erzeugt wird, wenn ich aber den aber mit nem smart ptr auf dem Heap erzeuge, ist diese Tatsache aber auch nicht mehr gegeben
std::array
enthält direkt die Elemente, gleich wie ein "nacktes" Array. D.h. die Elemente liegen dort wo auch das std::array liegt. Wenn du std::array als Membervariable einer Klasse X verwendest, und du Objekte der Klasse X auf dem Stack erzeugst, dann liegen die std::array Elemente auf dem Stack. Erzeugst du die Objekte der Klasse X allerdings mitnew
, dann liegen die Elemente vonstd::array
natürlich ebenso wie das ganze X Objekt im "free store" (heap).
-
Arcoth schrieb:
Man kann sich nicht darauf verlassen, aber mindestens eine Implementierung ist ausgeklügelt genug:
Cool. Bin beeindruckt.
-
@Sewing
Achja, falls du godbolt.org nocht nicht kennst und den Link von Arcoth nicht genauer beachtet hast: godbolt.org ist ein saugeiles Werkzeug für wenn man wissen möchte was verschiedene Compiler für verschiedene C++ Konstrukte für Code generieren.
Guck's dir an und sei beeindruckt: https://godbolt.org/Und guck dir vor allem den Unterschied zwischen diesen beiden Varianten an:
auto f(int* arr) { std::vector<int> v(1000); std::copy_n(arr, 1000, v.data()); return v; } auto g(int* arr) { std::vector<int> v; v.reserve(1000); for (size_t i = 0; i < 1000; i++) v.push_back(arr[i]); return v; }
-
mir sagt Assembler Code nur nichts
aber vielleicht reicht es aus, auf die Zeilenanzahl zu schauen.
Wäre das hier nicht auch nen Anwendungsbeispiel für std::allocator?
-
und nur so nebenbei:
Benchmark-Versuche nur im Release-Mode mit eingeschalteter Optimierung (alles andere ist völlig sinnfrei - da um Faktor 100-beinahe unendlich langsamer)
keine trivial-Loop-Tests unter ein paar Mio Durchläufe
und IO-Ausgaben am Ende mit rein (irgendwas in der Schleife kumulieren) sonst entfernt der Optimierer zu viel Code und du freust dich über nicht vorhandene Hyper-Geschwindigkeit