可以使用函数指针数组来删除分支吗
Could arrays of function pointers be used to remove branches
函数指针数组是否可以通过删除分支来提高性能?
是
(function[number])();
比
快
if(number == 0)
function1();
else if (number == 1)
function2();
为了表现? (假设这是对性能极其关键的代码)
编辑:数字是 0 或 1(因为它应该只是 0 或 1 而被使用)
假设您的 number
是 unsigned int
类型,您的数组必须包含 UINT_MAX
个条目,除了 function[0]
之外所有条目都相同。如果不是这种情况,您的第一个代码 (function[number])();
将省略一个不是未定义行为的条件:(function[number != 0])();
总结一下:#1 是(正确书写的)一个条件和两个间接寻址。巨型数组的另一个选项将破坏缓存局部性。
#2 是一个条件和一个直接函数调用。
不太可能提供帮助。函数指针会导致与条件相同的停顿*。如今,两者都有预测器,但条件预测器的成功率更高。不足为奇,因为他们只需要预测一位。
* 停滞:指令处理中的延迟,由现代 CPU 中指令解码器的流水线特性引起。指令解码在实际执行之前开始几个周期,但在本质上有点推测未来将执行哪条指令。当这个推测错误时,必须丢弃一些解码指令,并且指令执行被延迟直到解码器赶上。
正如 MSalters 已经非常彻底地指出的那样,它不太可能提供帮助。此外,几乎无法预测它是否有帮助。我已经为您创建了两个示例,您可以在其中看到这一点——一个使用指针数组,另一个使用 if-else 子句。
#include <array>
#include <functional>
int test1(int* i)
{
return *i * *i * *i;
}
int test2(int* i)
{
return *i * *i* *i* *i* *i* *i;
}
std::array<int (*)(int*), 2> funcs{test1, test2};
int testing(int v)
{
int testme = v + 4 ;
return funcs[v](&testme);
}
及其组件
test1(int*): # @test1(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
retq
test2(int*): # @test2(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
imull %eax, %eax
retq
testing(int): # @testing(int)
pushq %rax
leal 4(%rdi), %eax
movl %eax, 4(%rsp)
movslq %edi, %rax
leaq 4(%rsp), %rdi
callq *funcs(,%rax,8)
popq %rcx
retq
funcs:
.quad test1(int*)
.quad test2(int*)
这里是Example2
int test1(int* i)
{
return *i * *i * *i;
}
int test2(int* i)
{
return *i * *i* *i* *i* *i* *i;
}
int testing(int v)
{
int testme = v + 4 ;
return (v == 0) ? test1(&testme) : test2(&testme);
}
与其对应的程序集
test1(int*): # @test1(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
retq
test2(int*): # @test2(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
imull %eax, %eax
retq
testing(int): # @testing(int)
leal 4(%rdi), %eax
movl %eax, %ecx
imull %eax, %ecx
imull %eax, %ecx
testl %edi, %edi
movl , %eax
cmovnel %ecx, %eax
imull %ecx, %eax
retq
在指令计数方面它们非常相似(也许专家可以在这里帮助我们并进一步解释)。 if-else-statement 中断了这条指令 cmovnel
。我怀疑这会产生很大的不同。
我认为您总是需要在您的确切设置中进行测量。在示例情况下,编译器优化总是难以预测。
这里不会有正确或错误的答案。
函数指针数组是否可以通过删除分支来提高性能?
是
(function[number])();
比
快if(number == 0)
function1();
else if (number == 1)
function2();
为了表现? (假设这是对性能极其关键的代码)
编辑:数字是 0 或 1(因为它应该只是 0 或 1 而被使用)
假设您的 number
是 unsigned int
类型,您的数组必须包含 UINT_MAX
个条目,除了 function[0]
之外所有条目都相同。如果不是这种情况,您的第一个代码 (function[number])();
将省略一个不是未定义行为的条件:(function[number != 0])();
总结一下:#1 是(正确书写的)一个条件和两个间接寻址。巨型数组的另一个选项将破坏缓存局部性。 #2 是一个条件和一个直接函数调用。
不太可能提供帮助。函数指针会导致与条件相同的停顿*。如今,两者都有预测器,但条件预测器的成功率更高。不足为奇,因为他们只需要预测一位。
* 停滞:指令处理中的延迟,由现代 CPU 中指令解码器的流水线特性引起。指令解码在实际执行之前开始几个周期,但在本质上有点推测未来将执行哪条指令。当这个推测错误时,必须丢弃一些解码指令,并且指令执行被延迟直到解码器赶上。
正如 MSalters 已经非常彻底地指出的那样,它不太可能提供帮助。此外,几乎无法预测它是否有帮助。我已经为您创建了两个示例,您可以在其中看到这一点——一个使用指针数组,另一个使用 if-else 子句。
#include <array>
#include <functional>
int test1(int* i)
{
return *i * *i * *i;
}
int test2(int* i)
{
return *i * *i* *i* *i* *i* *i;
}
std::array<int (*)(int*), 2> funcs{test1, test2};
int testing(int v)
{
int testme = v + 4 ;
return funcs[v](&testme);
}
及其组件
test1(int*): # @test1(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
retq
test2(int*): # @test2(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
imull %eax, %eax
retq
testing(int): # @testing(int)
pushq %rax
leal 4(%rdi), %eax
movl %eax, 4(%rsp)
movslq %edi, %rax
leaq 4(%rsp), %rdi
callq *funcs(,%rax,8)
popq %rcx
retq
funcs:
.quad test1(int*)
.quad test2(int*)
这里是Example2
int test1(int* i)
{
return *i * *i * *i;
}
int test2(int* i)
{
return *i * *i* *i* *i* *i* *i;
}
int testing(int v)
{
int testme = v + 4 ;
return (v == 0) ? test1(&testme) : test2(&testme);
}
与其对应的程序集
test1(int*): # @test1(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
retq
test2(int*): # @test2(int*)
movl (%rdi), %ecx
movl %ecx, %eax
imull %ecx, %eax
imull %ecx, %eax
imull %eax, %eax
retq
testing(int): # @testing(int)
leal 4(%rdi), %eax
movl %eax, %ecx
imull %eax, %ecx
imull %eax, %ecx
testl %edi, %edi
movl , %eax
cmovnel %ecx, %eax
imull %ecx, %eax
retq
在指令计数方面它们非常相似(也许专家可以在这里帮助我们并进一步解释)。 if-else-statement 中断了这条指令 cmovnel
。我怀疑这会产生很大的不同。
我认为您总是需要在您的确切设置中进行测量。在示例情况下,编译器优化总是难以预测。
这里不会有正确或错误的答案。