虚函数调用开销在哪里?

iks*_*nov 3 c++ optimization virtual

我正在尝试对函数指针调用和虚函数调用之间的差异进行基准测试.为此,我编写了两段代码,对数组进行相同的数学计算.一个变体使用指向函数的指针数组并在循环中调用它们.另一个变体使用指向基类的指针数组并调用其虚函数,该函数在派生类中重载,与第一个变体中的函数完全相同.然后我打印经过的时间并使用简单的shell脚本多次运行基准测试并计算平均运行时间.

这是代码:

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cmath>

using namespace std;

long long timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
    ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}

void function_not( double *d ) {
*d = sin(*d);
}

void function_and( double *d ) {
*d = cos(*d);
}

void function_or( double *d ) {
*d = tan(*d);
}

void function_xor( double *d ) {
*d = sqrt(*d);
}

void ( * const function_table[4] )( double* ) = { &function_not, &function_and, &function_or, &function_xor };

int main(void)
{
srand(time(0));
void ( * index_array[100000] )( double * );
double array[100000];
for ( long int i = 0; i < 100000; ++i ) {
    index_array[i] = function_table[ rand() % 4 ];
    array[i] = ( double )( rand() / 1000 );
}

struct timespec start, end;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);
for ( long int i = 0; i < 100000; ++i ) {
    index_array[i]( &array[i] );
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);

unsigned long long time_elapsed = timespecDiff(&end, &start);
cout << time_elapsed / 1000000000.0 << endl;
}
Run Code Online (Sandbox Code Playgroud)

这是虚函数变量:

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cmath>

using namespace std;

long long timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
    ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}

class A {
public:
    virtual void calculate( double *i ) = 0;
};

class A1 : public A {
public:
    void calculate( double *i ) {
    *i = sin(*i);
    }
};

class A2 : public A {
public:
    void calculate( double *i ) {
        *i = cos(*i);
    }
};

class A3 : public A {
public:
    void calculate( double *i ) {
        *i = tan(*i);
    }
};

class A4 : public A {
public:
    void calculate( double *i ) {
        *i = sqrt(*i);
    }
};

int main(void)
{
srand(time(0));
A *base[100000];
double array[100000];
for ( long int i = 0; i < 100000; ++i ) {
    array[i] = ( double )( rand() / 1000 );
    switch ( rand() % 4 ) {
    case 0:
    base[i] = new A1();
    break;
    case 1:
    base[i] = new A2();
    break;
    case 2:
    base[i] = new A3();
    break;
    case 3:
    base[i] = new A4();
    break;
    }
}

struct timespec start, end;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);
for ( int i = 0; i < 100000; ++i ) {
    base[i]->calculate( &array[i] );
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);

unsigned long long time_elapsed = timespecDiff(&end, &start);
cout << time_elapsed / 1000000000.0 << endl;
}
Run Code Online (Sandbox Code Playgroud)

我的系统是LInux,Fedora 13,gcc 4.4.2.代码用g ++ -O3编译.第一个是test1,第二个是test2.

现在我在控制台中看到了这个:

[Ignat@localhost circuit_testing]$ ./test2 && ./test2 
0.0153142
0.0153166
Run Code Online (Sandbox Code Playgroud)

好吧,或多或少,我想.然后,这个:

[Ignat@localhost circuit_testing]$ ./test2 && ./test2 
0.01531
0.0152476
Run Code Online (Sandbox Code Playgroud)

应该可见的25%在哪里?第一个可执行文件如何比第二个可执行文件慢?

我问这个是因为我正在做一个项目,它涉及调用像这样的行中的许多小函数来计算数组的值,而我继承的代码执行非常复杂的操作以避免虚函数调用开销.现在这个着名的呼叫在哪里开销?

小智 8

在这两种情况下,您都是间接调用函数.在一种情况下通过你的函数指针表,在另一种情况下通过编译器的函数指针数组(vtable).毫不奇怪,两个类似的操作可以为您提供类似的计时结果.

  • @Semen Semenych:但是一旦vtable间接由CPU缓存,额外的间接层变得非常便宜.性能方面,避免vtable间接的最佳理由不是"它很慢",而是"它阻止了一些优化".它并不慢,它只是阻止代码快速. (2认同)