Я решил повторить тест на своей машине, используя код Lik32. Я должен был изменить это из-за моих окон или компилятора, думая, что высокое разрешение составляет 1 мс, используя
mingw32-g ++. exe -O3 -Wall -std = c ++ 11 -fexceptions -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, первые 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, но не в этом случае.