加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c – 虚函数调用开销在哪里?

发布时间:2020-12-16 10:50:21 所属栏目:百科 来源:网络整理
导读:我正在尝试对函数指针调用和虚函数调用之间的差异进行基准测试.为此,我编写了两段代码,对数组进行相同的数学计算.一个变体使用指向函数的指针数组并在循环中调用它们.另一个变体使用指向基类的指针数组并调用其虚函数,该函数在派生类中重载,与第一个变体中的
我正在尝试对函数指针调用和虚函数调用之间的差异进行基准测试.为此,我编写了两段代码,对数组进行相同的数学计算.一个变体使用指向函数的指针数组并在循环中调用它们.另一个变体使用指向基类的指针数组并调用其虚函数,该函数在派生类中重载,与第一个变体中的函数完全相同.然后我打印经过的时间并使用简单的 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;
}

这是虚函数变量:

#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,&start);
for ( int i = 0; i < 100000; ++i ) {
    base[i]->calculate( &array[i] );
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&start);
cout << time_elapsed / 1000000000.0 << endl;
}

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

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

[Ignat@localhost circuit_testing]$./test2 && ./test2 
0.0153142
0.0153166

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

[Ignat@localhost circuit_testing]$./test2 && ./test2 
0.01531
0.0152476

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

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

解决方法

我认为你看到了差异,但这只是函数调用开销.在两种情况下,分支错误预测,内存访问和触发功能都是相同的.与那些相比,它只是没有那么大的交易,尽管当我尝试它时,函数指针的情况肯定会快一点.

如果这是你的大型项目的代表,这是一个很好的证明,这种微观优化有时只是海洋中的一滴,最坏的是徒劳.但是将它放在一边,为了更清晰的测试,函数应该执行一些更简单的操作,这对于每个函数都是不同的:

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

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

依此类推,类似于虚拟功能.

(每个函数应该做一些不同的事情,这样它们就不会被删除,并且所有函数都以相同的地址结束;这会使分支预测工作变得不切实际.)

通过这些更改,结果会有所不同.每种情况下最好的4次运行. (不是很科学,但是对于大量的运行,数字大致相似.)所有时间都是循环的,在我的笔记本电脑上运行.代码是用VC编译的(只改变了时序),但gcc以相同的方式实现虚函数调用,因此即使使用不同的OS / x86 CPU /编译器,相对时序也应大致相似.

函数指针:2,052,770

虚拟:3,598,039

这种差异似乎有点过分了!果然,两位代码在内存访问行为方面并不完全相同.第二个应该有一个4个A * s的表,用于填充基数,而不是为每个条目新建一个新的.然后,当获取指针跳过时,两个示例将具有类似的行为(1个高速缓存未命中/ N个条目).例如:

A *tbl[4] = { new A1,new A2,new A3,new A4 };
for ( long int i = 0; i < 100000; ++i ) {
    array[i] = ( double )( rand() / 1000 );
    base[i] = tbl[ rand() % 4 ];
}

有了这个,仍然使用简化的功能:

虚拟(如此处所示):2,487,699

所以有20%,最好的情况.足够近?

所以也许你的同事至少应该考虑这一点,但我怀疑在任何现实的程序中,呼叫开销都不足以成为值得跳过篮球的瓶颈.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读