5

如果我有一个示例函数,例如:

void func1(float a, float b, float c)
{
    setA(a);
    setB(b);
    setC(c);                                        
}

调用内联函数:

inline void setA(float a){ m_a = a; m_isValid = false; }
inline void setB(float b){ m_b = b; m_isValid = false; }
inline void setC(float c){ m_c = c; m_isValid = false; }

我应该关心“m_isValid = false”重复还是编译器通过优化消除它们?

4

3 回答 3

10

是的,这通常称为死存储消除(用编译器的说法读取 = 加载和写入 = 存储)。

通常,编译器可以优化任何无用的操作,前提是它可以证明您(用户)无法注意到它(在语言设置的范围内)。

特别是对于 Dead Store Elimination,它通常仅限于:

  • 单个函数的主体(但是,内联在这里有帮助)
  • 不干预对不透明函数的调用

一些例子:

struct Foo { int a; int b; };

void opaque(Foo& x); // opaque, aka unknown definition

Foo foo() {
    Foo x{1, 2};
    x.a = 3;
    return x; // provably returns {3, 2}
              // thus equivalent to Foo foo() { return {3, 2}; }
}

Foo bar() {
    Foo x{1, 2};
    opaque(x); // may use x.a, so need to leave it at '1' for now
    x.a = 3;
    return x;
}

Foo baz() {
    Foo x{1, 2};
    opaque(x);
    x.a = 1;   // x.a may have been changed, cannot be optimized
    return x;
}

请注意,是否连续存储相同的值并不重要,只要编译器能够证明在两次存储操作之间没有读取一个变量,就可以安全地消除第一个。

一种特殊情况:根据 C++ 中的规范,volatile无法优化对 a 的加载/存储。这是因为volatile被指定为允许与硬件交互,因此编译器无法先验地知道硬件是否会在程序背后读取或写入变量。

另一种特殊情况:出于优化的目的,多线程程序中使用的内存同步操作(栅栏、屏障等)也可以防止这种优化。这是因为,与这种情况非常相似volatile,同步意味着另一个执行线程可能已经修改了该线程背后的变量。

最后,像所有优化一样,它的有效性在很大程度上取决于对上下文的了解。如果证明opaque不读取或不写入x.a,则可能会优化某些存储(如果编译器可以检查 的定义,则可以证明opaque),因此通常它实际上取决于内联和常量传播。

于 2013-08-25T18:00:42.483 回答
5

在这种特定情况下,一个体面的编译器应该删除它们。

完成一个完整的编译示例

struct Foo {
    float m_a, m_b, m_c;
    bool m_isValid;

    void setA(float a){ m_a = a; m_isValid = false; }
    void setB(float b){ m_b = b; m_isValid = false; }
    void setC(float c){ m_c = c; m_isValid = false; }

    void func1(float a, float b, float c);
};

Foo f;

void func1(float a, float b, float c)
{
    f.setA(a);
    f.setB(b);
    f.setC(c);
}

在这种情况下,g++ 编译func1

_Z5func1fff:
.LFB3:
    .cfi_startproc
    movl    4(%esp), %eax     ;; loads a
    movb    $0, f+12          ;; clears m_isValid
    movl    %eax, f           ;; stores m_a
    movl    8(%esp), %eax     ;; loads b
    movl    %eax, f+4         ;; stores m_b
    movl    12(%esp), %eax    ;; loads c
    movl    %eax, f+8         ;; stores m_c
    ret
    .cfi_endproc

请注意,虽然如果性能是一个问题,您确实应该注意如何设计程序,但这种微观级别的优化最好在最后完成,在测量代码实际浪费时间的地方之后。

于 2013-08-25T15:40:15.750 回答
0

大多数现代编译器(启用优化选项)都应该成功!

于 2013-08-25T20:29:54.573 回答