【问题标题】:C: Does value return from the function take more CPU cycles than void?C: 从函数返回的值是否比 void 占用更多的 CPU 周期?
【发布时间】:2018-07-13 21:38:47
【问题描述】:

如果在 C 中使用 void 函数与返回任意类型(例如 int)的函数相比,int 函数会比 void 函数占用更多 CPU 周期吗?

例子:

int a, b = 1, c = 2;

void f()
{
     a = b + c;
}

int g()
{
     a = b + c;
     return a;
}

我的常识告诉我返回是一个动作,所以它应该花费一些 CPU 时间,但是我没有这里需要的适当的深入基础知识,我也不知道汇编程序可以自信地回答这个问题我自己。谷歌搜索也没有成功。

编辑: 我的兴趣纯粹是学术性的,我不希望通过使用 void 与 int 函数来获得任何显着(甚至接近)的性能。

【问题讨论】:

  • 是的——通常,必须将返回值加载到目标寄存器中(尽管编译器可能会对其进行优化,以便您的代码无论如何将其加载到该寄存器中)。但是,如果您查看典型mov 指令的实际timing latency (PDF),您的担忧会消失吗?它是“1”,意思是(在最基本的解释中)对于像我的 Mac 这样的 3.04GHz 计算机,它需要 1/3,040,000,000 秒。
  • 优化的汇编器输出说明了什么?如果这对您真的很重要,那就是找出答案和大量基准测试的方法。提示:可能不是。
  • @tadman 你是对的,它不是。我在这里的兴趣纯粹是学术上的,我不希望通过学习如何处理返回值来提高性能。至于汇编器,我没有生成汇编代码只是因为我不知道它(还)。
  • 汇编代码是您学习最多的地方,因为编译器会告诉您它是如何解释您的代码的。弄清楚如何获得“反汇编”输出,您将获得所需的所有答案,或者至少更接近它们。
  • @usr2564301 显然太长了。

标签: c function return-value cpu-usage


【解决方案1】:

这完全取决于 CPU 指令集和调用约定。

例如,如果返回值总是在特定寄存器中返回,并且编译器可以在插入返回指令之前将计算结果 b+c 安排在该特定寄存器中,则为这两个生成的代码功能可能相同。

但是,除非您用尽所有其他选项来提高性能,否则您不想考虑在程序中进行优化。而你当然没有。

【讨论】:

  • 我完全同意这不是优化代码的方式。我对这里的兴趣纯粹是学术上的,我不希望通过学习如何处理返回值来提高性能。
【解决方案2】:

在 x86_64 系统上,这两个函数可以编译为相同的代码。我已经用大致等效的 C 代码“修饰”了反汇编:

f_or_g:
    pushq   %rbp                 ; // Standard stack frame setup
    movq    %rsp, %rbp           ; // same
    movl    OFFSET1(%rip), %eax  ; eax = c;
    addl    OFFSET2(%rip), %eax  ; eax += b;
    movq    OFFSET3(%rip), %rcx  ; rcx = &a;
    movl    %eax, (%rcx)         ; *rcx = eax;
    popq    %rbp                 ; // Standard stack frame teardown
    retq                         ; return

由于 x86_64 使用 eax 作为 32 位值的返回寄存器,加法的结果已经在“正确的位置”返回它——不需要额外的代码。

在更复杂的函数中,可能需要一些小的开销来确保返回值在正确的寄存器中结束。不过,一般来说,这种开销应该是非常小的。

同样的原则适用于大多数其他架构——这并不特定于 x86_64;我只是在使用它,因为这是第一个出现的编译器。

【讨论】:

  • 我是否正确理解任何使用eax 的处理器(或类似的我猜——抱歉,如果我只是说些废话!)不需要额外的代码?
  • 这基本上取决于您返回的值是您在函数中计算的最后一件事,因此它可以确保计算将其结果放在同一个寄存器中。
  • @ZenJ 寄存器的确切名称无关紧要。唯一真正重要的因素是体系结构的调用约定使用寄存器作为返回值(大多数这样做),并且体系结构不需要某些寄存器用于特定操作(大多数不需要;x86_64 做了一点,但它没有不在这里发挥作用)。
  • @Barmar 它不一定是 last 值;只要有足够的可用寄存器,就可以在计算返回值“之后”执行任何工作,而无需触及返回寄存器。
  • 您可以将来自 cmets 的信息包含在答案中吗?我认为他们确实为它增加了价值。
【解决方案3】:

我认为这个问题没有多大意义。

如果您需要返回值,则无法使用void 来加快它的速度——即使它会更快。
如果您不需要结果,返回它是没有用的,所以不要这样做。 无论哪种方式,选择都是由调用者的需要定义的。

通常,现代编译器不会返回值,而是在原地构造它。例如,如果您编写int sum = f(a,b);,编译器将永远不会在您的函数中生成临时变量,而是使用sum 的内存来存储结果。这意味着执行时间没有差异。

【讨论】:

  • 实际上有时这可能会派上用场:例如,如果一个函数修改了一些全局变量或类成员变量,但在使用函数的每个地方都不需要返回。但无论如何,我只想将我的问题应用于一个完美的“学术知识”世界,所以基本上这是一个可能没有实际意义的“假设”问题。我很抱歉一开始没有说清楚
  • 在你的例子中,编译器知道你什么时候使用结果,什么时候不使用,并在你使用它时就地构造它,如果你扔掉它就永远不会返回它。换句话说:如果你定义了一个返回值,并且从不在任何地方使用它,它就永远不会被返回,所以没有区别。
【解决方案4】:

我们不能说是否会有额外的周期使用,因为它完全取决于您的处理器或 cpu,此外,如果您需要返回值,则 return 语句仅访问该元素的内存位置,因此可能只有复杂性的微小变化可以忽略不计。

【讨论】:

    猜你喜欢
    • 2016-08-28
    • 2023-03-24
    • 2021-04-02
    • 1970-01-01
    • 2021-06-06
    • 2017-07-13
    • 2011-03-26
    • 2015-04-26
    • 2020-02-10
    相关资源
    最近更新 更多