Ich habe beschlossen, den Test auf meinem eigenen Computer mit Lik32-Code erneut auszuführen. Ich musste es ändern, weil mein Windows- oder Compiler dachte, dass eine hohe Auflösung 1 ms beträgt
mingw32-g ++. exe -O3 -Wall -std = c ++ 11 -fexceptions -g
vector<int> rand_vec(10000000);
GCC hat für beide Originalcodes dieselbe Transformation durchgeführt.
Beachten Sie, dass nur die beiden ersten Bedingungen getestet werden, da die dritte immer wahr sein muss. GCC ist hier eine Art Sherlock.
Umkehren
.L233:
mov DWORD PTR [rsp+104], 0
mov DWORD PTR [rsp+100], 0
mov DWORD PTR [rsp+96], 0
call std::chrono::_V2::system_clock::now()
mov rbp, rax
mov rax, QWORD PTR [rsp+8]
jmp .L219
.L293:
mov edx, DWORD PTR [rsp+104]
add edx, 1
mov DWORD PTR [rsp+104], edx
.L217:
add rax, 4
cmp r14, rax
je .L292
.L219:
mov edx, DWORD PTR [rax]
cmp edx, 94
jg .L293 // >= 95
cmp edx, 19
jg .L218 // >= 20
mov edx, DWORD PTR [rsp+96]
add rax, 4
add edx, 1 // < 20 Sherlock
mov DWORD PTR [rsp+96], edx
cmp r14, rax
jne .L219
.L292:
call std::chrono::_V2::system_clock::now()
.L218: // further down
mov edx, DWORD PTR [rsp+100]
add edx, 1
mov DWORD PTR [rsp+100], edx
jmp .L217
And sorted
mov DWORD PTR [rsp+104], 0
mov DWORD PTR [rsp+100], 0
mov DWORD PTR [rsp+96], 0
call std::chrono::_V2::system_clock::now()
mov rbp, rax
mov rax, QWORD PTR [rsp+8]
jmp .L226
.L296:
mov edx, DWORD PTR [rsp+100]
add edx, 1
mov DWORD PTR [rsp+100], edx
.L224:
add rax, 4
cmp r14, rax
je .L295
.L226:
mov edx, DWORD PTR [rax]
lea ecx, [rdx-20]
cmp ecx, 74
jbe .L296
cmp edx, 19
jle .L297
mov edx, DWORD PTR [rsp+104]
add rax, 4
add edx, 1
mov DWORD PTR [rsp+104], edx
cmp r14, rax
jne .L226
.L295:
call std::chrono::_V2::system_clock::now()
.L297: // further down
mov edx, DWORD PTR [rsp+96]
add edx, 1
mov DWORD PTR [rsp+96], edx
jmp .L224
Das sagt uns also nicht viel, außer dass der letzte Fall keine Verzweigungsvorhersage benötigt.
Jetzt habe ich alle 6 Kombinationen der Ifs ausprobiert, die Top 2 sind die Originalumkehrung und sortiert. hoch ist> = 95, niedrig ist <20, mittel ist 20-94 mit jeweils 10000000 Iterationen.
high, low, mid: 43000000ns
mid, low, high: 46000000ns
high, mid, low: 45000000ns
low, mid, high: 44000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 44000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 45000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 42000000ns
mid, low, high: 46000000ns
high, mid, low: 46000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 43000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 44000000ns
low, mid, high: 44000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 43000000ns
mid, low, high: 48000000ns
high, mid, low: 44000000ns
low, mid, high: 44000000ns
mid, high, low: 45000000ns
low, high, mid: 45000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 43000000ns
mid, low, high: 47000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 46000000ns
low, high, mid: 44000000ns
high, low, mid: 43000000ns
mid, low, high: 46000000ns
high, mid, low: 45000000ns
low, mid, high: 45000000ns
mid, high, low: 45000000ns
low, high, mid: 44000000ns
high, low, mid: 42000000ns
mid, low, high: 46000000ns
high, mid, low: 44000000ns
low, mid, high: 45000000ns
mid, high, low: 45000000ns
low, high, mid: 44000000ns
1900020, 7498968, 601012
Process returned 0 (0x0) execution time : 2.899 s
Press any key to continue.
Warum ist die Reihenfolge hoch, niedrig, med dann schneller (geringfügig)?
Weil das Unvorhersehbarste das Letzte ist und daher niemals durch einen Verzweigungsprädiktor geführt wird.
if (i >= 95) ++nHigh; // most predictable with 94% taken
else if (i < 20) ++nLow; // (94-19)/94% taken ~80% taken
else if (i >= 20 && i < 95) ++nMid; // never taken as this is the remainder of the outfalls.
So werden die Zweige vorhergesagt genommen, genommen und der Rest mit
6% + (0,94 *) 20% falsche Vorhersagen.
"Sortiert"
if (i >= 20 && i < 95) ++nMid; // 75% not taken
else if (i < 20) ++nLow; // 19/25 76% not taken
else if (i >= 95) ++nHigh; //Least likely branch
Die Zweige werden mit nicht genommen, nicht genommen und Sherlock vorhergesagt.
25% + (0,75 *) 24% falsche Vorhersagen
Geben Sie eine Differenz von 18-23% an (gemessene Differenz von ~ 9%), aber wir müssen Zyklen berechnen, anstatt% falsch vorherzusagen.
Nehmen wir an, dass auf meiner Nehalem-CPU eine Strafe von 17 Zyklen falsch vorhergesagt wird und dass jede Überprüfung 1 Zyklus dauert (4-5 Anweisungen) und die Schleife auch einen Zyklus dauert. Die Datenabhängigkeiten sind die Zähler und die Schleifenvariablen, aber sobald die falschen Vorhersagen aus dem Weg sind, sollte dies das Timing nicht beeinflussen.
Für "Umkehren" erhalten wir also die Timings (dies sollte die in der Computerarchitektur verwendete Formel sein: Ein quantitativer Ansatz IIRC).
mispredict*penalty+count+loop
0.06*17+1+1+ (=3.02)
(propability)*(first check+mispredict*penalty+count+loop)
(0.19)*(1+0.20*17+1+1)+ (= 0.19*6.4=1.22)
(propability)*(first check+second check+count+loop)
(0.75)*(1+1+1+1) (=3)
= 7.24 cycles per iteration
und das gleiche für "sortiert"
0.25*17+1+1+ (=6.25)
(1-0.75)*(1+0.24*17+1+1)+ (=.25*7.08=1.77)
(1-0.75-0.19)*(1+1+1+1) (= 0.06*4=0.24)
= 8.26
(8,26-7,24) / 8,26 = 13,8% gegenüber ~ 9% gemessen (nahe am gemessenen!?!).
Das Offensichtliche des OP ist also nicht offensichtlich.
Bei diesen Tests sind andere Tests mit komplizierterem Code oder mehr Datenabhängigkeiten sicherlich anders. Messen Sie also Ihren Fall.
Durch Ändern der Testreihenfolge wurden die Ergebnisse geändert. Dies kann jedoch an unterschiedlichen Ausrichtungen des Schleifenstarts liegen, die idealerweise 16 Byte betragen sollten, die auf allen neueren Intel-CPUs ausgerichtet sind, in diesem Fall jedoch nicht.