一般来说,很多小函数会不会影响性能?

时间:2013-05-07 14:41:11

标签: c++

假设我需要完成三项任务。第一个选项是这样的:

void doAllStuffInOneFunc() {
     //code block for task 1
     ...
     ...
     //code block for task 2
     ...
     ...
     //code block for task 3
     ...
     ...
 }

或者,以下内容可能更易于阅读和维护:

 void doAllStuffByCallingOtherFuncs() {
      doTask1();
      doTask2();
      doTask3();
}

我会为第二个选项支付什么费用?

7 个答案:

答案 0 :(得分:6)

取决于:

  • 关于目标体系结构上函数调用的总体开销。
  • 关于传递任何参数的开销。
  • 处理任何返回值的开销。
  • 关于编译器是否决定内联调用。

将具有单独步骤的版本分解为各自的功能(至关重要的是,为其提供名称)的版本要好得多,并且在所有情况下都应该首选,并且只有在严重时才会删除分析和测试证明手动内联版本确实更好。

当然,只有当相关代码在性能关键路径上开始时才会发生这种情况。

答案 1 :(得分:6)

如果代码在编译单元中是“已知的”,并且函数不是太复杂,那么大多数现代编译器都会内联代码。如果函数也声明为static,那么它将不会生成“实际函数”。

编辑3:

关于static的解释:当一个自由函数(不是一个类的成员)可用于内联时,如果编译器不确定调用此函数的所有位置都是内联的,它会产生一个输出行功能(也称为“实际功能”)。

如果声明了一个自由函数static,它告诉编译器这个函数是“这个编译单元的本地”,所以没有别的东西会调用这个函数。如果编译器然后在此编译单元中内联所有调用,那么它也不需要生成“外联”函数,因为编译器可以知道对函数的所有调用。

另请注意,获取函数的地址也会强制编译器生成一个外联函数,因为函数指针必须指向某处[虽然在非常特殊的情况下,我看到编译器内联函数通过调用函数指针也是]

与所有性能问题一样,如果它在您的应用程序中非常关键,那么对实际代码(及其不同变体)进行基准测试和分析是确保正确行事的关键。没有“这是正确的答案”这样的东西,不同的编译器(在不同的平台上)具有不同的设置将做不同的事情。

编辑:除非有证据表明代码值得减少可读性,否则不要牺牲优化的可读性。总体代码中的很少一部分通常对性能很重要。

Edit2:如果你还可以重新使用其他功能中的一些代码,那就是额外的奖励。但通常,使代码可读是首先分成函数的关键目标。

答案 2 :(得分:2)

这取决于功能。如果他们被内联,你什么也不付钱。如果他们不是你支付一跳。它的成本你无法轻易预测。它取决于你跳到的地址,因为它可能发生在TLB未命中。

显然你必须考虑优化等级之类的事情。 一般规则是,如果你不在循环中调用它,你应该瞄准代码可读性而不是这种小优化。

答案 3 :(得分:2)

第二个选项会产生更清晰的代码。它将更容易维护您的代码,并且您可以更好地单独测试它们。虽然函数调用会有一些成本,但现代编译器可能会优化成本。

答案 4 :(得分:2)

在您的第一个示例中,我假设您正在对每个任务进行内联编码。这通常会更快。

据我所知: 你在第二个例子中支付的罚款很小。您将为参数分配所需的任何内存(假设您将在此处传递值)。您可能会找到this link a nice read。 根据每个子任务中的函数调用次数,堆栈的深度将变得越来越大。如果你计划在其中任何一个中调用巨大的递归函数,那么你将会达到递归限制,如果你不小心,你的程序可能会耗尽内存。 如果您愿意,可以为您拥有的代码生成程序集,并查看它实际调用函数的方式。程序集中的JMP或其他类型的GoTo操作可能需要解析它所发生的任何标签,这可能会增加一小部分时间,具体取决于程序的大小。实际上,通过使用这些功能,您不会产生太多开销。如果你将它们内联声明,它们将由编译器内联编写,用于代码执行,并且运行速度与首先以这种方式编写它们的速度一样快。 You can check out more on inline functions here

我个人认为,如果每个子任务彼此之间和/或大块代码相当独立,那么第二种方式就是前进的方法。如果遇到任何错误,将更容易维护和追踪错误。希望有所帮助!

答案 5 :(得分:1)

这取决于硬件以及拨打电话的频率。但总的来说,除非你的目标受众有一些荒谬的东西,否则它不应该对性能产生重大影响。

一般来说,让代码可读和维护比担心性能要好得多。

答案 6 :(得分:0)

一般没有。

通话非常快,不会对整体性能产生太大影响。如果您的任务非常糟糕,则无法衡量性能损失。如果任务非常小,编译器可能会内联它,这意味着通过优化自动删除调用。

如果出现以下情况,电话会变得更加昂贵:

  1. 有很多参数
  2. 参数是非基本类型,如std :: string并被复制。您可以通过使用引用(const std :: string&)
  3. 来避免这种情况
  4. 该调用是虚拟的(仅适用于使用多态的类)
  5. 要检查特殊情况(您的程序)的性能,您可以使用porfiler。这样的程序会告诉你最失去的表现。开始优化那里。