У меня есть функция Compare()
, которая выглядит так:
inline bool Compare(bool greater, int p1, int p2) {
if (greater) return p1>=p2;
else return p1<=p2;
}
Я решил оптимизировать, чтобы избежать ветвления:
inline bool Compare2(bool greater, int p1, int p2) {
bool ret[2] = {p1<=p2,p1>=p2};
return ret[greater];
}
Затем я проверил это:
bool x = true;
int M = 100000;
int N = 100;
bool a[N];
int b[N];
int c[N];
for (int i=0;i<N; ++i) {
a[i] = rand()%2;
b[i] = rand()%128;
c[i] = rand()%128;
}
// Timed the below loop with both Compare() and Compare2()
for (int j=0; j<M; ++j) {
for (int i=0; i<N; ++i) {
x ^= Compare(a[i],b[i],c[i]);
}
}
Результаты:
Compare(): 3.14ns avg
Compare2(): 1.61ns avg
Я бы сказал, дело закрыто, избегайте ветвления FTW. Но для полноты я заменил
a[i] = rand()%2;
с:
a[i] = true;
и получил точно такое же измерение ~ 3,14 нс. Предположительно, тогда ветвление не происходит, и компилятор фактически переписывает Compare()
, чтобы избежать оператора if
. Но тогда почему Compare2()
быстрее?
К сожалению, я неграмотен в ассемблерном коде, иначе я бы сам попытался ответить на этот вопрос.
EDIT: Ниже приведена сборка:
_Z7Comparebii:
.LFB4:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
movl %edi, %eax
movl %esi, -8(%rbp)
movl %edx, -12(%rbp)
movb %al, -4(%rbp)
cmpb $0, -4(%rbp)
je .L2
movl -8(%rbp), %eax
cmpl -12(%rbp), %eax
setge %al
jmp .L3
.L2:
movl -8(%rbp), %eax
cmpl -12(%rbp), %eax
setle %al
.L3:
leave
ret
.cfi_endproc
.LFE4:
.size _Z7Comparebii, .-_Z7Comparebii
.section .text._Z8Compare2bii,"axG",@progbits,_Z8Compare2bii,comdat
.weak _Z8Compare2bii
.type _Z8Compare2bii, @function
_Z8Compare2bii:
.LFB5:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
movl %edi, %eax
movl %esi, -24(%rbp)
movl %edx, -28(%rbp)
movb %al, -20(%rbp)
movw $0, -16(%rbp)
movl -24(%rbp), %eax
cmpl -28(%rbp), %eax
setle %al
movb %al, -16(%rbp)
movl -24(%rbp), %eax
cmpl -28(%rbp), %eax
setge %al
movb %al, -15(%rbp)
movzbl -20(%rbp), %eax
cltq
movzbl -16(%rbp,%rax), %eax
leave
ret
.cfi_endproc
.LFE5:
.size _Z8Compare2bii, .-_Z8Compare2bii
.text
Теперь фактический код, выполняющий тест, может использовать встроенные версии двух вышеупомянутых функций, поэтому есть вероятность, что это может быть неправильный код для анализа. При этом я вижу команду jmp
в Compare()
, поэтому я думаю, что это означает, что она разветвляется. Если это так, я думаю, возникает вопрос: почему предсказатель ветвления не улучшает производительность Compare()
, когда я меняю a[i]
с rand()%2
на true
(или false
в этом отношении)?
EDIT2: я заменил "предсказание ветвления" на "ветвление", чтобы сделать мой пост более осмысленным.
optimize to avoid branch prediction
Разве это не оксюморон? - person Lightness Races in Orbit   schedule 02.04.2013rand()
в этом случае? Просто быстрая мысль. Также вы должны действительно сравнить сборку. Даже если вы неграмотны в ассемблере, вы все равно можете показать разницу. - person Zeta   schedule 02.04.2013Compare()
быстрее, но в вашем вопросе все наоборот. - person Mysticial   schedule 02.04.2013-O3
? - person Oliver Charlesworth   schedule 02.04.2013setge
иsetle
? Они обеспечивают логический результат сравнения без каких-либо ветвлений. Обе версии используют эти инструкции. Не отвечает на ваш вопрос, но я подумал, что вам будет интересно. - person Mark Ransom   schedule 02.04.2013(rand()/256)%2
вместоrand()%2
? Младший значащий бит rand() может быть хорошо предсказуем и так же хорош, какtrue
илиfalse
для предсказателя ветвления. - person Evgeny Kluev   schedule 02.04.2013(rand()%256)<128
вместоrand()%2
, тот же результат. - person dshin   schedule 02.04.2013Compare2
быстрее. На самом деле единственная разница в том, чтоCompare
использует инструкциюcmov
, аCompare2
использует в два раза больше обращений к памяти. Я ожидаю, чтоCompare2
будет в два раза медленнее, чемCompare
... - person Evgeny Kluev   schedule 03.04.2013Compare()
происходит ветвь - по крайней мере, когда она не встроена (и я не понимаю, почему встраивание может помочь, если компилятор не может вывести значение большего). g++ 4.5.3 под cygwin создает для меня ожидаемый код без сюрпризов (слишком много движений происходит в данной сборке для меня) - person Voo   schedule 03.04.2013