boost::mp11::mp_with_index 的性能与 std::function 的数组相比
Performance of boost::mp11::mp_with_index compared to array of std::function
考虑以下两个片段:
// Option (1).
boost::mp11::mp_with_index<N>(i,
[&](const auto i){ function<i>(/* args... */); });
和
// Option (2).
inline static const std::array<std::function<void(/* Args... */)>, N>
functionArray{function<0>, ..., function<N-1>};
functionArray[i](/* args... */);
其中 N
是大约在 [0, 20] 范围内的编译时大小,i
是 0
和 N-1
之间的运行时索引,并且 template <size_t I> function(/* Args... */)
是一个具有已知签名的模板函数。 两个选项中哪个最快?
注意:我知道 boost::mp11::mp_with_index
基本上创建了一个 switch 语句,允许将运行时索引转换为编译时索引。这引入了一些间接性,但我希望这不会太昂贵。同样,我知道 std::function
由于类型擦除而引入了一些间接。我的问题是:两种间接类型中哪一种最有效?
与纯指针数组 std::array<void(*)(Args...), N>
. 相比,std::array<std::function<void(Args...)>, N>
可能会引入一些开销
查看在 https://godbolt.org/z/a8z9aKs7P 处生成的程序集,可以得出以下观察结果:
boost::mp11::mp_with_index
被编译为一个分支 table,它为 N 个不同的指令保存 N 个地址,这些指令在跳转到时简单地调用 function<I>
。所以,它会在分支 table 中寻找一个地址,跳转到那个地址,然后再次跳转到所需的函数。
这个分支table可以简化为只存储function<I>
的地址,只需要一次跳转。这 是 当你有一个函数指针数组时会发生什么,该数组本质上是一个分支 table.
std::function
类似,但调用 std::function
比调用常规函数指针稍微复杂一些。
请注意 clang 在 -O2
甚至 -O3
上的优化都很糟糕。 boost::mp11::mp_with_index<N>
实际上是一堆 if/else 语句,应该很容易编译,就好像它是 switch
,但 clang 无法做到这一点(并留下 N
”比较和条件跳转”指令)。函数指针数组是这里唯一好的选择。
考虑以下两个片段:
// Option (1).
boost::mp11::mp_with_index<N>(i,
[&](const auto i){ function<i>(/* args... */); });
和
// Option (2).
inline static const std::array<std::function<void(/* Args... */)>, N>
functionArray{function<0>, ..., function<N-1>};
functionArray[i](/* args... */);
其中 N
是大约在 [0, 20] 范围内的编译时大小,i
是 0
和 N-1
之间的运行时索引,并且 template <size_t I> function(/* Args... */)
是一个具有已知签名的模板函数。 两个选项中哪个最快?
注意:我知道 boost::mp11::mp_with_index
基本上创建了一个 switch 语句,允许将运行时索引转换为编译时索引。这引入了一些间接性,但我希望这不会太昂贵。同样,我知道 std::function
由于类型擦除而引入了一些间接。我的问题是:两种间接类型中哪一种最有效?
std::array<void(*)(Args...), N>
. 相比,std::array<std::function<void(Args...)>, N>
可能会引入一些开销
查看在 https://godbolt.org/z/a8z9aKs7P 处生成的程序集,可以得出以下观察结果:
boost::mp11::mp_with_index
被编译为一个分支 table,它为 N 个不同的指令保存 N 个地址,这些指令在跳转到时简单地调用 function<I>
。所以,它会在分支 table 中寻找一个地址,跳转到那个地址,然后再次跳转到所需的函数。
这个分支table可以简化为只存储function<I>
的地址,只需要一次跳转。这 是 当你有一个函数指针数组时会发生什么,该数组本质上是一个分支 table.
std::function
类似,但调用 std::function
比调用常规函数指针稍微复杂一些。
请注意 clang 在 -O2
甚至 -O3
上的优化都很糟糕。 boost::mp11::mp_with_index<N>
实际上是一堆 if/else 语句,应该很容易编译,就好像它是 switch
,但 clang 无法做到这一点(并留下 N
”比较和条件跳转”指令)。函数指针数组是这里唯一好的选择。