【问题标题】:C static inline parameter evaluation optimizationC静态内联参数评估优化
【发布时间】:2016-11-05 14:47:45
【问题描述】:

假设一个 API,其中每个函数都返回一个错误代码,在没有错误的情况下为零,在错误值的情况下为非零。

让

int foo(...);
int bar(...);

成为此 API 中的函数。假设有一个代码片段,其中必须按顺序调用foo 和bar 并且应该始终调用 foo 和 bar,而不管先前的错误,但是传播第一个返回的非零错误码,即

int foobar(...)
{
    int rc = 0, rc_;
    /* ... */
    rc_ = foo(...); rc = rc ? rc : rc_;
    rc_ = bar(...); rc = rc ? rc : rc_;
    return rc;
}

编写 rc,rc_ 多路复用很累并且容易出错(无论是否使用三元运算符、if/else 或其他东西)。

让传播辅助函数出现错误

static inline
int rc_propagate(int r, int p){ return p ? p : r; }

这可以像这样在foobar中使用

int foobar(...)
{
    int rc = 0;
    /* ... */
    rc = rc_propagate(foo(...), rc);
    rc = rc_propagate(bar(...), rc);
    return rc;
}

C 标准是否允许通过将 rc_propagate 的第一个参数(一个静态内联函数)的评估拉到三元中进行优化,这样如果第二个参数 @ 时,由于三元运算符评估规则,它可能不会被执行987654329@ 非零?

【问题讨论】:

  • 我认为没有,但我会等待有人引用标准的相关部分。
  • @2501:只是在这种情况下它不是外部函数,而是静态内联。我的怀疑完全是关于静态内联;具体来说,我刚刚阅读了 LLVM 开发人员的一些博客文章以及关于调用静态内联函数的可能优化。
  • 顺便说一句,您为什么不直接写:rc = rc ? rc : foo(...); 并完全避免这个问题?
  • 我认为因为对bar() 的函数调用是内联函数的参数,所以即使foo() 失败也必须调用它——这不是你想要的。无论rc_propagate() 函数是否内联,程序的行为都不应改变。
  • 如果您想要无论如何都要对其进行评估,我认为内联函数会做到这一点。如果没有,您可以尝试类似函数的宏...

标签: c optimization language-lawyer


【解决方案1】:

只要程序保持不变,就允许编译器(或硬件)优化程序1,即您不能证明执行的程序与执行的程序不同你写的。

在这种情况下,您编写的程序将始终调用外部函数,因为它不存在于可能无法计算的三元运算符中。该函数可能具有不同的有趣副作用,并且编译器不知道。这意味着程序的优化版本必须在某些时候调用外部函数(代码可能会重新排序)以保留该行为。

如果不是这样,您可以证明执行的程序与您编写的程序不同。这样做很容易;将 printf(或等效)语句放入外部函数调用中。

1。抽象程序的概念,它在执行时存在,而不是生成的机器代码或可执行文件。


使用权威的参数,您可以看到没有编译器会真正优化对 foo() 和 bar() 的调用:

gcc 版本 4.9.2、5.3 或 6.1,带 -O2:

foobar():
pushq   %rbx    
call    foo()    
movl    %eax, %ebx    
call    bar()    
testl   %ebx, %ebx    
cmove   %eax, %ebx    
movl    %ebx, %eax    
popq    %rbx    
ret

clang 版本 3.7.1 或带有 -O2 的 3.8:

foobar():                          
pushq   %rbx    
callq   foo()    
movl    %eax, %ebx    
callq   bar()    
testl   %ebx, %ebx    
cmovnel %ebx, %eax    
popq    %rbx    

【讨论】:

  • 嗯,我怀疑传播函数是static inline 并且没有副作用,所以在这种情况下编译器确实知道里面发生了什么。我只是想确保链中的所有函数将被调用,即使其中一个返回错误。将rc_propagate 设为外部肯定会成功,但是static inline 呢?后者让编译器全面了解那里发生的事情。
  • @datenwolf 即使使用静态内联也会调用它们。注意:我提到外部函数是未知的,而不是静态的,因此必须调用前者。
猜你喜欢
  • 1970-01-01
  • 2020-07-24
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多