【问题标题】:which one is better in performance: using function or exact code?哪一个性能更好:使用函数还是精确代码?
【发布时间】:2012-05-31 20:48:35
【问题描述】:

我为 3D 模拟编写代码,所以我的代码充满了这样的内容:

使用函数可能会导致开销吗?为什么?

“a”是一个 3D 指针。

更新

函数定义

double update_a(double a[][JE][KE],...)
{

for(i=1;i<IE;i++){
    for(j=1;j<JE;j++){
        for(k=1;k<KE;k++){
            curl_h=(hz[i][j][k]-hz[i][j-1][k]-hy[i][j][k]+hy[i][j][k-1]);
            idxl[i][j][k]=idxl[i][j][k]+curl_h;
            a[i][j][k]=gj3[j]*gk3[k]*dx[i][j][k]+gj2[j]*gk2[k]*.5*(curl_h+gi1[i]*idxl[i][j][k]);
        }}}

IE=JE=KE=200

哪个更好:

 int main()
{
 update_a(...)}

int main(){
 for (i=0; i<200; i++) {
    for (j=0; j<200; j++) {
        for (k=0; k<200; k++) {
             curl_h=(hx[i][j][k]-hx[i][j][k-1]-hz[i][j][k]+hz[i-1][j][k]);

                idyl[i][j][k]=idyl[i][j][k]+curl_h;
                a[i][j][k]=gi3[i]*gk3[k]*ey[i][j][k]+gi2[i]*gk2[k]*0.5*(curl_h+gj1[j]*idyl[i][j][k]);
        }
    }
}
 }

更新2:

我的确切代码如下所示:

int main()
    {
for(n=1;n<100000;n++){
     update_a(...);
update_a2(...);
.
.
.
update_a30(...);}}

【问题讨论】:

  • 使用函数是什么意思?
  • 你能发布替代方案吗?
  • 使用函数可能会导致轻微的开销。将局部变量压入栈等
  • 澄清一下,函数调用里面到底是什么?函数是调用 a(i,j,k) 上的计算,还是调用整个三重 for 循环?
  • 唯一知道的方法是用探查器测试它们。只使用一个函数,因为它最容易维护,如果分析器说这个循环不好然后继续测试不同的编写方式。在那之前,没有人能真正帮助你。投票结束。

标签: c++ performance performance-testing


【解决方案1】:

将代码存储在函数中可能会导致一些开销,如果每秒调用一千次,开销就会开始增加,但请注意,有很多地方适合各种编译器优化很可能会执行,因为它位于循环中(这是用于 3D 模拟的常用更新逻辑函数所在的位置,因为它是每帧数据)。我建议不要对此进行任何实际操作,只有在测试过程中发现问题时才可能进入“手动优化模式”。

但如果您确实使用“3D”指针(T***),则会产生大部分开销,因为它只会连续分配实际的匿名。指向指针的指针数组。这意味着每次间接都会使您付出沉重的代价,因为任何给定元素的地址都无法像使用线性布局在内存中的 T[m][n][q] 数组那样简单地计算(并且可以衰减为指针,也许这就是您所指的)。然后,由于内存和 CPU 性能之间的差异,您将产生开销。

【讨论】:

  • 由于我的“3D”指针的连续形式,我可以避免缓存未命中并使用“mpi 派生数据类型”
【解决方案2】:

与您提供的代码的运行时间相比,函数调用的性能损失微不足道。

【讨论】:

  • 不相关,因为该函数实际上不会在任何具有适当优化标志的现代 C++ 编译器上调用。此外,与函数调用相关的开销会在许多数字代码示例中产生相当大的差异。
  • 不是那么简单,因为像缓存这样的代码在当时起到了一定的作用。此外,如果调用数百万次,即使调用本身也会产生开销。
  • 当然取决于函数调用的次数,但如果我们想象循环将停留在原处,并且中间的所有垃圾都进入一个函数,那么当然与至少 27 次间接(如果真的有那么多,我不知道“3D 指针”实际上是什么)、我可以看到的 6 次乘法以及各种其他算术相比,函数调用的开销很小。当然,还有一个问题是,编译器可以将多少垃圾提升到内部循环之外——可能要做的工作比乍看之下要少。
  • @void-pointer:你说,“函数实际上不会被调用”,C++ 抽象机说,“函数调用开销可能为零”。 Potayto,potahto ;-)
【解决方案3】:

过早的优化是不好的!

您应该使用一个函数,并且仅在发现存在问题时才手动内联代码! (使用分析工具和性能测试)

函数开销可以忽略不计,尤其是考虑到您的代码。

无论如何,许多编译器都会根据需要内联函数。这意味着您所做的任何内联都不会影响性能,并且会导致维护和代码可读性问题。

