C中的动态调度:使用函数指针是否更快?

lx0bsm1f  于 2023-05-06  发布在  其他
关注(0)|答案(1)|浏览(114)

假设我需要在两个函数中选择一个来动态调用(就像我们在C++中的vtable中所做的那样):
common.h

#include<stdlib.h>
#include<time.h>
#define TEST_N 100000000

static volatile int state = 0;
static void foo1() {
    state = 1;
} 
static void foo2() {
    state = 2;
}

test1.c

#include"common.h"
int foo_choose(){
    if (rand()%2 == 0){
        return 2;
    }
    return 1;
}

int main(){
    time_t t;
    srand(time(&t));
    for(size_t i = 0; i<TEST_N; i++){
        switch(foo_choose()){
            case 1:
                foo1();
                break;
            case 2:
                foo2();
                break;
        }
    }
    return 0;
}

test2.c

#include"common.h"
void (*foo_choose(void))(){
    if (rand()%2 == 0){
        return &foo2;
    }
    return &foo1;
}

int main(){
    time_t t;
    srand(time(&t));
    for(size_t i = 0; i<TEST_N; i++){
        (foo_choose())();
    }
    return 0;
}

我试着比较一下表演。显然,在带有-O1-O3的ARM64 apple clang上,test1明显快于test2。使用gcc12,无论我们如何设置优化级别,它们都差不多快。我已经看过生产的组件,但没有发现任何重要的东西。
有谁知道为什么我们会有性能上的差异吗?有没有什么原因可以解释为什么当代码比上面的演示复杂得多时,一般来说应该更快?
在C中执行动态分派的更好方法是test1还是test2?毕竟,test1更灵活,因为它允许处理不是函数指针的东西。
编辑:如果很难决定,请分享一些想法。如果这取决于体系结构,那么最好是同时编写适用于所有可能的体系结构的代码,那么有没有一种好的方法呢?

l3zydbqr

l3zydbqr1#

我不认为C和C在这方面有太大的区别。
动态分派速度较慢的可能性是可以理解的,因为它不能内联调用的函数,这意味着不能优化那么多,这是一个额外的间接。
选择(不同的)随机数可能不是测试的好主意。不同的数字序列可能会影响结果(我不知道兰德()函数的成本有多确定)。你可以预先计算随机数序列。
我认为,当它不是在一个紧密的循环中,并且被调用的函数不是太短时,你不应该太担心选择一个解决方案而不是另一个解决方案。如果你想提高性能,较短的函数应该是可内联的。
还有一个令人担忧的因素,在很多情况下可能更重要:分支预测
失败的预测是昂贵的。如果你以一种可预测的方式分支,例如1000次到foo 1,然后1000次到foo 2,那么预测几乎总是成功的。可能存在分支预测器可能识别的其他模式。(cppcon,Fedor Pikus:无分支编程)
这就是为什么它可能会更好,例如在C
中,如果你根据数组/向量中的多态元素的具体类型对其进行排序/分组,那么当你迭代数组并在对象上调用虚函数时,预测更有可能成功。
所以测量它,但尝试使用相同的数字为两个测试(随机或不)。

相关问题