C 和 C++ 之间运行时多态性的性能差异

meg*_*uli 0 c c++ benchmarking run-time-polymorphism

我知道基准测试是一个非常微妙的主题,简单的、未经深思熟虑的基准测试对于性能比较来说大多毫无意义,但我现在所拥有的实际上是一个非常小且人为的示例,我认为应该很容易解释。因此,即使这个问题看起来没有帮助,它至少会帮助我理解基准测试。

那么,我开始了。

我试图用 C 语言尝试简单的 API 设计,通过void *. 然后我将它与使用常规虚函数在 C++ 中实现的相同内容进行了比较。这是代码:

#include <cstdlib>
#include <cstdio>
#include <cstring>

int dummy_computation()
{
    return 64 / 8;
}

/* animal library, everything is prefixed with al for namespacing */
#define AL_SUCCESS 0;
#define AL_UNKNOWN_ANIMAL 1;
#define AL_IS_TYPE_OF(animal, type) \
    strcmp(((type *)animal)->animal_type, #type) == 0\

typedef struct {
    const char* animal_type;
    const char* name;
    const char* sound;
} al_dog;

inline int make_dog(al_dog** d) {
    *d = (al_dog*) malloc(sizeof(al_dog));
    (*d)->animal_type = "al_dog";
    (*d)->name = "leslie";
    (*d)->sound = "bark";
    return AL_SUCCESS;
}

inline int free_dog(al_dog* d) {
    free(d);
    return AL_SUCCESS;
}
    
typedef struct {
    const char* animal_type;
    const char* name;
    const char* sound;
} al_cat;

inline int make_cat(al_cat** c) {
    *c = (al_cat*) malloc(sizeof(al_cat));
    (*c)->animal_type = "al_cat";
    (*c)->name = "garfield";
    (*c)->sound = "meow";
    return AL_SUCCESS;
}

inline int free_cat(al_cat* c) {
    free(c);
    return AL_SUCCESS;
}

int make_sound(void* animal) {
    if(AL_IS_TYPE_OF(animal, al_cat)) {
        al_cat *c = (al_cat*) animal;
        return dummy_computation();
    } else if(AL_IS_TYPE_OF(animal, al_dog)) {
        al_dog *d = (al_dog*) animal;
        return dummy_computation();
    } else {
        printf("unknown animal\n");
        return 0;
    }
}
/* c style library finishes here */

/* cpp library with OOP */
struct animal {
    animal(const char* n, const char* s) 
    :name(n)
    ,sound(s)
    {} 
    virtual int make_sound() {
        return dummy_computation();
    }
    const char* name;
    const char* sound;
};

struct cat : animal {
    cat() 
    :animal("garfield", "meow")
    {}
};

struct dog : animal {
    dog() 
    :animal("leslie", "bark")
    {}
};
/* cpp library finishes here */ 
Run Code Online (Sandbox Code Playgroud)

我有一个叫做 的东西dummy_computation,只是为了确保我在基准测试中进行一些计算。对于这样的例子,我通常会实现不同的printf吠叫、喵叫等调用,但printf在 fast-benchmarks.com 中不容易进行基准测试。我真正想要进行基准测试的是运行时多态性实现。这就是为什么我选择创建一些小函数并在 C 和 C++ 实现中使用它作为填充物。

现在,在 fast-benchmarks.com 中,我有一个如下所示的基准:

static void c_style(benchmark::State& state) {
  // Code inside this loop is measured repeatedly
  for (auto _ : state) {
    al_dog* d = NULL;
    al_cat* c = NULL;

    make_dog(&d);
    make_cat(&c);
    
    int i1 = make_sound(d);
    benchmark::DoNotOptimize(i1);
    int i2 = make_sound(c);
    benchmark::DoNotOptimize(i2);

    free_dog(d);
    free_cat(c);
  }
}
// Register the function as a benchmark
BENCHMARK(c_style);

static void cpp_style(benchmark::State& state) {
  for (auto _ : state) {
    animal* a1 = new dog();
    animal* a2 = new cat();
    int i1 = a1->make_sound();
    benchmark::DoNotOptimize(i1);
    int i2 = a2->make_sound();
    benchmark::DoNotOptimize(i2);
    delete a1;
    delete a2;
  }
}
BENCHMARK(cpp_style); 
Run Code Online (Sandbox Code Playgroud)

我添加了DoNotOptimize调用,以便虚拟调用最终不会被优化。

如果重新创建它看起来很痛苦,可以在这里找到整个基准测试。

https://quick-bench.com/q/ezul9hDXTjfSWijCfd2LMUUEH1I

现在,令我惊讶的是,C 版本的结果速度快了 27 倍。我预计 C++ 版本的性能可能会受到一些影响,因为它是一个更精致的解决方案,但绝对不是 27 倍。

有人可以解释这些结果吗?与 C 相比,虚函数调用真的会产生这么多开销吗?或者说我设置这个基准测试的方式完全没有意义?如果是这样,如何更正确地对此类问题进行基准测试?

lor*_*rro 5

这是因为你没有实现同样的事情。如果您在 C 中执行链if的链switch,那么您(在数学上)就拥有了 C++ 中的可区分联合std::variant

如果您希望将 C++ 版本移植到 C,那么您需要函数指针。它很可能同样慢。背后的原因virtual意味着向前兼容:任何代码(包括稍后加载的库)都可以从您的基础派生并实现这些virtual方法。这意味着,有时您甚至不知道在基本模块的编译时它可能需要处理哪些(后代)类(类型系统是开放的)。不提供这种向前兼容性std::variant,它是封闭的(仅限于固定的类型列表)。

  • @meguli&gt; 当您有一组封闭的类型并且您不关心使其对添加保持开放时,继承是完成这项工作的错误工具,因此您质疑习惯是正确的。我希望更多的人这样做,我仍然经常看到在新代码中毫无理由地创建继承层次结构。 (2认同)