Probiere gerade C code auf plusplus umzubiegen um festzustellen ob
std::array in der Geschwindigkeit irgendetwas bringt.
1
std::array<double,20>cpp_lut{1,2,3};
2
3
for(autoi{oo_lut.size()};i-->0;)
4
std::cout<<cpp_lut[i]<<std::endl;
Soweit funktioniert das, aber wenn ich was anderes im Array haben möchte
meckert der Compiler (weil r_data nicht im Namespace std bekannt ist ?)
error: template argument 1 is invalid std::array<std::r_data,20>
cpp_lut;
1
structr_data{
2
boole_use;
3
std::stringe_name;
4
doublee_value;
5
};
6
7
std::array<r_data,20>cpp_lut;
Was muss ich da machen damit es
r_data c99_lut[20];
ersetzt ? Vielleicht kann mir auch schon jemand sagen was bei der
Geschwindigkeit rauskommt ? Es wird in einer zweifach verschachtelten
Schleife jeder mit jedem multipliziert, also nur Lesezugriffe auf das
Array.
nochmal genaue Fehlermeldung:
std::array<r_data, 20>::value_type {aka r_data}’ is not derived from
‘const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>
jv schrieb:> ersetzt ? Vielleicht kann mir auch schon jemand sagen was bei der> Geschwindigkeit rauskommt ?
Was erwartest du?
std::array ist „under the hood“ ein C-Style-Array.
Bezug. des Compilerfehlers zeig den echten Code.
Oliver
jv schrieb:> Vielleicht kann mir auch schon jemand sagen was bei der> Geschwindigkeit rauskommt ?
Genau so schnell wie ein C-Array, 1. wie kommst du darauf das es
schneller ist und 2. Was willst du denn mit schnelleren Array Zugriffen
reissen?
Eine sehr unprofessionelle Frage
cppbert schrieb:> jv schrieb:>> Vielleicht kann mir auch schon jemand sagen was bei der>> Geschwindigkeit rauskommt ?>> Genau so schnell wie ein C-Array, 1. wie kommst du darauf das es> schneller ist und 2. Was willst du denn mit schnelleren Array Zugriffen> reissen?>> Eine sehr unprofessionelle Frage
Dein std::cout ist z.B. locker 100x langsamer als der Arrayzugriff
* hier der misslungene Versuch das Gleiche mit std::array container
29
* sowie C++11 range based loop zu machen
30
*/
31
32
std::array<r_data,20>cpp_lut{
33
true,"blabla",1000,
34
true,"hahaha",12,
35
false,"hihie",13
36
};
37
38
/*
39
void test_cpp(void)
40
{
41
for(auto i{cpp_lut.size() }; i-- > 0; )
42
std::cout<<cpp_lut[i]<<std::endl;
43
}
44
*/
Was ist an test_cpp alles falsch ? Sorry für die unprofessionelle
Fragerei aber ich blick halt nicht durch.
Der cout fliegt natürlich zur Zeitmessung raus - ist jetzt nur um zu
sehen ob irgendwas geht. Es sind etwa 5000 unsortierte double Zahlen von
welche jeder mit jeder multipliziert wird (verschachtelte Schleife mit
Index i und j). Das Resultat mit der kleinsten Abweichung zu einer
Variablen X ist dann der Lösungstext aus den beiden Strings. Es werden
nur Einträge mit .use==true verarbeitet. Das dauert hier gut eine
Sekunde und vielleicht kann man da noch was rausholen. Momentan wäre ich
aber schon mal froh das überhaupt mit std:array hinzukriegen
jv schrieb:> Was ist an test_cpp alles falsch ? Sorry für die unprofessionelle> Fragerei aber ich blick halt nicht durch.
Kompilierst du im release also benchmarkst du unoptimierte builds?
Momentan kompiliere ich debug, aber ich denke die Verhältnisse sollten
ähnlich bleiben wenn man bei beiden Varianten die Optimierung
hochstellt. Zudem gibt es ja erst eine Variante die überhaupt geht
cppbert schrieb:> jv schrieb:>> Was ist an test_cpp alles falsch ? Sorry für die unprofessionelle>> Fragerei aber ich blick halt nicht durch.>> Kompilierst du im release also benchmarkst du unoptimierte builds?
Poste doch mal deinen ganzen Algorithmus
Und sorry mit std::array wird sich nichts veraendern
du kannst sicher sein, daß ich (wenn es erst mal läuft) auch mit -o3
probiere.
Zeitmessung ist so auf 1% reproduzierbar:
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
t2 - t1 ).count();
jv schrieb:> die ganze Datei ist viel zu lang und hat mit dem Problem auch> nichts zu> tun. Hab das jetzt nur hierfür extrahiert damit es nicht soviel zu lesen> ist.
Machst 5000x5000 Multiplikationen oder die notwendige Menge?
Es sind (fast) 5000 x 5000, nur ganz wenige stehen auf false. Einmal
5000 sind wegen der Tauschmöglichkeit der Produkte natürlich doppelt
gerechnet, aber das ist dann ja nur 1/5000 der Zeit und das zu Verwalten
wäre aufwendiger.
Die Menge ist auch nur so ganz grob und wenn man nicht aufpasst könnte
es schnell größer werden was sich aber vermeiden lassen sollte.
jv schrieb:> Es sind (fast) 5000 x 5000, nur ganz wenige stehen auf false.> Einmal> 5000 sind wegen der Tauschmöglichkeit der Produkte natürlich doppelt> gerechnet, aber das ist dann ja nur 1/5000 der Zeit und das zu Verwalten> wäre aufwendiger.> Die Menge ist auch nur so ganz grob und wenn man nicht aufpasst könnte> es schnell größer werden was sich aber vermeiden lassen sollte.http://cpp.sh/9xk5m, warum geht das bei dir nicht
Warum reicht nicht
for a = 0..5000
for b = a+1..5000
[a]*[b]
Und anstatt (wirklich sinnlos) auf indizierungsoptimierung zu hoffen
waere die Frage ob du nicht viel mehr erreichen kannst mit
vektorisierung/simd, wenn deine Datenlayout stimmt
es geht mir nicht in erste Linie um die Optimierung sondern um C++11 und
die Container zu probieren. Ich glaube es fehlt mir das "using" ... wo
ich noch nachlesen muss. Der Code im Link scheint nicht vollständig zu
sein.
jv schrieb:> es geht mir nicht in erste Linie um die Optimierung sondern um> C++11 und> die Container zu probieren. Ich glaube es fehlt mir das "using" ... wo> ich noch nachlesen muss. Der Code im Link scheint nicht vollständig zu> sein.
using ist das c++11 typedef, ich wollte so wenig code wie moeglich
schreiben (tablet)
http://cpp.sh/4r5lo
Dafür hast du viel zu oft von Zeit und Optimierung gesprochen - machst
du jetzt 5000x5000 oder die kurze Form?, das wuerde schon gehörig Zeit
sparen
Und falls dein std string waerend der Rechnung ueber 20 Zeichen kommt
ists locker 1000x langsamer den String zu zuweisen als die indizierung
Dein code läuft unter cpp.sh, ist dein kompiler zu alt?
jv@JamesWebb:~/kicad/build/debug$ gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is
NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.
Aber es geht zumindest mal das Array (ohne using alias).
Jetzt muss ich noch mal an der "range based loop" drehen und dann ab zur
Zeitmessung
jv schrieb:> jv@JamesWebb:~/kicad/build/debug$ gcc --version> gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0> Copyright (C) 2017 Free Software Foundation, Inc.> This is free software; see the source for copying conditions. There is> NO> warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR> PURPOSE.>> Aber es geht zumindest mal das Array (ohne using alias).> Jetzt muss ich noch mal an der "range based loop" drehen und dann ab zur> Zeitmessung
Also hat es sich jetzt per Magie gelöst? Was war dein Fehler
string-Header vergessen?
Zur Performanz: halte die Daten zusammen, besser fuer den Cache lieber
Structs of Arrays als Arrays of Structs:
https://en.m.wikipedia.org/wiki/AoS_and_SoA
Das bringt dir bei neueren Kompilern Autovektorisierung, ich hab hier
den gcc 9.2 und VS2019.x
Ja die LTS Version hat uraltes Zeugs drin. Aber soviel hat sich beim gcc
nicht getan. Wenn muss man clang/llvm nehmen.
>Was war dein Fehler string-Header vergessen?
War alles inkl. for loop etwas verkorkst und habe die gcc Meldungen
nicht geschnallt. So läufts mit normaler Schleife und range-based
Version:
[c]
std::array<r_data,20> cpp_lut {
true, "blabla",1000,
true, "hahaha",12,
false, "hihie",13
};
void test_cpp(void)
{
for (uint i=0; i<20; i++)
std::cout<<cpp_lut[i].e_name<<std::endl;
for (uint i=0: cpp_lut[i])
std::cout<<cpp_lut[i].e_name<<std::endl;
}
AoS gegen SoA habe ich auch schon überlegt weil ja immer nur die float
Zahl im kritischen Timing liegt. Allerdings muß auch abgefragt werden ob
überhaupt. Der String ist dann nur bei wenigen Treffern interessant.
Wird halt von der Initialisierung etwas unlesbar. Die erste r_data
Struktur hat 72 initialisierte Einträge und wird zu sqrt 72 expandiert
abgespeichert (daher die etwa 5000). Hier hätte man aber die Möglichkeit
das anders zu beschreiben und die 72 ersten Initialisierer im Quelltext
so zu belassen. Werde ich probieren ...
jv schrieb:> Wäre aber auch DER Anwendungsfall für Mulicore CPU mit> multithread. Habe> aber auch keine Ahnung wie das geht
Vielleicht, bring aber auch viel latenz, mit simd z.b. avx2 gehen auch
schon 8 multiplikationen auf einen schlag gerechnet werden
jv schrieb:> Ja die LTS Version hat uraltes Zeugs drin.
aber eigentlich kannst du was neueres installieren. ich habe auch 18.04
parallel und da ist gcc-9 drauf.
for( const r_data& td: cpp_lut )
Das ist harter Tobak für mich zum verstehen:
Vor der Doppelpunkt:
td ist ein Zeiger auf eine Adresse wo Elemente vom Typ r_data abgelegt
sind ?
Es ersetzt in diesem Fall das uint i als Index. Aber warum muß das const
sein ?
Ohne & würde td den Inhalt des ersten Elements bekommen, also auf
Adresse 0 oder 1 zeigen ?
Nach dem Doppelpunkt:
Hier ist die LookUpTabelle angegeben. Hiermit könnte doch GCC selbst
wissen auf welchen Typ er zeigen muss ?
J.V. schrieb:> td ist ein Zeiger auf eine Adresse wo Elemente vom Typ r_data abgelegt> sind ?
Nein, td ist eine Referenz, welche das aktuelle Element referenziert.
Weil es kein Zeiger ist, braucht es auch kein -> sondern ein .
J.V. schrieb:> Es ersetzt in diesem Fall das uint i als Index.
Es gibt hier gar keinen Index mehr, sondern nur noch eine Referenz auf
Elemente. Daher funktioniert das z.B. auch mit std::list; auf diese kann
nicht effizient per Index zugegriffen werden. Daher werden hier intern
Container-spezifische Iteratoren, kein Index, benutzt.
J.V. schrieb:> Aber warum muß das const> sein ?
Es ist nur erforderlich, wenn der Container (cpp_lut) selbst const ist -
sonst könnte man ja in der Schleife die Daten ändern! Wenn man einen
nicht-const Container iterieren möchte, kann man trotzdem "const"
hinschreiben, um die Elemente nicht versehentlich verändern zu können
(td.e_use = ... ergäbe dann einen Compiler-Fehler). Außerdem zeigt es
dem Leser des Codes klar an, dass hier nichts verändert wird -
verbessert die Lesbarkeit.
J.V. schrieb:> Ohne & würde td den Inhalt des ersten Elements bekommen
Genau, es würde kopiert statt referenziert.
J.V. schrieb:> also auf> Adresse 0 oder 1 zeigen ?
Da ohne "&" keine Referenz oder Pointer verwendet wird, wird der Inhalt
des Elements kopiert, somit wird hier nicht "gezeigt" und es sind
keine (sichtbaren) Adressen im Spiel.
J.V. schrieb:> Hiermit könnte doch GCC selbst> wissen auf welchen Typ er zeigen muss ?
Richtig. Daher kann man auch schreiben:
1
for(auto&td:cpp_lut){
Oder, um zu verdeutlichen dass man nichts ändern möchte
1
for(constauto&td:cpp_lut){
Hier ist nachzulesen was der Compiler mit diesem range-based for macht:
https://en.cppreference.com/w/cpp/language/range-for
Es werden im Endeffekt die Funktionen "begin" und "end" des Containers
genutzt um Iteratoren zu erhalten, und dessen "++" und "*" Operatoren
zum Weitergehen bzw. Zugriff. Daher kann man das auch mit allen
Containern der Standard-Bibliothek und auch eigenen Containern nutzem.
Weil eben keine Indices genutzt werden, klappt das auch mit Containern
ohne effizienten Index-Zugriff (z.B. verlinkte Listen) oder auch solche
ohne fixe Größe ("Streams"). Man kann den Containertyp auch leicht
ändern, ohne die range-for-loop anpassen zu müssen.
J.V. schrieb:> Aber warum muß das const sein ?
Es muss nicht const sein, aber wenn du das Element nicht ändern willst,
sollte es das sein. Wenn du das const nicht hinschreibst, sagst du damit
dem Compiler und dem Leser deines Code, dass du das Element in der
Schleife verändern willst.
Besten Dank, wieder was verstanden. Allerdings fällt die Zeitmessung
ernüchternd aus. Die Laufzeit steigt um 75% bei Verwendung eines
std::array<rdata.. anstelle r_data C99Array[]. Hatte ich zwar befürchtet
aber nicht soo schlimm.
Die Zugriffe sind gleich geblieben, also nix von den hübschen
Möglichkeiten des Containers wirklich genutzt. Es wird nur in
aufsteigender Reihenfolge ab Anfang gelesen und zwar immer bis variables
Ende. Geht das auch mit Iterator oder range based loop oder muss man da
size()-UnbenutztesEnde rechnen?
JV schrieb:> Die Laufzeit steigt um 75% bei Verwendung eines std::array<rdata..> anstelle r_data C99Array[].
Mit Optimierungen kompiliert? Da sollte kein Unterschied sein. Zeig mal
den Assembler Code und den genauen C++ Code.
Nein, es die der Debug compile aber auch dafür ist es heftig. Für
Release und Mix-Listing muss ich cmake des Projekts umbauen (da ich auch
noch keinen Debugger habe).
Noch ein Problem habe ich aber mit den C++11 std::array
Bei C99 kann ich schreiben:
#define LUT_SIZE sizeof ( C99_lut ) / sizeof ( r_data )
und dann r_data C99_lut[] = {Initialisiererliste...}
und LUT_SIZE dann in meine FOR Schleife eintragen
Bei C++11 muss ich die Größe bei Definition selbst als zweites Argument
angeben obwohl das der compiler aufgrund der Initialisierungsliste
selbst könnte. Erst danach liefert .size() für den Iterator das was ich
zuvor von Hand selbst abgezählt und eingetragen habe.
JV schrieb:> Nein, es die der Debug compile aber auch dafür ist es heftig
Nö, das ist normal. Da werden die Funktionsaufrufe nicht wegoptimiert.
Performance Vergleiche im Debug Modus sind sinnlos, die Performance ist
da eh mies.
JV schrieb:> Bei C++11 muss ich die Größe bei Definition selbst als zweites Argument> angeben obwohl das der compiler aufgrund der Initialisierungsliste> selbst könnte.
Ab C++17 kann er:
https://en.cppreference.com/w/cpp/container/array/deduction_guides
Ok, release build mit -o3 läuft. Ich habe folgende Zeiten in mSec:
C99 C++11
Debug 1100 1900
Release 777 777
Zur Rettung von GCC habe ich daneben noch festgestellt daß der Release
einiges schneller compiliert, was ich jetzt immer so mache solange ich
sowieso keinen Debugger habe.
JV schrieb:> Besten Dank, wieder was verstanden. Allerdings fällt die> Zeitmessung> ernüchternd aus. Die Laufzeit steigt um 75% bei Verwendung eines> std::array<rdata.. anstelle r_data C99Array[]. Hatte ich zwar befürchtet> aber nicht soo schlimm.
Bei C++ ist es schlimmer als bei C die Template-Aufrufe sind total
aufgebläht, und glaub uns das auch einfach mal
JV schrieb:> was ich jetzt immer so mache solange ich> sowieso keinen Debugger habe.
zu pauschal - wenn du komplexere Algorithmen hast die mehr
Kompiletimeoptimierung erlauben dauert Release viel länger
C++ ist lange nicht so "deterministisch" beim Kompilieren wie C
Btw: zeigt doch trotzdem mal deinen vollstaendigen (abgespeckten)
Algorithmus
bei deinen noch etwas dünnen Erfahrungswissen kannst du ganz leicht
übelste Bottlenecks einbauen ohne das dues erkennst
Oben am Verzeichnis siehst du das Paket an dem ich bastle (Kicad). Die
massgebliche Schleife rauszunehmen ist etwas aufwendig und ich habe
derweil noch selbst etliche Ideen das deutlich flotter zu kriegen. Eine
davon ist AoS und SoA, eine andere Float gegen uint32_t zu tauschen und
die dritte ist zwei hintereinanderliegende verschachtelte Śchleifen in
eine verschachtelte Schleife zu legen wo beide Vorgänge gerechnet
werden. Die Operanden sind zur unterschiedlichen Rechnung gleich, so daß
es mehr Cache hits geben müsste.
Also nochmal von vorhin scheint das korrektes cpp++11 ohne C Altlast zu
sein:
1
std::array<r_data,20>cpp_lut{{
2
true,"blabla",1000,
3
true,"hahaha",12,
4
false,"hihie",13
5
}};
6
voidlut_dump(uintlen)
7
{
8
for(auto&idx:cpp_lut)
9
std::cout<<idx.e_name<<std::endl;
10
}
Nachdem ich jetzt weis, dass in cpp_lut nur 3 Einträge drinstehen,
später vielleicht auch was anderes, möchte ich auch nur len ausgeben.
Kann ich dann den hübschen idx iterator wieder vergessen und / oder wird
aus der range based C++11 loop wieder eine normale FOR Schleife ohne
Doppelpunkt ? Ein Bereichscheck ist wohl nicht nötig weil das len beim
schreiben schon gemacht wird.
Du solltest zumindest erst grob die Sprache lernen bevor du dich mit
Dingen wie Cache-Misses auseinandersetzt. Bei allem Respekt aber bei
deinem aktuellen Wissensstand ist das sinnlos.