gcc是否会优化对相同变量的重复函数调用,并为每次调用提供相同的输出?

Will gcc optimize away repeated function calls upon the same variable with same output for each call?

本文关键字:调用 输出 优化 是否 变量 函数调用 gcc      更新时间:2023-10-16

对于一个应用程序,我的情况是相同的信息以多种形式存在:Base64字符串,十六进制字符串和char[]

现在,为了提高效率,不要费力地声明&对每个函数初始化一次变量,我只在上述形式之间明显的转换点上应用它。原因是,在某些点上,变量不需要转换为另一种形式,以进行条件比较等操作。

从我所读到的,看起来好像编译器是令人难以置信的高效,而且越来越高效;然而,当我试图阅读更深入的分析和描述时,我经常超过我经验的极限,我的大脑堆栈溢出。

如果一个函数被反复调用到一个变量,把它变成另一种形式,比如从Base64字符串到十六进制字符串,每次都产生相同的结果,编译器会优化这些调用,使为整个作用域声明的变量是不必要的吗?

在我的情况下,我使用-Ofast,直到有更好的东西。

编译器可以优化掉的东西实际上取决于代码是如何编写的;然而,依赖编译器过于聪明通常是不明智的。编译器在优化寄存器分配和各种低级别的东西方面做得很好,但是如果你知道你的程序中有一些不变量可以让代码更有效地编写,不要认为编译器理解整个程序。

对于您提到的这个特定示例,如果您将数据包装在实现各种格式转换操作符的类中,并缓存转换结果,这将是比依赖编译器不重做相同计算更好的方法。但是,如果将这些转换操作符标记为"const",则有可能(假设没有执行交错的非const操作)编译器将重用先前调用"const"方法的结果。但是,我建议在缓存结果的基础上再执行这个操作,而不是依赖于这个优化。

而且,当涉及到这些优化时,唯一确定的方法是使用特定的编译器实际编译代码并检查汇编输出以确定它是否应用了该优化。

我希望gcc不会执行这种优化。如果是这样,就必须满足几个要求,比如被调用的函数同时被编译,以提供跨调用优化寄存器的可能性。

这样的优化令人印象深刻,但并不完全有用。程序员可以很容易地编写对函数的调用并保存返回值。

CDC Cyber (c. 1975) FORTRAN编译器有一些有趣的行为。它将优化对IRAND()的调用。这让许多编写游戏的学生感到惊讶和困惑,就像下面的掷两个骰子的片段:

integer roll
roll = mod (irand(0), 6)  +  mod (irand(0), 6)  +  2

这只产生偶数,因为它认为它好像写的是

roll = 2 * mod (irand(0), 6)  +  2

它被报告为1978年左右的一个错误,通过不优化涉及irand()rand()的表达式来修复。这很容易通过使其难以优化来解决:

integer roll, die1, die2
die1 = mod (irand(0), 6)  +  1
die2 = mod (irand(0), 6)  +  1
roll = die1 + die2

只要优化不是太高,它就会像预期的那样工作。第一个例子是总是优化:它不能被关闭。

这里有一段代码来说明这个概念:

class TriString
{
  public:
    enum Format { Binary, Hex, Base64 };
    TriString(const std::string& s) : s_(s) { }
    // mutators - must modify b_ and h_ accordingly or clear them
    TriString& operator=(const std::string& rhs)
        { s_ = rhs; b_.clear(); h_.clear(); }
    TriString& erase(size_type index = 0, size_type count = npos)
    {
        s_.erase(index, npos);
        h_.clear(); // will need regeneration...
        b_.erase(index * 2, count == npos ? npos : count * 2);
    }
    char& operator[](size_type n)
    {
        h_.clear();
        b_.clear();
        return s_[n];
    }
    // ...add more as needed...
    // accessors
    const std::string& get(Format) const
    {
        if (Format == Binary || s_.empty())
            return s_;
        if (Format == Hex)
        {
            if (h_.empty()) h_ = to_hex(s_);
            return h_;
        }
        // Format == Base64
        if (b_.empty()) b_ = to_base64(s_);
        return b_;
    }
    const char& operator[](size_type n) const { return s_[n]; }
    // ...add more as needed...
  private:
    std::string s_;          // normal string
    // "cached" conversions - invariant: valid if not empty(), or s_.empty() too
    // (mutable so get(Format) const can modify despite being const)
    mutable std::string b_;  // base64 encoded
    mutable std::string h_;  // hex encoded
};

使用通常的std::string接口执行此操作并不安全,因为像下面这样的客户端代码将不起作用:

TriState s("hello!");
char& c = s[2];
const std::string& h = s.get(TriState::Hex);  // triggers caching of hex conversion
c = 'x';                                      // oops - modifies s_ without clearing/updating h_
const std::string& h2 = s.get(TriState::Hex); // oops - gets old cached h_ despite changed s_

您必须做出一些选择,要么限制接口以避免授予持续更改字符串的能力(如非const operator[],迭代器等),返回代理对象(而不是例如字符引用),可以在写入时清除缓存的转换,或者记录一些客户端使用的限制并希望得到最好的....