衰减调用方方法中的多个函数

Decay multiple functions inside caller method

本文关键字:函数 方法 调用 方方法 衰减      更新时间:2023-10-16

这个问题主要与我所遇到的大型代码维护和可重用性问题有关。我有几个类,它们有这样的方法:

void MyClassX::Draw(){
   ///Some large chunk of code#1 here

   ///Another large chunk of code#2 goes here

   ///Another large chunk of code#3 goes here
}

void MyClassY::Draw(){
   ///Some large chunk of code#1 here

   ///Another large chunk of code#2 goes here
}

该方法变体之间的区别在于,其中一些代码块在不同的类中是可选的。现在,这可能听起来很傻,但因为该方法执行一些性能关键的实时处理(3D渲染)。我不为这些代码块使用函数,也不想用这些函数来包装它们,因为我试图减少一般的函数调用次数。我想要的是在一些块中定义这些函数(例如使用#define),并在void Draw()内的适当位置声明这些函数,而不是整个代码块。我不想使用预处理器,因为它很难编辑这样的代码(没有适当的错误提示或调试)。我认为另一个选择是内联函数。但因为编译器不保证内联,我不确定这种方式是有效的。还有其他技巧吗?也许通过c++模板的使用?我可以在调用者方法中强制一些函数体衰变吗?

注:出于性能考虑,我也没有对这些类使用继承,因为它基本上也可以通过将所有这些块定义为基类中的函数来解决这个问题。同样在void Draw()中,我尝试将条件分支减少到最小。

在某些编译器上有强制使用内联函数的方法,例如MSVC上的__forceinline或GCC上的__attribute__((always_inline))。它是非常特定于编译器的,但您可以通过一些注意事项来保证它。在这里,我建议检查你的编译器文档,并习惯在一定程度上分析反汇编(足以识别函数调用,例如,这相当容易,因为你可以用调试器跟踪它,并注意到它何时发生分支)。

也就是说,我真的认为你可能看错了。我可能是错的,也许你有我以前没有遇到过的非常特殊的需求,但我在一个性能关键领域,除了正确性之外,效率通常与产品的感知质量成正比(我也在3D领域,包括路径跟踪)。

我已经习惯了手头有一个分析器,我已经学会了对优化器持保留态度。有些事情优化器和标准库做得不太好,我习惯做一些事情,比如滚动我自己的内存分配器,并获得可观的收益。

但是在我的许多分析会话中,我从来没有遇到过编译器在函数调用开销方面做得很差的情况。我见过优化器有问题,在指令选择或寄存器分配方面做得很差,以某种方式重写代码实际上并没有帮助,但却有助于产生更有效的汇编。这包括将一些大块代码转换为更多的函数,您可能认为这不会提高性能,但会帮助编译器处理这两个方面(寄存器分配和指令选择)。

我在优化中发现的一个更有用的事情是,优化器可能很棒,但它们不了解在运行时将接收到什么样的用户输入。这是通用案例执行的一个关键方面,只有了解产品设计的人才能预料到。因此,我发现的一件事是,将少数情况下的代码分支放入单独的、未内联的函数中,这样逻辑就不会全部内联在一个巨大的blob中,实际上在某种程度上有所帮助(没有调查所有这些情况下的反汇编,只是注意到时间上的改进)。我认为这只是一种帮助编译器不把你所有的代码看作是一种公平的竞争环境,帮助它更有选择性地/局部地优化一点,因为只有这么多寄存器,例如,把所有的东西放在一个巨大的函数中可能会让优化器困惑,不知道哪一部分需要更多的关注。

也就是说,我发现在分析和优化期间使用更集中的代码通常是有帮助的。这并不是因为在大型函数中使用更集中的代码实际上加快了速度,而是因为当您试图挤出微观级别的效率时,更容易使用一些不那么结构化的代码。在您进行微调之后,强加更多的结构比在您进行详尽的分析之前试图将其分解更容易,因此有时以这种方式编写代码可能会有所帮助,并牺牲一些整洁和结构以获得大块代码。但是这样做的好处是,在分析代码之后,更容易更改代码,并在事后获得帮助组织代码的效率知识;我从来没见过这样的代码能让事情变得更快。

所以我真的认为你最好不要太关注内联,把它更多地留给优化器。通常有更好的东西可以优化,即使是在最小的微效率水平上也能获得更清晰的收益。