1

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

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

或者,以下内容可能更好地提高可读性和维护性:

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

我将为第二种选择支付什么费用?

4

7 回答 7

6

这取决于:

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

将单独的步骤分解成自己的功能的版本(关键是为它们提供了一个名称)要好得多,在所有情况下都应该首选,并且只有在认真的分析和测试证明手动内联版本是真的更好。

当然,这只有在相关代码一开始就处于性能关键路径上时才会发生。

于 2013-05-07T14:44:16.903 回答
6

如果代码在编译单元中是“已知的”,并且函数不是过于复杂,那么大多数现代编译器无论如何都会内联代码。如果该函数也被声明static,那么它不会生成一个“真正的函数”。

编辑3:

解释static:当一个自由函数(不是类的成员)可用于内联时,如果编译器不确定调用此函数的所有位置都是内联的,它会产生一个外联函数(又名“真正的功能”)也是如此。

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

还要注意,获取函数的地址也会迫使编译器生成一个外联函数,因为函数指针必须指向某个地方[尽管在非常特殊的情况下,我也看到编译器通过函数指针调用内联函数]

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

编辑:除非有证据表明代码的可读性降低是值得的,否则不要为了优化而牺牲可读性。无论如何,总体代码中很少有通常对性能很重要。

Edit2:如果您还可以在其他函数中重用某些代码,那是额外的好处。但是使代码可读是首先拆分为函数的关键目标,通常情况下。

于 2013-05-07T14:45:05.300 回答
2

这取决于功能。如果它们是内联的,您无需支付任何费用。如果他们不是你支付一跳。它的成本你不能轻易预测。这取决于您要跳转到的地址,因为它可能会在 TLB 未命中时发生。

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

于 2013-05-07T14:43:34.697 回答
2

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

于 2013-05-07T14:44:04.137 回答
2

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

据我了解:您在第二个示例中支付的罚款很小。您将为参数分配所需的任何内存到堆栈上(假设您将在此处按值传递)。你可能会觉得这个链接很好读. 根据每个子任务中函数调用的数量,堆栈的深度会越来越大。如果您计划在其中任何一个函数中调用大量递归函数,那么您将遇到递归限制,并且如果您不小心,您的程序可能会耗尽内存。如果您愿意,您可以为您拥有的代码生成程序集,并查看它实际上是如何调用函数的。程序集中的 JMP 或某种其他类型的 GoTo 操作可能需要解析它要去的任何标签,这可能会增加少量时间,具体取决于程序的大小。但实际上,使用这些函数并不会产生太多开销。如果您将它们声明为内联,它们将由编译器内联编写以执行代码,并且运行速度与您一样快您可以在此处查看有关内联函数的更多信息

我个人的观点是,如果这些子任务中的每一个都彼此相当独立和/或大块代码相互独立,那么第二种方法就是要走的路。如果遇到任何错误,维护和跟踪它们会更容易。希望有帮助!

于 2013-05-07T14:50:40.443 回答
1

这取决于硬件以及您拨打电话的频率。但总的来说,除非你的目标受众有一些荒谬的古老东西,否则它不应该显着损害性能。

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

于 2013-05-07T14:44:36.840 回答
0

一般来说没有。

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

如果出现以下情况,通话费用会变得更高:

  1. 有很多参数
  2. 参数是非基本类型,如 std::string 并被复制。您可以通过使用引用来避免这种情况 (const std::string&)
  3. 调用是虚拟的(仅在使用多态的类中才有可能)

要检查特殊情况(您的程序)的性能,您可以使用 porfiler。这样的程序会告诉你被损失最多的性能。在那里开始优化。

于 2013-05-07T14:51:03.003 回答