Summe schneller machen?
-
1.) Stichwort: horizontal add (SSE3 oder SSE4)
2.) 4 Summen "gleichzeitig" bei SSE2Gibt es einen SSE Befehl der auf eine einzige Summenvariable den Inhalt mehrerer Felder addiert?
Wurde schon angesprochen, du kannst die Summe auf mehrere Variablen aufteilen und diese am Ende einzeln aufaddieren.
-
schnellsummer schrieb:
Was bringt einem hier SSE? Gibt es einen SSE Befehl der auf eine einzige Summenvariable den Inhalt mehrerer Felder addiert?
SSE müsste das intern doch auch einfach seriell machen, oder?Du berechnest mehrere Summen vektoriell und addierst ganz zum Schluss die einzelnen Komponenten des Ergebnisvektors. Also im Prinzip so:
s0 = a0 + a4 + a8 + a12 + ... s1 = a1 + a5 + a9 + a13 + ... s2 = a2 + a6 + a10 + a14 + ... s3 = a3 + a7 + a11 + a15 + ... ^ ^ ^ ^ parallele Additionen sum = s0 + s1 + s2 + s3;
Allerdings wird das nichts bringen, wenn die Daten nicht schon im Cache liegen; denn dann ist der Speicherzugriff sehr wahrscheinlich der Flaschenhals.
-
nevermore schrieb:
Ethon schrieb:
nevermore schrieb:
Wenn du nur einen Akkumulator verwendest könnte das schlecht für das Pipelining der CPU sein. Ethon's Ansatz mit mehreren Akkumulatoren (also r+=... s+=... t+=... und return r+s+t statt nur auf r zu addieren) könnte noch was bringen (zusätzlich zu SSE & Co).
Müsste man dabei aber nicht aufpassen nicht mehr Akkumulatoren zu verwenden als freie Register zur Verfügung stehen? Ich denke da ist jeder Compiler zu schlau, aber angenommen ein Akkumulator lebt als Stackvariable dann wäre das fatal.
Moderne CPUs haben mehr Register als über den Befehlssatz benutzt werden können, die Technik nennt sich "Register Renaming".
diese werden aber fuer die OOO ausfuehrung benutzt, nicht dafuer um register spilling abzufangen. wenn du also mehr variablen nutzt als register frei sind, wird deine cpu diese in den speicher schreiben, weil...
- der compiler der cpu nicht mitteilen kann, dass diese memory writes nur auslagerungen sind
- die cpu nicht annehmen darf dass irgend ein write unterlassen werden koennte vor einem read (der ja zwangslaeufig folgen wird wenn zuviele temporaries fuer das register set da sind).
@nevermore
ja das koennte schlecht sein, aber an sich sollte das bei einem add kein problem sein.hier mal kurz einen benchmark zusammengehauen
#include <stdint.h> #include <malloc.h> #include <time.h> #include <stdio.h> #include <emmintrin.h> const size_t LOOPS = 16; const size_t SIZE = 128*1024*1024; template<size_t Index> struct TAdd { static uint32_t Eval(const uint32_t* pBuffer) { return pBuffer[SIZE-Index]+TAdd<Index-1u>::Eval(pBuffer); } }; template<> struct TAdd<0> { static uint32_t Eval(const uint32_t* pBuffer){return 0;} }; int main(int argc,char* argv[]) { uint32_t* pBuffer = reinterpret_cast<uint32_t*>(_mm_malloc(SIZE*sizeof(*pBuffer),16)); for(size_t a=0;a<SIZE;a++) pBuffer[a]=a*17+13; uint32_t Sum0=0; uint32_t Sum1=0; uint32_t Sum2=0; uint32_t Sum3=0; const float T0 = static_cast<float>(clock())/CLOCKS_PER_SEC; for(size_t b=0;b<LOOPS;b++) for(size_t a=0;a<SIZE;a++) Sum0+=pBuffer[a]; const float T1 = static_cast<float>(clock())/CLOCKS_PER_SEC; uint32_t SumA=0,SumB=0,SumC=0,SumD=0; for(size_t b=0;b<LOOPS;b++) for(size_t a=0;a<SIZE;a+=4) { SumA+=pBuffer[a]; SumB+=pBuffer[a+1]; SumC+=pBuffer[a+2]; SumD+=pBuffer[a+3]; } Sum1=SumA+SumB+SumC+SumD; const float T2 = static_cast<float>(clock())/CLOCKS_PER_SEC; __m128i SumA4 = _mm_set1_epi32(0); __m128i SumB4 = _mm_set1_epi32(0); __m128i SumC4 = _mm_set1_epi32(0); __m128i SumD4 = _mm_set1_epi32(0); for(size_t b=0;b<LOOPS;b++) for(size_t a=0;a<SIZE;a+=16) { SumA4=_mm_add_epi32(SumA4,*reinterpret_cast<__m128i*>(pBuffer+a)); SumB4=_mm_add_epi32(SumB4,*reinterpret_cast<__m128i*>(pBuffer+a+4)); SumC4=_mm_add_epi32(SumC4,*reinterpret_cast<__m128i*>(pBuffer+a+8)); SumD4=_mm_add_epi32(SumD4,*reinterpret_cast<__m128i*>(pBuffer+a+12)); } SumA4 = _mm_add_epi32(_mm_add_epi32(SumA4,SumB4),_mm_add_epi32(SumC4,SumD4)); Sum2 = SumA4.m128i_i32[0]+SumA4.m128i_i32[1]+SumA4.m128i_i32[2]+SumA4.m128i_i32[3];//wird auf dem gcc nicht bauen, da muesst ihr halt eine union {uint32 t[4];__m128i T;}... basteln const float T3 = static_cast<float>(clock())/CLOCKS_PER_SEC; for(size_t b=0;b<LOOPS;b++) Sum3+=0;//TAdd<SIZE>::Eval(pBuffer); const float T4 = static_cast<float>(clock())/CLOCKS_PER_SEC; printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum0,T1-T0,LOOPS/1000000.f*SIZE/(T1-T0),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T1-T0)); printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum1,T2-T1,LOOPS/1000000.f*SIZE/(T2-T1),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T2-T1)); printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum2,T3-T2,LOOPS/1000000.f*SIZE/(T3-T2),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T3-T2)); printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum3,T4-T3,LOOPS/1000000.f*SIZE/(T4-T3),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T4-T3)); return 0; }
1 ist das vom op
2 ist 4x unrolled
3 ist 4x unrolled sse
4 ist template (falls jemand die zeit hat es durchbauen zu lassen :D), oder hab ich da was unsmart gemacht?
-
Kannst du auch noch eine kurze Version mit mehreren Threads bauen? Ich bin nicht so gut mit den C-Threads, kann nur C++
. Aber die CPU-Hersteller hätten so gerne, dass man alle Kerne voll ausnutzt, sollen sie mal zeigen was besser ist: SSE oder mehr Kerne? :xmas1:
-
SeppJ schrieb:
Kannst du auch noch eine kurze Version mit mehreren Threads bauen? Ich bin nicht so gut mit den C-Threads, kann nur C++
. Aber die CPU-Hersteller hätten so gerne, dass man alle Kerne voll ausnutzt, sollen sie mal zeigen was besser ist: SSE oder mehr Kerne? :xmas1:
Summe mit mehreren Kernen? Da ist doch sowieso der Speicher der Flaschenhals, ein Kern allein kann doch schon die komplette Speicherbandbreite ausnutzen.
-
rapso schrieb:
nevermore schrieb:
Ethon schrieb:
nevermore schrieb:
Wenn du nur einen Akkumulator verwendest könnte das schlecht für das Pipelining der CPU sein. Ethon's Ansatz mit mehreren Akkumulatoren (also r+=... s+=... t+=... und return r+s+t statt nur auf r zu addieren) könnte noch was bringen (zusätzlich zu SSE & Co).
Müsste man dabei aber nicht aufpassen nicht mehr Akkumulatoren zu verwenden als freie Register zur Verfügung stehen? Ich denke da ist jeder Compiler zu schlau, aber angenommen ein Akkumulator lebt als Stackvariable dann wäre das fatal.
Moderne CPUs haben mehr Register als über den Befehlssatz benutzt werden können, die Technik nennt sich "Register Renaming".
diese werden aber fuer die OOO ausfuehrung benutzt, nicht dafuer um register spilling abzufangen. wenn du also mehr variablen nutzt als register frei sind, wird deine cpu diese in den speicher schreiben, weil...
- der compiler der cpu nicht mitteilen kann, dass diese memory writes nur auslagerungen sind
- die cpu nicht annehmen darf dass irgend ein write unterlassen werden koennte vor einem read (der ja zwangslaeufig folgen wird wenn zuviele temporaries fuer das register set da sind).
@nevermore
ja das koennte schlecht sein, aber an sich sollte das bei einem add kein problem sein.hier mal kurz einen benchmark zusammengehauen
#include <stdint.h> #include <malloc.h> #include <time.h> #include <stdio.h> #include <emmintrin.h> const size_t LOOPS = 16; const size_t SIZE = 128*1024*1024; template<size_t Index> struct TAdd { static uint32_t Eval(const uint32_t* pBuffer) { return pBuffer[SIZE-Index]+TAdd<Index-1u>::Eval(pBuffer); } }; template<> struct TAdd<0> { static uint32_t Eval(const uint32_t* pBuffer){return 0;} }; int main(int argc,char* argv[]) { uint32_t* pBuffer = reinterpret_cast<uint32_t*>(_mm_malloc(SIZE*sizeof(*pBuffer),16)); for(size_t a=0;a<SIZE;a++) pBuffer[a]=a*17+13; uint32_t Sum0=0; uint32_t Sum1=0; uint32_t Sum2=0; uint32_t Sum3=0; const float T0 = static_cast<float>(clock())/CLOCKS_PER_SEC; for(size_t b=0;b<LOOPS;b++) for(size_t a=0;a<SIZE;a++) Sum0+=pBuffer[a]; const float T1 = static_cast<float>(clock())/CLOCKS_PER_SEC; uint32_t SumA=0,SumB=0,SumC=0,SumD=0; for(size_t b=0;b<LOOPS;b++) for(size_t a=0;a<SIZE;a+=4) { SumA+=pBuffer[a]; SumB+=pBuffer[a+1]; SumC+=pBuffer[a+2]; SumD+=pBuffer[a+3]; } Sum1=SumA+SumB+SumC+SumD; const float T2 = static_cast<float>(clock())/CLOCKS_PER_SEC; __m128i SumA4 = _mm_set1_epi32(0); __m128i SumB4 = _mm_set1_epi32(0); __m128i SumC4 = _mm_set1_epi32(0); __m128i SumD4 = _mm_set1_epi32(0); for(size_t b=0;b<LOOPS;b++) for(size_t a=0;a<SIZE;a+=16) { SumA4=_mm_add_epi32(SumA4,*reinterpret_cast<__m128i*>(pBuffer+a)); SumB4=_mm_add_epi32(SumB4,*reinterpret_cast<__m128i*>(pBuffer+a+4)); SumC4=_mm_add_epi32(SumC4,*reinterpret_cast<__m128i*>(pBuffer+a+8)); SumD4=_mm_add_epi32(SumD4,*reinterpret_cast<__m128i*>(pBuffer+a+12)); } SumA4 = _mm_add_epi32(_mm_add_epi32(SumA4,SumB4),_mm_add_epi32(SumC4,SumD4)); Sum2 = SumA4.m128i_i32[0]+SumA4.m128i_i32[1]+SumA4.m128i_i32[2]+SumA4.m128i_i32[3];//wird auf dem gcc nicht bauen, da muesst ihr halt eine union {uint32 t[4];__m128i T;}... basteln const float T3 = static_cast<float>(clock())/CLOCKS_PER_SEC; for(size_t b=0;b<LOOPS;b++) Sum3+=0;//TAdd<SIZE>::Eval(pBuffer); const float T4 = static_cast<float>(clock())/CLOCKS_PER_SEC; printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum0,T1-T0,LOOPS/1000000.f*SIZE/(T1-T0),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T1-T0)); printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum1,T2-T1,LOOPS/1000000.f*SIZE/(T2-T1),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T2-T1)); printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum2,T3-T2,LOOPS/1000000.f*SIZE/(T3-T2),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T3-T2)); printf("%8d %2.3fs %2.3fMIntop/s %2.3fMB/s\n",Sum3,T4-T3,LOOPS/1000000.f*SIZE/(T4-T3),LOOPS/1000000.f*SIZE*sizeof(*pBuffer)/(T4-T3)); return 0; }
1 ist das vom op
2 ist 4x unrolled
3 ist 4x unrolled sse
4 ist template (falls jemand die zeit hat es durchbauen zu lassen :D), oder hab ich da was unsmart gemacht?VS 2012 mit SSE code-generation aktiviert (advanced vector extensions bringt das programm interessanterweise zum abstürzen. unterstützt meine cpu das nicht?), i5 2500k:
1. 0.638s
2. 1.347s
3. 0.655sDas hat ja nicht viel gebracht
-
Auf meinem Intel(R) Core(TM) i7-2670QM CPU @ 2.20GHz
[ferler@notebook-fedora17 ~]$ g++ -o bench -msse -O3 -march=native bla.cpp [ferler@notebook-fedora17 ~]$ ./bench 1073741824 0.410s 5237.765MIntop/s 20951.061MB/s 1073741824 0.200s 10737.417MIntop/s 42949.668MB/s 1073741824 0.510s 4210.752MIntop/s 16843.008MB/s 0 0.000s infMIntop/s infMB/s
Ziemlich abweichende Ergebnisse?
-
Einfach #ifdef Compiler und die Sache wird endlich wieder leserlich
-
Wie kommt es eigentlich dass auf dem GCC mit -O3 das manuelle Loop Enrolling schneller ist? Das Flag bedeutet doch dass der Compiler da sehr speicherverschwenderisch loslegen darf, warum tut er das nicht?
-
rapso schrieb:
nevermore schrieb:
Ethon schrieb:
nevermore schrieb:
Wenn du nur einen Akkumulator verwendest könnte das schlecht für das Pipelining der CPU sein. Ethon's Ansatz mit mehreren Akkumulatoren (also r+=... s+=... t+=... und return r+s+t statt nur auf r zu addieren) könnte noch was bringen (zusätzlich zu SSE & Co).
Müsste man dabei aber nicht aufpassen nicht mehr Akkumulatoren zu verwenden als freie Register zur Verfügung stehen? Ich denke da ist jeder Compiler zu schlau, aber angenommen ein Akkumulator lebt als Stackvariable dann wäre das fatal.
Moderne CPUs haben mehr Register als über den Befehlssatz benutzt werden können, die Technik nennt sich "Register Renaming".
diese werden aber fuer die OOO ausfuehrung benutzt, nicht dafuer um register spilling abzufangen. wenn du also mehr variablen nutzt als register frei sind, wird deine cpu diese in den speicher schreiben, weil...
- der compiler der cpu nicht mitteilen kann, dass diese memory writes nur auslagerungen sind
- die cpu nicht annehmen darf dass irgend ein write unterlassen werden koennte vor einem read (der ja zwangslaeufig folgen wird wenn zuviele temporaries fuer das register set da sind).
Okay, stimmt.
@nevermore
ja das koennte schlecht sein, aber an sich sollte das bei einem add kein problem sein.Ich glaube schon, dass das was bringt -- wenn die Daten in den Cache passen und das ganze nicht durch die Speicheranbindung limitiert ist.
Was ich mir schon vorstellen könnte ist, dass bei einem Integer der Compiler selber mehrere Akkumulatoren nutzt. Bei Floating Point vermutlich nicht, das könnte ja potenziell das Ergebnis abändern.
-
Ethon schrieb:
Summe mit mehreren Kernen? Da ist doch sowieso der Speicher der Flaschenhals, ein Kern allein kann doch schon die komplette Speicherbandbreite ausnutzen.
Wie ist es da eigentlich genau: Wenn ich ein Programm hab, bei dem der Speicher der Flaschenhals ist, dann macht es auch keinen Sinn mehr, dieses Programm mehrfach (mit verschiedenen Parametern etc) parallel zu starten, weil der Speicher eben bereits am limit ist und sich die Programme gegenseitig die Bandbreite klaun. oder? Oder gibt s da vielleicht inzwischen auch so etwas wie Parallelisierung?
-
otze schrieb:
Ethon schrieb:
Summe mit mehreren Kernen? Da ist doch sowieso der Speicher der Flaschenhals, ein Kern allein kann doch schon die komplette Speicherbandbreite ausnutzen.
Wie ist es da eigentlich genau: Wenn ich ein Programm hab, bei dem der Speicher der Flaschenhals ist, dann macht es auch keinen Sinn mehr, dieses Programm mehrfach (mit verschiedenen Parametern etc) parallel zu starten, weil der Speicher eben bereits am limit ist und sich die Programme gegenseitig die Bandbreite klaun. oder? Oder gibt s da vielleicht inzwischen auch so etwas wie Parallelisierung?
Ich denke es könnte schneller sein einen großen Speicherbereich in den CPU-Cache zu laden als ihn in kleineren Stückchen (1-16 Byte) einzulesen.
Mit mehreren CPUs hat man auch mehr Cache. Aber ich habe nicht viel Ahnung davon ...
-
SeppJ schrieb:
Kannst du auch noch eine kurze Version mit mehreren Threads bauen? Ich bin nicht so gut mit den C-Threads, kann nur C++
. Aber die CPU-Hersteller hätten so gerne, dass man alle Kerne voll ausnutzt, sollen sie mal zeigen was besser ist: SSE oder mehr Kerne? :xmas1:
an sich ganz einfach, vor der inneren schleife
#pragma omp parallel for
eventuel musst du das uint32_t in ein int umwandeln, je nachdem wie penibel der compiler ist.
@Ethon
koennte mir vorstellen, dass vielleicht turbo boost ein wenig mit reinspielt, gerade mobile cpus takten sehr weit runter (manchmal <0.8Ghz) und dann hoch auf 3GHz+ um dann festzustellen es ist zuviel des guten und danng ehen die wieder auf <2GHz. vielleicht ganz oben den loop counter weiter hochdrehen, dann laeuft der test insgesammt laenger, aber die GIntop/s sollten stabil bleiben.
Vielleicht auch die anderen gcc flags probieren, O3 macht nicht alles, und gcc ist sehr penibel was z.b. restrict usw angeht, vielleicht unrollt es deswegen nicht.otze schrieb:
Ethon schrieb:
Summe mit mehreren Kernen? Da ist doch sowieso der Speicher der Flaschenhals, ein Kern allein kann doch schon die komplette Speicherbandbreite ausnutzen.
Wie ist es da eigentlich genau: Wenn ich ein Programm hab, bei dem der Speicher der Flaschenhals ist, dann macht es auch keinen Sinn mehr, dieses Programm mehrfach (mit verschiedenen Parametern etc) parallel zu starten, weil der Speicher eben bereits am limit ist und sich die Programme gegenseitig die Bandbreite klaun. oder? Oder gibt s da vielleicht inzwischen auch so etwas wie Parallelisierung?
das stimmt zu 50%
wenn du linear speicher liest, bist du an sich nur durchsatz limitiert und moderne cpus haben bei einem core locker genug leistung um am speicher zu saturieren (die benchmarks die memory performance messen laufen entsprechend nur auf einem core). wenn man sich zudem SSE oder AVX bedient, dann ziehen die cpus ziemlich genau am limit vom L1 cache.
-Nahalem: 16byte/cycle -> SSE breite
-sandy bridge: 32byte/cycle -> avx breite
-haswell wird 64byte/cycle leisten -> avx mit FMA, bei fma3 brauchst du pro takt zwei avx quellregister, fma wuerde nichts bringen ohne die doppelte bandbreite, weil man sonst entweder trotzdem nur alle 2 cycles ein fma befuellen kann oder nur lokal auf registern FMAs machen koennte (die benchmarks ala linpack machen das nicht).64byte/cycle bei 3GHz-4GHz -> 200GB/s bis 250GB/s pro core, bei 4 bis 6 cores hast du entsprechend bis 1.5TB/s bandwidth... ein quad channel prozessor von intel schaft ca 50GB/s.
also ja, ein core kann saturieren.
man muss random reads beachten, dabei saturiert man am zugriff selbst, nicht an der bandbreite, man kann also ein paar cache hits lang arbeiten udn dann wieder wartet der core, in dem fall bietet sich hyper threading sehr an, da der andere thread seine cache hits nutzen kann waehrend der erste stallt. (hab in manchen faellen 30% geschwindigkeitzuwachs durch hyperthreading). man kann sich durch cache polution aber natuerlich die threads auch zerballern und dann ist hyperthreading kontraproduktiv.
Ethon schrieb:
Ich denke es könnte schneller sein einen großen Speicherbereich in den CPU-Cache zu laden als ihn in kleineren Stückchen (1-16 Byte) einzulesen.
Mit mehreren CPUs hat man auch mehr Cache. Aber ich habe nicht viel Ahnung davon ...cpus arbeiten eh auf cache lines, ob du 1 byte oder 16 laedst, es werden immer 32/64/128 bytes in den cache geladen, je nach cpu. die cache line breite entspricht der 'wort' breite vom ram, also auch wenn man nur 1byte cachen wuerde, die speicherriegel liefern beim auslesen immer die gleiche menge zureuck.
man kann vielleicht an 'ports' limitiert sein von der load/store einheit. diese hat oft unabhaengig der speichermenge die man transferiert, eine fixe groesse. bei den aktuellen intel cpus sind es glaube ich 4 eintraege, schreibt/liest man also 4 einzelne bytes oder 4 mal 32byte, befuellt man diese queue gleich. diese einheit kuemmert sich auch drum unaligned reads durch zwei seperate reads + mergen der bytes zu emulieren.
ich denke das ist weshalb die sse version vielleicht leicht schneller ist. aber an sich sollte jeder i7 prozessor auf 20GB/s (bzw die 3820k 3930k und 3960x auf 50GB/s) kommen.
-
Danke für die ausführlichen Antworten.
Loops mal auf 512 gestellt:
0 12.640s 5436.667MIntop/s 21746.670MB/s 0 6.270s 10960.045MIntop/s 43840.180MB/s 0 15.970s 4303.035MIntop/s 17212.141MB/s
Immer noch Manuelles loop unrolling > einfache variante > sse.
Welches Flags wären denn einen Versuch wert? Afaik sind bei O3 ja die ganzen aggressiven Optimierungen alle dabei.
-
laut http://ark.intel.com/products/53469/Intel-Core-i7-2670QM-Processor-6M-Cache-up-to-3_10-GHz
hat deine cpu 21.3 GB/s (hatte den benchmark in ner viertelstunde geschrieben deswegen nicht /1024 sondern /1000 geteilt, aber auch dann waerst du noch bei
0 12.640s 5436.667MIntop/s 20739.240MB/s 0 6.270s 10960.045MIntop/s 41809.253MB/s 0 15.970s 4303.035MIntop/s 16414.776MB/s
die 0 am anfang wirkt suspekt und die 41GB/s sind eindeutig ueber dem limit deiner cpu.
hmhmhm. hast du etwas am code modifiziert um es zum laufen zu bringen?
[edit]
bau mal mit -O2, vielleicht geht eine optimierung schief, O2 sollte recht sicher sein, imo.
-
Jetzt ist es anders:
0 42.300s 1624.574MIntop/s 6498.295MB/s 0 26.790s 2565.117MIntop/s 10260.467MB/s 0 17.170s 4002.300MIntop/s 16009.198MB/s
Werde mir beide Versionen morgen mal im Decompiler ansehen.
-
bin gespannt auf den assembler dump, jetzt scheinen die werte viel zu niedrig zu sein.
in den ersten testlaeufen schien ja das ergebnis zu stimmen, muss also eine funky optimierung sein. assembler assembler assembler :xmas1:
-
raps schrieb:
muss also eine funky optimierung sein.
Das spricht doch auch für einen Code, wenn er sich gut vom Compiler optimieren lässt.
-
SeppJ schrieb:
raps schrieb:
muss also eine funky optimierung sein.
Das spricht doch auch für einen Code, wenn er sich gut vom Compiler optimieren lässt.
zweischneidig, entweder der code ist gut oder falsch und der compiler exploited border cases um optimierungen durchzufuehren z.b.
for(...) { *pMeinFloatPointer=... if(*reinterpret_cast<int*>(pMeinFloatPointer)==0) break; ... }
ich denke in VS wuerde es laufen wie man es sich vorstellt, in gcc mit -O0 auch, aber bei -O3 wird gcc annehmen dass das float-write und int-read nicht aliasen und koennte den float write sogar nur als registerwert zwischenspeichern und ausserhalb vom loop dann rausschreiben.
gut zu optimierbarer code, aber ein programmierfehler