假设我需要完成三项任务。第一个选项是这样的:
void doAllStuffInOneFunc() {
//code block for task 1
...
...
//code block for task 2
...
...
//code block for task 3
...
...
}
或者,以下内容可能更易于阅读和维护:
void doAllStuffByCallingOtherFuncs() {
doTask1();
doTask2();
doTask3();
}
我会为第二个选项支付什么费用?
答案 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)
一般没有。
通话非常快,不会对整体性能产生太大影响。如果您的任务非常糟糕,则无法衡量性能损失。如果任务非常小,编译器可能会内联它,这意味着通过优化自动删除调用。
如果出现以下情况,电话会变得更加昂贵:
要检查特殊情况(您的程序)的性能,您可以使用porfiler。这样的程序会告诉你最失去的表现。开始优化那里。