复合布尔值与 unlikely/likely 的性能
Performance for compound boolean vs. unlikely/likely
这样做更快吗?:
return m_bVisible && (_colorTransform.alphaTransform > 0 || _colorTransform.colorOffsetTransform.a > 0) && (!_masked || _maskVisitable);
比这样做?:
if (UNLIKELY(_colorTransform.alphaTransform == 0 && _colorTransform.colorOffsetTransform.a == 0))
{
return false;
}
if (UNLIKELY(_masked && !_maskVisitable))
{
return false;
}
return m_bVisible;
做了很多小优化,显着提高了我们游戏的帧率性能,这是我不确定的优化。我对获得 0.01% 的性能提升感到满意,因为在进行了 100 次这些优化之后,我已经能够相当显着地提高性能 (30-40%)。询问使用 UNLIKELY 优化与仅使用复合布尔值。在这个特定的表达式中短路不是很容易。
我会说第一个更快!
即使你是 "optimizing" 第二个分支预测,这基本上只是意味着 return m_bVisible;
在 ASM 代码中比你的 return false
更早,你仍然必须在您的 if 语句中最多进行四次比较。
但是,正如您所说,由于它们非常不可能,因此在实际查看 m_bVisible
之前没有必要这样做。第一个示例首先检查 m_bVisible
,这似乎并不偏向 FALSE 或 TRUE。
我的预感是,对于 m_bVisible == false
的所有情况,第一个会更快。在所有其他情况下,除了第二个示例中 JMP 的最小开销(需要您跳过 return m_bVisible;
以到达实际的 return false
)之外,不会有太大差异。
编辑
让我们更深入地了解两种变体的 x86_64 程序集 (gcc 8):
测试代码:
#include <stdio.h>
bool a=true; int b=0; int c=0; bool d=false; bool e=true;
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
int main()
{
marker1:
return a && (b > 0 || c > 0) && (!d || e);
}
int alternative()
{
if (unlikely(b == 0 && c == 0))
{
return false;
}
if (unlikely(d && !e))
{
return false;
}
return a;
}
X86_86 为两个函数组装 (GCC 8.2):
主要()
main:
push rbp
mov rbp, rsp
movzx eax, BYTE PTR a[rip]
test al, al
je .L3
mov eax, DWORD PTR b[rip]
test eax, eax
jg .L4
mov eax, DWORD PTR c[rip]
test eax, eax
jle .L3
.L4:
movzx eax, BYTE PTR d[rip]
xor eax, 1
test al, al
jne .L5
movzx eax, BYTE PTR e[rip]
test al, al
je .L3
.L5:
mov eax, 1
jmp .L6
.L3:
mov eax, 0
.L6:
movzx eax, al
pop rbp
ret
备选方案()
alternative():
push rbp
mov rbp, rsp
mov eax, DWORD PTR b[rip]
test eax, eax
sete al
movzx eax, al
test rax, rax
je .L9
mov eax, DWORD PTR c[rip]
test eax, eax
sete al
movzx eax, al
test rax, rax
je .L9
mov eax, 0
jmp .L10
.L9:
movzx eax, BYTE PTR d[rip]
movzx eax, al
test rax, rax
je .L11
movzx eax, BYTE PTR e[rip]
xor eax, 1
movzx eax, al
test rax, rax
je .L11
mov eax, 0
jmp .L10
.L11:
movzx eax, BYTE PTR a[rip]
movzx eax, al
.L10:
pop rbp
ret
在第一个变体中,在 main() 中,最快的退出是在第五行,je .L3
。在第二个变体中,最快的退出发生得晚很多......在 .L9 at je .L11
编辑2
只是为了让它完整,这里是 -O3
的汇编
main:
xor eax, eax
cmp BYTE PTR a[rip], 0
je .L1
cmp DWORD PTR b[rip], 0
jle .L10
.L4:
cmp BYTE PTR d[rip], 0
mov eax, 1
je .L1
movzx eax, BYTE PTR e[rip]
.L1:
ret
.L10:
cmp DWORD PTR c[rip], 0
jg .L4
ret
和
alternative():
mov eax, DWORD PTR b[rip]
or eax, DWORD PTR c[rip]
je .L11
cmp BYTE PTR d[rip], 0
jne .L18
.L13:
movzx eax, BYTE PTR a[rip]
ret
.L18:
cmp BYTE PTR e[rip], 0
jne .L13
xor eax, eax
.L11:
ret
即使使用内联,第一个在 O3 上似乎仍然更快!
这样做更快吗?:
return m_bVisible && (_colorTransform.alphaTransform > 0 || _colorTransform.colorOffsetTransform.a > 0) && (!_masked || _maskVisitable);
比这样做?:
if (UNLIKELY(_colorTransform.alphaTransform == 0 && _colorTransform.colorOffsetTransform.a == 0))
{
return false;
}
if (UNLIKELY(_masked && !_maskVisitable))
{
return false;
}
return m_bVisible;
做了很多小优化,显着提高了我们游戏的帧率性能,这是我不确定的优化。我对获得 0.01% 的性能提升感到满意,因为在进行了 100 次这些优化之后,我已经能够相当显着地提高性能 (30-40%)。询问使用 UNLIKELY 优化与仅使用复合布尔值。在这个特定的表达式中短路不是很容易。
我会说第一个更快!
即使你是 "optimizing" 第二个分支预测,这基本上只是意味着 return m_bVisible;
在 ASM 代码中比你的 return false
更早,你仍然必须在您的 if 语句中最多进行四次比较。
但是,正如您所说,由于它们非常不可能,因此在实际查看 m_bVisible
之前没有必要这样做。第一个示例首先检查 m_bVisible
,这似乎并不偏向 FALSE 或 TRUE。
我的预感是,对于 m_bVisible == false
的所有情况,第一个会更快。在所有其他情况下,除了第二个示例中 JMP 的最小开销(需要您跳过 return m_bVisible;
以到达实际的 return false
)之外,不会有太大差异。
编辑
让我们更深入地了解两种变体的 x86_64 程序集 (gcc 8):
测试代码:
#include <stdio.h>
bool a=true; int b=0; int c=0; bool d=false; bool e=true;
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
int main()
{
marker1:
return a && (b > 0 || c > 0) && (!d || e);
}
int alternative()
{
if (unlikely(b == 0 && c == 0))
{
return false;
}
if (unlikely(d && !e))
{
return false;
}
return a;
}
X86_86 为两个函数组装 (GCC 8.2):
主要()
main:
push rbp
mov rbp, rsp
movzx eax, BYTE PTR a[rip]
test al, al
je .L3
mov eax, DWORD PTR b[rip]
test eax, eax
jg .L4
mov eax, DWORD PTR c[rip]
test eax, eax
jle .L3
.L4:
movzx eax, BYTE PTR d[rip]
xor eax, 1
test al, al
jne .L5
movzx eax, BYTE PTR e[rip]
test al, al
je .L3
.L5:
mov eax, 1
jmp .L6
.L3:
mov eax, 0
.L6:
movzx eax, al
pop rbp
ret
备选方案()
alternative():
push rbp
mov rbp, rsp
mov eax, DWORD PTR b[rip]
test eax, eax
sete al
movzx eax, al
test rax, rax
je .L9
mov eax, DWORD PTR c[rip]
test eax, eax
sete al
movzx eax, al
test rax, rax
je .L9
mov eax, 0
jmp .L10
.L9:
movzx eax, BYTE PTR d[rip]
movzx eax, al
test rax, rax
je .L11
movzx eax, BYTE PTR e[rip]
xor eax, 1
movzx eax, al
test rax, rax
je .L11
mov eax, 0
jmp .L10
.L11:
movzx eax, BYTE PTR a[rip]
movzx eax, al
.L10:
pop rbp
ret
在第一个变体中,在 main() 中,最快的退出是在第五行,je .L3
。在第二个变体中,最快的退出发生得晚很多......在 .L9 at je .L11
编辑2
只是为了让它完整,这里是 -O3
的汇编main:
xor eax, eax
cmp BYTE PTR a[rip], 0
je .L1
cmp DWORD PTR b[rip], 0
jle .L10
.L4:
cmp BYTE PTR d[rip], 0
mov eax, 1
je .L1
movzx eax, BYTE PTR e[rip]
.L1:
ret
.L10:
cmp DWORD PTR c[rip], 0
jg .L4
ret
和
alternative():
mov eax, DWORD PTR b[rip]
or eax, DWORD PTR c[rip]
je .L11
cmp BYTE PTR d[rip], 0
jne .L18
.L13:
movzx eax, BYTE PTR a[rip]
ret
.L18:
cmp BYTE PTR e[rip], 0
jne .L13
xor eax, eax
.L11:
ret
即使使用内联,第一个在 O3 上似乎仍然更快!