我正在看一个演讲,"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。)
答案 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完全实现。