【讨论】:

  • 如果不为时过早,微优化不一定是坏事。
【解决方案4】:

它没有显着差异。该函数仅在main 中调用一次。所以函数调用开销,如果有的话,每次运行程序只支付一次。与运行程序所需的所有工作相比,一个额外的函数入口和返回不算什么。

有一种方法可能很重要,即如果您在... 中未显示的参数之一是按值传递的,并且复制起来确实很昂贵。但是,您显示的只是一个指针,因此复制起来并不昂贵。

【讨论】:

  • 感谢您的帮助。我对您的回答有疑问,您提到“该函数仅在 main 中调用一次。因此,函数调用开销(如果有)仅在每次运行程序时支付一次。”,用于执行我的代码有了这些函数,我必须调用函数大约 3,000,000 次!这些大量的函数调用会导致我的代码速度降低吗?
  • @Ehsan:不,您的主程序不会调用update_a 大约 300 万次。它只调用一次。如果您仍然不会显示您的实际代码的作用,我无能为力。你必须自己回答这个问题。不要考虑函数被调用了多少次,要考虑函数调用开销与调用时函数所做的事情相比是否可能是一个显着的成本。在您的情况下,该函数包含执行 800 万次操作的嵌套循环,因此它并不重要。然后,用秒表运行这两个版本的代码,看看你的猜测是否正确。
  • 感谢您的回复。我的确切代码大约有 1500 行;但我试图向您展示我的代码到底是什么样的。请看update2
【解决方案5】:

函数调用会稍微消耗你创建堆栈和其他东西的性能。

但在您的情况下,这并不重要,因为您只有几行代码(假设)。

【讨论】:

  • @Ehsan:你的意思是你的代码中有完全相同的 3 行不可读的垃圾被复制并粘贴到 30 个不同的地方?如果是这样,那么您需要将其共享。如果这被证明是低效的,那么接下来研究如何有效地将其共享!绝对最坏的情况,你把它放在一个函数中,你找不到一种方法来避免函数调用开销,这会影响你的性能,所以你用一个等效的宏替换这个函数。您至少避免了代码重复。就编译器而言,代码是相同的,所以它会执行相同的操作。
  • ... 但最坏的情况不太可能发生。将其放入函数中您可能不会注意到性能差异,只是没有人可以向您保证您不会。
  • @SteveJessop 我的意思是 30 个这样的循环(不一样,但像在一起)。我发布的循环是“a”的更新功能。如果我一次运行这些循环就没有问题。当我必须更新我的“a”和其他参数 100000 次时,问题就出现了
  • @Ehsan:那么,我还是不太明白你在问什么。你一直说,“我有代码 A。代码 B 使用函数。它慢吗?”。您已经向我们展示了代码 A 的一部分,但您还没有向我们展示代码 B,您也没有运行它们来比较每个需要多长时间。这就是为什么你会得到很多概括和猜测。试一试,如果您不喜欢结果,请询问有关如何改进您尝试过的代码的问题。
【解决方案6】:

假设您正在谈论的函数在循环内被调用:

在任何现代 C++ 编译器上,该函数几乎肯定会被内联。以g++为例,你会发现用函数调用产生的机器码和不用函数调用产生的机器码是一样的。

如果函数调用在循环之外,编译器是否决定内联函数取决于使用函数的上下文。

【讨论】:

  • 这么大的循环,如果用在很多地方呢?这似乎不太可能。如果您不通过某些构造强制内联,我怀疑它会被内联
  • 特别是如果在很多地方都使用了循环——编译器将确定该函数是“热的”,并且它被内联的机会将大大增加。您是否尝试过使用任何最新版本的 g++ 的代码?这是一个非常简单的测试,您可以在发表评论之前尝试一下。
  • 如果主体尺寸相对较小,则内联是有利的。如果它很大,如果只使用一次或两次内联(代码重复很少),则内联很有用,否则内联几乎没有任何好处,但代码缓存未命中。如果您甚至不为答案而做,我为什么要提供已编译的程序集来发表评论?
  • 对不起,我在这里有点困惑:我假设函数调用是 inside 循环。在这种情况下,内联会产生巨大的影响,编译器几乎肯定会内联函数。如果整个循环本身就是一个函数,那么函数是否被内联取决于上下文。
  • 现在,无论如何。把它放在一个函数中基本上会让编译器决定它是否应该被内联。在几乎所有情况下,编译器的判断力都比我们好。如果你真的希望它做出最好的决定,你可以将分析数据作为提示传递给最新的编译器..
猜你喜欢
  • 2018-05-09
  • 2012-08-25
  • 1970-01-01
  • 2018-10-02
  • 2016-07-16
  • 2011-07-23
  • 2019-04-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多