C ++中常见的表达式消除的局限性

时间:2015-05-29 04:29:11

标签: c++ clang compiler-optimization

我正在看一个演讲,"Efficiency with Algorithms, Performance with Data Structures",而且是 对以下评论感到惊讶:

#include <string>
#include <unordered_map>
#include <memory>

struct Foo {
  int x;
};

Foo* getFoo(std::string key,
            std::unordered_map<std::string,
                               std::unique_ptr<Foo>> &cache) {
  if (cache[key])
    return cache[key].get();

  cache[key] = std::unique_ptr<Foo>(new Foo());
  return cache[key].get();
}

Foo* getFooBetter(std::string key,
                  std::unordered_map<std::string,
                                     std::unique_ptr<Foo>> &cache) {
  std::unique_ptr<Foo> &entry = cache[key];
  if (entry)
    return entry.get();

  entry = std::unique_ptr<Foo>(new Foo());
  return entry.get();
}

getFooBetter()更好。我一直认为我可以依靠 在编译器上执行此类转换 我希望只评估x+y多次出现的方式 一旦。不出所料,生成的LLVM IR确实同意了 主持人。即使使用-O9,我们也会留下3个cache[key]来电 getFoo()版本。

我已将长LLVM IR of both with c++ symbols unmangled移出线,以免在视觉上令人反感。

Another StackOverflow question显示,答案的一部分是operator[] 假设能够修改它所希望的任何全局状态,并且 因此我们不能忽视电话。 A linked proposal关于介绍一个 [[pure]]注释讲述了它对CSE的应用。

如果我们留下4个电话,我就能在此结束感到满意。 但是,如果我对IR的读数是正确的,看起来我们已经优化了 getFoo()就像我们写的那样:

Foo* getFoo(std::string key,
            std::unordered_map<std::string,
                               std::unique_ptr<Foo>> &cache) {
  if (cache[key])
    return cache[key].get();

  std::unique_ptr<Foo> &entry = cache[key];
  entry = std::unique_ptr<Foo>(new Foo());
  return entry.get();
}

有人能够解释clang对代码的看法是什么 它能够合并最后两个cache[key],但不是全部 他们? (我当地的铿锵声是3.4。)

2 个答案:

答案 0 :(得分:2)

llvm中的CSE实现正在使用Arithmatic Expressions。 你可以在llvm / lib / Transforms / Scalar / EarlyCSE.cpp中看到llvm Common Subexpression Elimination源代码

我们在这里遇到的案例是过程间优化。

此通话cache[key]原来是[](cache,key)功能。因此,根据内联[]函数的成本,内联等优化可能会起作用。 Chandler提到同样的问题,鉴于散列函数计算成本昂贵,内联被阻止,最终不止一次计算散列函数!

如果内联发生,则首先计算IR-O3,cache[key],并且cache key根本没有变异,这样的调用将被优化为相同的SSA值。

cache[key].get()的情况下,我们通常会写入IR,因为cache [key]返回对象并使用get()内的getelementpointer获取字段的值。启用优化后,此IR将成为我们先前为'cache [key]`计算的SSA值,其中元素从唯一指针的struct访问。

回到getFooBetter(),在最坏的情况下,如果编译器无法跨程序进行优化,cache[key]的更多次出现将导致更多的计算,即使在O3,此调用也会显示为原样!

答案 1 :(得分:1)

在unordered_map查找中发生了很多事情。有哈希计算,通过bin搜索,如果不存在则添加到bin,如果它现在太大,可能会增加表。与比较你有两个&#34; x + y&#34;的实例不同。在你的代码中。你应该更惊讶它实际上发现了两个调用可以合并。 (我是。)

作为一般规则,我不会指望编译器发现可以共享两个函数调用,并且,当性能很重要时,我会在源代码中自行执行常见的子表达式消除。我甚至不希望它会发现sin(x)是相同的,直到constexpr完全实现。