未使用的默认参数会降低c++的性能吗?

Will unused default arguments decrease performance c++

本文关键字:c++ 性能 默认 参数 未使用      更新时间:2023-10-16

假设我声明一个函数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: %fn", begin);
   printf("nTime: %fn", (float)clock());
   return 0;
}

基本上执行函数returnMe 1亿次,然后告诉我花了多长时间。取值范围从280毫秒到318ms。然后我运行这个程序:

#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: %fn", begin);
   printf("nTime: %fn", (float)clock());
   return 0;
}

大约10次,现在的值在584 ms到624 ms之间。

结论:是的,它会使函数调用变慢,但幅度很小。创建一个单独的函数来传递其他参数给对象,或者有一个不同的构造函数,将会提高性能,但它值得额外的代码吗?

还有另一种解决方法,由Box2D使用,它基本上是为默认参数创建一个单独的结构体,并传递一个指针到它的实例。这样,当不需要设置额外参数时,传递的唯一降低性能的"垃圾参数"是一个空指针,这并不是那么糟糕。当您想要指定一些默认值时,您可以在堆栈中创建所述结构体的实例,填充所需的值,然后将其地址传递给函数。简单、优雅、高效。

然而:两种节省性能的解决方案(一个额外的函数和传递一个结构指针)都需要额外的代码。如果您的函数很少被调用,并且额外的参数没有那么多,那么节省的性能可能根本不会产生任何影响,如果是这种情况,那么不值得您花费时间。只在必要时进行优化。记住,我添加了12个默认参数,甚至没有使函数调用时间翻倍。

= = = = = = = =编辑:对认真测试的奖励。

所以前两个测试是用简单的编译命令g++ test.cpp -o test.exe完成的。正如在许多评论中指出的那样,这意味着优化级别为- 0。在-O3下测试会得到什么结果?

我用g++ test.cpp -o test.exe -O3重复了现在编译的测试,但发现程序现在在1-2毫秒内完成。我试着把迭代增加到1万亿,然后是100万亿,结果是一样的。因此,我认为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: %fn", begin);
   printf("nTime: %fn", (float)clock());
   printf("Bar: %in", 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: %fn", begin);
   printf("nTime: %fn", (float)clock());
   printf("Bar: %in", bar);
   return 0;
}

结果:

第一个程序:从653到686毫秒

第二个程序:从652毫秒到735毫秒

正如我所料,第二个程序仍然比第一个程序慢,但是差别现在不那么明显了。

这取决于你的编译器,是否启用了优化,以及函数是否内联。

如果函数/构造函数是内联的,编译器可能会将其优化掉。如果函数不是内联的,则每次调用都会将值压入堆栈,因此它会对性能产生影响(无论是否重要)。

但请记住,过早优化是万恶之源。在运行概要文件之前,不要只是假设这是一个大问题,并编写一个不易维护的代码来绕过它,并确保它需要优化。