我正在研究围绕制作内联函数的速度优势的研究。我没有这本书,但是我正在阅读的一篇文章暗示了进行函数调用需要相当大的开销;当可执行文件的大小可以忽略不计,或者可以保留时,为了速度,应该为内联声明一个函数。
我已经编写了以下代码来测试这个理论,从我所知道的,将内联函数声明为没有速度效益。这两个函数在我的计算机上调用4294967295次时,在196秒内执行。
我的问题是,您对为何会发生这种情况有何想法?是现代编译器优化吗?是否缺乏功能中发生的大量计算?
对此事的任何见解将不胜感激。在此先感谢朋友们。
#include < iostream >
#include < time.h >
// RESEARCH Jared Thomson 2010
////////////////////////////////////////////////////////////////////////////////
// Two functions that preform an identacle arbitrary floating point calculation
// one function is inline, the other is not.
double test(double a, double b, double c);
double inlineTest(double a, double b, double c);
double test(double a, double b, double c){
a = (3.1415 / 1.2345) / 4 + 5;
b = 9.999 / a + (a * a);
c = a *=b;
return c;
}
inline
double inlineTest(double a, double b, double c){
a = (3.1415 / 1.2345) / 4 + 5;
b = 9.999 / a + (a * a);
c = a *=b;
return c;
}
// ENTRY POINT Jared Thomson 2010
////////////////////////////////////////////////////////////////////////////////
int main(){
const unsigned int maxUINT = -1;
clock_t start = clock();
//============================ NON-INLINE TEST ===============================//
for(unsigned int i = 0; i < maxUINT; ++i)
test(1.1,2.2,3.3);
clock_t end = clock();
std::cout << maxUINT << " calls to non inline function took "
<< (end - start)/CLOCKS_PER_SEC << " seconds.\n";
start = clock();
//============================ INLINE TEST ===================================//
for(unsigned int i = 0; i < maxUINT; ++i)
test(1.1,2.2,3.3);
end = clock();
std::cout << maxUINT << " calls to inline function took "
<< (end - start)/CLOCKS_PER_SEC << " seconds.\n";
getchar(); // Wait for input.
return 0;
} // Main.
装配输出
答案 0 :(得分:15)
inline
关键字基本没用。这只是一个建议。编译器可以自由地忽略它并拒绝内联这样的函数,并且它也可以自由地内联一个没有inline
关键字声明的函数。
如果您真的对测试函数调用开销感兴趣,则应检查生成的程序集以确保该函数确实(或没有)内联。我对VC ++并不熟悉,但它可能有一个特定于编译器的强制或禁止内联函数的方法(但标准的C ++ inline
关键字不是它)。
因此,我认为您调查的更大背景的答案是:不要担心明确的内联。现代编译器知道何时内联,何时不知道,并且通常会比非常有经验的程序员做出更好的决策。这就是inline
关键字经常被完全忽略的原因。您不应该担心显式强制或禁止内联函数,除非您有非常特殊的需要(通过分析程序的执行情况并发现可以通过强制编译器对某些内联的内联来解决瓶颈问题)理由没有完成)。
回复:集会:
; 30 : const unsigned int maxUINT = -1;
; 31 : clock_t start = clock();
mov esi, DWORD PTR __imp__clock
push edi
call esi
mov edi, eax
; 32 :
; 33 : //============================ NON-INLINE TEST ===============================//
; 34 : for(unsigned int i = 0; i < maxUINT; ++i)
; 35 : blank(1.1,2.2,3.3);
; 36 :
; 37 : clock_t end = clock();
call esi
这个程序集是:
请注意缺少的内容:多次调用您的函数
编译器注意到你没有对函数的结果做任何事情,并且该函数没有副作用,因此它根本不被称为。
你可以通过关闭优化(在调试模式下)编译来调用函数。
答案 1 :(得分:1)
这两个功能都可以内联。非内联函数的定义与使用点在同一个编译单元中,因此即使没有您要求,编译器也有权内联它。
发布会,我们可以为您确认。
编辑:用于禁止内联的MSVC编译器编译指示是:</ p>
#pragma auto_inline(off)
void myFunction() {
// ...
}
#pragma auto_inline(on)
答案 2 :(得分:1)
可能会发生两件事:
编译器可能同时内联两个函数或两个函数。检查编译器文档以了解如何控制它。
你的函数可能很复杂,以至于执行函数调用的开销不足以在测试中产生很大的不同。
内联对于非常小的功能非常有用,但它并不总是 。代码膨胀可以阻止CPU缓存代码。
一般内联getter / setter函数和其他一个衬里。然后在性能调优期间,如果您认为自己会得到提升,可以尝试内联函数。
答案 3 :(得分:1)
您发布的代码包含几个奇怪的内容。
1)测试函数的数学和输出完全独立于函数参数。如果编译器足够聪明,可以检测到这些函数总是返回相同的值,那么这可能会激励他们完全内联或不完全优化它们。
2)您的主要功能是为内联和非内联测试调用test
。如果这是您运行的实际代码,那么在您看到相同结果的原因中,这将扮演相当大的角色。
正如其他人所建议的那样,你最好检查编译器生成的实际汇编代码,以确定你实际上正在测试你想要的东西。
答案 4 :(得分:0)
//============================ INLINE TEST ===================================//
for(unsigned int i = 0; i < maxUINT; ++i)
test(1.1,2.2,3.3);
是
//============================ INLINE TEST ===================================//
for(unsigned int i = 0; i < maxUINT; ++i)
inlineTest(1.1,2.2,3.3);
但是,如果这只是一个拼写错误,建议您查看反汇编程序或反射器,看看代码是实际内联还是堆栈。
答案 5 :(得分:0)
如果每个循环的测试花费196秒,那么你一定不能进行优化;优化关闭后,通常编译器不会内联任何。
然而,通过 on ,编译器可以自由地注意到您的测试函数可以在编译时完全评估,并将其压缩为“return [constant]” - 此时,它可能决定内联两个函数,因为它们是如此微不足道,然后注意到循环是没有意义的,因为函数值没有使用,并且也压扁了它!这基本上就是我在尝试时得到的。
无论哪种方式,你都没有测试你认为你测试过的东西。
与吹出1级指令缓存的开销相比,函数调用开销不再像过去那样,这就是积极内联对你的影响。您可以轻松地在线查找gcc -Os
选项(针对 size 进行优化)的报告,对于大型项目而言,这是-O2
的更好默认选择,其主要原因是{ {1}}更积极地推广。我希望它与MSVC大致相同。
答案 6 :(得分:0)
我知道保证函数内联的唯一方法是#define
例如:
#define RADTODEG(x) ((x) * 57.29578)
那就是说,我唯一一次打扰这样的功能就是在嵌入式系统中。在桌面/服务器上,性能差异可以忽略不计。
答案 7 :(得分:0)
在调试器中运行它并查看生成的代码,以查看您的函数是始终是内联还是从不内联。我想当你想要更多关于编译器优化的知识时,看一下汇编代码总是一个好主意。
答案 8 :(得分:0)
抱歉小火......
编译器用汇编语言思考。你也应该。无论你做什么,只需在汇编程序级别单步执行代码。然后你就会知道编译器究竟做了什么。
不要以“快”或“慢”等绝对术语来考虑性能。这都是相对的,百分比方面的。快速制作软件的方法是在连续的步骤中删除占用过多百分比的事情。
这就是火焰:如果编译器可以很好地内联显然需要它的函数,并且如果它能够很好地管理寄存器,我认为这正是应该做的。如果它可以合理地展开显然可以使用它的循环,我可以忍受它。如果它试图通过删除我明确写下并打算被调用的函数调用来超越我,或者当JMP占用0.000001%的运行时间(Fortran的方式)时,我会巧妙地试图保存JMP并试图保存JMP,我坦率地说,生气了。
在编译器世界中似乎有一种观念认为没有无益的优化。无论编译器多么聪明,真正的优化是程序员的工作,而没有其他人。