使用static_cast可以避免vtable开销吗?

lin*_*ver 5 c++ overhead vtable static-cast

这是我的问题.我有一个基类和一个派生类,它覆盖了基类中的一些方法.为简单起见,请考虑以下示例:

struct base
{
  virtual void fn()
  {/*base definition here*/}
};

struct derived : base 
{
  void fn()
  {/*derived definition here*/}
};
Run Code Online (Sandbox Code Playgroud)

在我的实际程序中,这些类作为参数传递给其他类,并在其他方法中调用,但为了简单起见,我们创建一个简单的函数,它将基类或派生类作为参数.我可以简单地写

void call_fn(base& obj)
{obj.fn();}
Run Code Online (Sandbox Code Playgroud)

并且由于虚函数,将在运行时解析对相应函数的调用.

然而,我担心,如果call_fn要被称为百万次(在我的情况下,它将作为我的实际应用程序是模拟实验),我将得到一个我想避免的重大开销.

所以,我想知道使用static_cast是否可以解决问题.也许是这样的:

template <typename T>
void call_fn(base& obj)
{(static_cast<T*>(&obj))->fn();}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,函数调用将完成call_fn<base>(obj)调用基本方法或call_fn<derived>(obj)调用派生方法.

这个解决方案会避免vtable开销还是会受到影响?在此先感谢您的回复!

顺便说一句,我知道CRTP但不太熟悉它.这就是为什么我想首先知道这个简单问题的答案:)

Dav*_*eas 6

这个解决方案会避免vtable开销还是会受到影响?

它仍然会使用动态调度(这是否会导致任何明显的开销是一个完全不同的问题).您可以通过限定函数调用来禁用动态调度,如下所示:

static_cast<T&>(obj).T::fn();
Run Code Online (Sandbox Code Playgroud)

虽然我甚至不想这样做.保留动态调度,然后测试应用程序的性能,进行一些分析,进行进一步的分析.再次描述以确保您了解分析器告诉您的内容.只有这样,才能考虑再次进行一次更改和配置文件,以验证您的假设是否正确.


Mat*_*son 5

这不是你实际问题的真正答案,但我很好奇"调用虚函数与​​调用常规类函数的真正原因是什么".为了使它"公平",我创建了一个实现非常简单的函数的classes.cpp,但它是在"main"之外编译的单独文件中.

classes.h:

#ifndef CLASSES_H
#define CLASSES_H

class base
{
    virtual int vfunc(int x) = 0;
};

class vclass : public base
{
public:
    int vfunc(int x);
};


class nvclass
{
public:
    int nvfunc(int x);
};


nvclass *nvfactory();
vclass* vfactory();


#endif
Run Code Online (Sandbox Code Playgroud)

classes.cpp:

#include "classes.h"

int vclass:: vfunc(int x)
{
    return x+1;
}


int nvclass::nvfunc(int x)
{
    return x+1;
}

nvclass *nvfactory()
{
    return new nvclass;
}

vclass* vfactory()
{
    return new vclass;
}
Run Code Online (Sandbox Code Playgroud)

这是从:

#include <cstdio>
#include <cstdlib>
#include "classes.h"

#if 0
#define ASSERT(x) do { if(!(x)) { assert_fail( __FILE__, __LINE__, #x); } } while(0)
static void assert_fail(const char* file, int line, const char *cond)
{
    fprintf(stderr, "ASSERT failed at %s:%d condition: %s \n",  file, line, cond); 
    exit(1);
}
#else
#define ASSERT(x) (void)(x)
#endif

#define SIZE 10000000

static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}


void print_avg(const char *str, const int *diff, int size)
{
    int i;
    long sum = 0;
    for(i = 0; i < size; i++)
    {
    int t = diff[i];
    sum += t;
    }

    printf("%s average =%f clocks\n", str, (double)sum / size);
}


int diff[SIZE]; 

int main()
{
    unsigned long long a, b;
    int i;
    int sum = 0;
    int x;

    vclass *v = vfactory();
    nvclass *nv = nvfactory();


    for(i = 0; i < SIZE; i++)
    {
    a = rdtsc();

    x = 16;
    sum+=x;
    b = rdtsc();

    diff[i] = (int)(b - a);
    }

    print_avg("Emtpy", diff, SIZE);


    for(i = 0; i < SIZE; i++)
    {
    a = rdtsc();

    x = 0;
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    x = v->vfunc(x);
    ASSERT(x == 4); 
    sum+=x;
    b = rdtsc();

    diff[i] = (int)(b - a);
    }

    print_avg("Virtual", diff, SIZE);

    for(i = 0; i < SIZE; i++)
    {
    a = rdtsc();
    x = 0;
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    x = nv->nvfunc(x);
    ASSERT(x == 4);     
    sum+=x;
    b = rdtsc();
    diff[i] = (int)(b - a);
    }
    print_avg("no virtual", diff, SIZE);

    printf("sum=%d\n", sum);

    delete v;
    delete nv;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

代码的真正区别在于:虚拟调用:

40066b: ff 10                   callq  *(%rax)
Run Code Online (Sandbox Code Playgroud)

非虚拟电话:

4006d3: e8 78 01 00 00          callq  400850 <_ZN7nvclass6nvfuncEi>
Run Code Online (Sandbox Code Playgroud)

结果如下:

Emtpy average =78.686081 clocks
Virtual average =144.732567 clocks
no virtual average =122.781466 clocks
sum=480000000
Run Code Online (Sandbox Code Playgroud)

请记住,这是每个循环16个调用的开销,因此调用函数和不调用函数之间的差异是每次迭代大约5个时钟周期[包括累加结果和其他所需的处理],并且虚拟调用每个增加22个时钟迭代,每次调用约1.5个时钟.

我怀疑你会注意到,假设你做的事情比你在函数中返回x + 1更有意义.