假设我声明了一个函数foo(int arg1, int arg2 = 0, int arg3 = 0, int arg4 = 0)
。最后三个参数只会偶尔指定(如果有的话),并且大多数情况下,函数将被称为foo(some_int)
. 我是否会通过将函数声明为 来获得性能foo(int arg1)
,并在确实需要其他参数时使用不同的解决方案来传递它们?
换句话说,声明但未指定的默认参数是否会使函数调用变慢?
在这种情况下,函数是对象的构造函数,但这是一个普遍的问题。
假设我声明了一个函数foo(int arg1, int arg2 = 0, int arg3 = 0, int arg4 = 0)
。最后三个参数只会偶尔指定(如果有的话),并且大多数情况下,函数将被称为foo(some_int)
. 我是否会通过将函数声明为 来获得性能foo(int arg1)
,并在确实需要其他参数时使用不同的解决方案来传递它们?
换句话说,声明但未指定的默认参数是否会使函数调用变慢?
在这种情况下,函数是对象的构造函数,但这是一个普遍的问题。
(如果你喜欢,你可以阅读最后的结论)
我做了一个基准测试来测试这个,我首先运行了这个短程序大约十次:
#include <iostream>
#include <ctime>
using namespace std;
int returnMe(int me)
{
return me;
}
int main()
{
float begin = (float)clock();
for(int i = 0; i < 100000000; i++)
{
int me = returnMe(i);
}
printf("\nTime: %f\n", begin);
printf("\nTime: %f\n", (float)clock());
return 0;
}
它基本上执行该功能returnMe
一亿次,然后告诉我花了多长时间。值范围从 280 毫秒到 318 毫秒。然后我运行了这个程序:
#include <iostream>
#include <ctime>
using namespace std;
int returnMe(int me, int me87 = 0, int m8e = 0, int m5e = 0, int m34e = 0,int m1e = 0,int me234 = 0,int me332 = 0,int me43 = 0,int me34 = 0,int me3 = 0,int me2 = 0,int me1 = 0)
{
return me;
}
int main()
{
float begin = (float)clock();
for(int i = 0; i < 100000000; i++)
{
int me = returnMe(i);
}
printf("\nTime: %f\n", begin);
printf("\nTime: %f\n", (float)clock());
return 0;
}
大约十倍,现在的值从 584 毫秒到 624 毫秒不等。
结论:是的,它会使函数调用变慢,但幅度很小。创建一个单独的函数来将其他参数传递给对象,或者使用不同的构造函数,会提高性能,但是值得额外的代码吗?
Box2D 使用了另一种解决方法,它基本上是为默认参数创建一个单独的结构,并传递一个指向它的实例的指针。这样,当不需要设置额外的参数时,传递的唯一会降低性能的“垃圾参数”是一个空指针,这还不错。当您想指定一些默认值时,您可以在堆栈中创建所述结构的实例,填写您想要的值,然后将其地址传递给函数。简单、优雅、高效。
但是:两种用于保存性能的建议解决方案(一个额外的函数和传递一个结构指针)都需要额外的代码。如果您的函数很少被调用,并且额外的参数并不多,那么保存的性能很可能根本不会产生任何影响,如果是这种情况,那不值得您花时间。仅在必要时进行优化。请记住,我添加了 12 个默认参数,甚至没有将函数调用时间加倍。
======== 编辑:认真测试的奖金。
所以前两个测试是用简单的编译命令完成的g++ test.cpp -o test.exe
。正如许多评论中所指出的,这意味着优化级别为 -O0。在 -O3 测试我们会得到什么结果?
我重复了现在用 编译的测试g++ test.cpp -o test.exe -O3
,但发现程序现在在 1-2 毫秒内完成。我试图将迭代增加到一万亿,然后是一百万亿,同样的结果。所以我认为 g++ 可能看到我声明了一个我不会使用的变量,因此可能会跳过对 的调用,甚至可能跳过returnMe
整个循环。
为了获得一些有用的结果,我在 中添加了实际功能returnMe
,以确保它没有被优化掉。以下是使用的程序:
#include <iostream>
#include <ctime>
using namespace std;
long long signed int bar = 0;
int returnMe(int me)
{
bar -= me;
return me;
}
int main()
{
float begin = (float)clock();
for(int i = 0; i < 1000000000; i++)
{
int me = returnMe(i);
bar -= me * 2;
}
printf("\nTime: %f\n", begin);
printf("\nTime: %f\n", (float)clock());
printf("Bar: %i\n", bar);
return 0;
}
和
#include <iostream>
#include <ctime>
using namespace std;
long long signed int bar = 0;
int returnMe(int me, int me87 = 0, int m8e = 0, int m5e = 0, int m34e = 0,int m1e = 0,int me234 = 0,int me332 = 0,int me43 = 0,int me34 = 0,int me3 = 0,int me2 = 0,int me1 = 0)
{
bar -= me;
return me;
}
int main()
{
float begin = (float)clock();
for(int i = 0; i < 1000000000; i++)
{
int me = returnMe(i);
bar -= me * 2;
}
printf("\nTime: %f\n", begin);
printf("\nTime: %f\n", (float)clock());
printf("Bar: %i\n", bar);
return 0;
}
结果:
第一个程序:从 653 到 686 毫秒
第二个程序:从 652 到 735 毫秒
正如我所料,第二个程序仍然比第一个慢,但现在差异不那么明显了。
这取决于您的编译器、启用的优化以及函数是否内联。
如果函数/构造函数是内联的,编译器可能会对其进行优化。如果函数不是内联的,则每次调用都会将值推入堆栈,因此会对性能产生影响(显着或不显着)。
但请记住,过早优化是万恶之源。在运行配置文件并确保它需要优化之前,不要只是假设它会很重要并编写一个不太可维护的代码来绕过它。