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] 范围内的编译时大小,i0N-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 ”比较和条件跳转”指令)。函数指针数组是这里唯一好的选择。