Я вирішив повторно випробувати тест на власній машині за допомогою коду Lik32. Мені довелося змінити це через те, що моє вікно або компілятор думає, що висока роздільна здатність становить 1 мс, використовуючи
mingw32-g ++. exe -O3 -Wall -std = c ++ 11 -прийняття -g
vector<int> rand_vec(10000000);
GCC здійснив однакову трансформацію в обох оригінальних кодах.
Зауважте, що тільки дві перші умови перевіряються, оскільки третя завжди повинна бути справжньою, GCC тут є своєрідним Шерлоком.
Зворотний
.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
Отже, це нам не дуже говорить, за винятком того, що останній випадок не потребує передбачення гілки.
Тепер я спробував усі 6 комбінацій if’s, верхні 2 - це оригінальний зворотний і відсортований. високий> = 95, низький - 20, середній - 20-94, з 10000000 ітерацій кожен.
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.
То чому порядок високий, низький, середній тоді швидший (незначно)
Тому що найнепередбачуваніше останнє і тому ніколи не проходить через галузевий предиктор.
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.
Так гілки будуть прогнозовані, взяті, взяті та залишені
6% + (0,94 *) 20% неправильних прогнозів.
"Відсортовано"
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
Гілки будуть прогнозовані з не взяті, не взяті і Шерлок.
25% + (0,75 *) 24% неправильних прогнозів
Даючи різницю 18-23% (виміряна різниця ~ 9%), але нам потрібно обчислити цикли замість неправильного прогнозування%.
Припустимо, що 17 циклів неправильно прогнозують штраф на моєму процесорі Nehalem, і що кожна перевірка потребує 1 циклу для видачі (4-5 інструкцій), і цикл також займає один цикл. Залежності даних - це лічильники та змінні циклу, але коли помилки непередбачувані, вони не повинні впливати на терміни.
Отже, для "зворотного" ми отримуємо терміни (це має бути формула, яка використовується в архітектурі комп'ютерів: кількісний підхід 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
те ж саме для "відсортованих"
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% проти ~ 9% виміряно (близько до вимірюваного!?!).
Тож очевидність ОП не очевидна.
З цими тестами інші тести зі складнішими кодовими або більшими залежностями даних, безумовно, будуть різними, тому вимірюйте ваш випадок.
Зміна порядку тесту змінила результати, але це могло бути пов'язано з різними вирівнюваннями циклу запуску, який в ідеалі повинен бути 16 байтів, вирівняних на всіх нових процесорах Intel, але це не в цьому випадку.