C++ 优化调用函数指针数组

C++ 优化调用函数指针数组,c++,c,optimization,jit,C++,C,Optimization,Jit,我有以下循环调用数组中的所有函数指针: for(auto f : program) { f(); } 我想优化这个。到目前为止,我尝试了两种方法: 尾部递归 JITting线程代码 以下是完整的测试代码: 我的机器(iMac Pro 8-Core)上的计时结果如下: 当然,所有函数都必须修改,以便于尾部递归,但这没关系。在代码清洁度方面,不太令人愉快的是将所有内容放在一个函数中,并使用类似于computed goto的东西(实际上,我也尝试过,computed goto只比我机器上的尾部

我有以下循环调用数组中的所有函数指针:

for(auto f : program) {
   f();
}
我想优化这个。到目前为止,我尝试了两种方法:

  • 尾部递归
  • JITting线程代码
  • 以下是完整的测试代码:

    我的机器(iMac Pro 8-Core)上的计时结果如下:

    当然,所有函数都必须修改,以便于尾部递归,但这没关系。在代码清洁度方面,不太令人愉快的是将所有内容放在一个函数中,并使用类似于computed goto的东西(实际上,我也尝试过,computed goto只比我机器上的尾部递归稍微快一点)

    我能在没有JITting的情况下比尾部递归做得更好吗?(在iOS上,不允许JITting)


    请注意,函数不能重新排序。

    是。事实上,我们可以在不抖动的情况下击败线程代码

    测试代码由100个可能的功能组成。我编写了一个小程序,为一个100x100函数数组生成代码,该数组调用这100个函数对。优化器将原始的100条线内联到这些线对中。我们现在有:

    naive: 0.534162
    tail recursion: 0.269307
    JIT threaded: 0.124608
    pairs: 0.085922
    
    通过分析函数调用的公共序列,而不是生成所有可能的对,这种技术可以推广到实际情况


    这可以与尾部递归相结合,以实现更快的调度。

    这令人印象深刻,即使最终毫无用处。同样使用此页面中的计时,我得到:
    naive:1.040995尾部递归:0.698340尾部递归:0.130868 JIT线程:0.870852
    。第一个尾部递归是你的,比JIT快(你也测量JIT本身吗?)。第二个尾部递归只是按顺序调用函数。它的速度要快得多,比pair方法快2-3倍,这对我来说,比在内存中排序函数的代码可能是最好的主意。所以,让函数可重新定位,通过调用order对它们进行排序,然后就可以了。@RadosławCybulski计时来自我的机器。正如测试代码所暗示的,函数调用顺序在编译时是未知的,这意味着它们不能在内存中排序。此外,函数可能被多次调用,因此不存在这样的排序顺序。当然,您可以对代码进行排序。如果函数代码是可重定位的,那么在创建跳转调用时,您可以按照相同的原则在内存中移动它(基本上不是编写跳转调用,而是编写整个函数体)。您甚至可以多次写入函数体(复制它们),因为它们必须非常短(否则它们的运行时间将超过任何调用时间惩罚)。@RadosławCybulski当然,好的,更复杂的抖动。在这一点上,最好使用LLVM。这里的要点是在不需要JIT的情况下实现更快的调度。我将在问题中更清楚地说明这一点。@RadosławCybulski在复制时如何确定函数的结尾?结语之后可能会有数据,对吧?你保证所有测试都使用相同的内存访问模式吗?我想象所有的缓存获取和分页都发生在对这些函数的第一次调用上,这将极大地影响结果。如果你最后测试naive,你的计时结果会有很大变化吗?或者可能从计时结果中排除第一次执行过程(因为第一次执行过程将包括分页未命中和CPU缓存未命中。)@Wyck很好。在我的机器上,我使用XTest,它运行每个测试10次。第一次运行稍微慢一点,但并不特别重要。我怀疑这是因为每个测试都在进行100m函数调用。
    naive: 0.534162
    tail recursion: 0.269307
    JIT threaded: 0.124608
    pairs: 0.085922