Forum: PC-Programmierung C++11 std::array mit struct


von jv (Gast)


Lesenswert?

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(auto i{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
struct r_data    {
2
                     bool        e_use;
3
                     std::string e_name;
4
                     double      e_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.

von jv (Gast)


Lesenswert?

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>

von Oliver S. (oliverso)


Lesenswert?

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

von cppbert (Gast)


Lesenswert?

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

von cppbert (Gast)


Lesenswert?

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

von jv (Gast)


Lesenswert?

Hier noch mal mein Originaltext zusammenkopiert
1
struct r_data  {
2
                     bool        e_use;
3
                     std::string e_name;
4
                     double      e_value;
5
               };
6
7
8
r_data c99_lut[20] { 
9
                       {true, "blabla",1000},\
10
                       {true, "hahaha",12},\
11
                       {false, "hihie",13}
12
                   };
13
14
void test_c99 (void)
15
{
16
    for ( uint i = 0; i < sizeof (c99_lut ) / sizeof ( r_data ); i++ )
17
    {
18
        std::cout<<\
19
        i<<") "<<\
20
        c99_lut[i].e_use<<" "<<\
21
        c99_lut[i].e_name<<\
22
        " Double = "<<(c99_lut[i].e_value)<<\
23
        std::endl;
24
    }
25
}
26
27
/*
28
 * 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

von cppbert (Gast)


Lesenswert?

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?

von jv (Gast)


Lesenswert?

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

von cppbert (Gast)


Lesenswert?

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

von cppbert (Gast)


Lesenswert?

Templates werden anders optimiert d.h. der Debug Code kann mit Templates 
fetter sein

von jv (Gast)


Lesenswert?

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.

von jv (Gast)


Lesenswert?

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();

von cppbert (Gast)


Lesenswert?

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?

von jv (Gast)


Lesenswert?

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.

von cppbert (Gast)


Lesenswert?

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]

von cppbert (Gast)


Lesenswert?

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

von jv (Gast)


Lesenswert?

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.

von jv (Gast)


Lesenswert?

nettes Spielzeug, habs mal hier eingegeben:

Short URL: cpp.sh/5nh75p

von cppbert (Gast)


Lesenswert?

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

von cppbert (Gast)


Lesenswert?

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?

von jv (Gast)


Lesenswert?

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

von cppbert (Gast)


Lesenswert?

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

von jv (Gast)


Lesenswert?

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;
}

von jv (Gast)


Lesenswert?

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 ...

von jv (Gast)


Lesenswert?

Wäre aber auch DER Anwendungsfall für Mulicore CPU mit multithread. Habe 
aber auch keine Ahnung wie das geht

von cppbert (Gast)


Lesenswert?

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

von zitter_ned_aso (Gast)


Lesenswert?

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.

von guest (Gast)


Lesenswert?

jv schrieb:
> ... range-based ...
Oder so:
1
for( const r_data& td: cpp_lut )
2
{
3
    std::cout << td.e_name << std::endl;
4
}

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

In C++11 brauchts doppelte geschweifte Klammern:
1
std::array<r_data,20> cpp_lut {{
2
                                  true, "blabla",1000,
3
                                  true, "hahaha",12,
4
                                  false, "hihie",13
5
                              }};
Erst ab C++14 reichen einfache (doppelte gehen aber weiterhin).

von J.V. (Gast)


Lesenswert?

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 ?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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(const auto& 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.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

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.

von JV (Gast)


Lesenswert?

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?

von Niklas Gürtler (Gast)


Lesenswert?

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.

von JV (Gast)


Lesenswert?

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.

von Niklas Gürtler (Gast)


Lesenswert?

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

von JV (Gast)


Lesenswert?

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.

von cppbert (Gast)


Lesenswert?

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

von cppbert (Gast)


Lesenswert?

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

von JV (Gast)


Lesenswert?

Ja, bei Kindern ist es ja auch so. Sie tun auch nicht das was Papa sagt 
und müssen immer erst selbst mal auf die Fresse fliegen...

von JV (Gast)


Lesenswert?

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.

von JV (Gast)


Lesenswert?

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
void lut_dump(uint len)
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.

von Vincent H. (vinci)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.