c – 虚函数调用开销在哪里?
我正在尝试对函数指针调用和虚函数调用之间的差异进行基准测试.为此,我编写了两段代码,对数组进行相同的数学计算.一个变体使用指向函数的指针数组并在循环中调用它们.另一个变体使用指向基类的指针数组并调用其虚函数,该函数在派生类中重载,与第一个变体中的函数完全相同.然后我打印经过的时间并使用简单的
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%,最好的情况.足够近? 所以也许你的同事至少应该考虑这一点,但我怀疑在任何现实的程序中,呼叫开销都不足以成为值得跳过篮球的瓶颈. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |