问题描述
我想了解编译器可以执行的具体优化(如果有的话)。
两个 get 函数 get() 通过引用返回一个字符串,另一个通过值返回。地图本质上是全球性的。
我想理解这一点,因为它是常见的事情,所以如果我们有对象映射或映射映射或非常大的字符串,会发生什么(不将我的映射仅限于字符串)。它可以得到多少代价。我可以理解我们大多数人都知道它依赖于编译器,但是我们可以有一个未知数列表。真的会有帮助
std::map<std::string,std::string> value;
std::string& get(std::string& key) {
return value[key];
}
std::string get2(std::string& key) {
return value[key];
}
int main()
{
value.insert(std::make_pair("name","XXXXXXX"));
std::string keyaa = "name";
auto new_val = get(keyaa);
auto new_val2 = get2(keyaa);
}
解决方法
从 C++ 语言的角度来看,不能保证 get2
中的副本将被省略。强制 return value optimization 仅涵盖 prvalue 操作数(即在函数调用本身中创建的值)。即使是“允许的”优化也不包括预先存在的对象。
所以我们只能希望今天的编译器足够聪明来优化副本,这意味着我们必须对其进行测试!
我稍微重写了示例,以使编译器最容易优化字符串:
#include <string>
#include <map>
struct Test {
std::map<std::string,std::string> value = {{"name","XXXXXX"}};
std::string const& get(std::string const& key) {
return value[key];
}
std::string get2(std::string const& key) {
return value[key];
}
};
static void TestReturnByReference(benchmark::State& state) {
Test test;
std::string key = "name";
for (auto _ : state) {
size_t n = test.get(key).size();
benchmark::DoNotOptimize(n);
}
}
BENCHMARK(TestReturnByReference);
static void TestReturnByValue(benchmark::State& state) {
Test test;
std::string key = "name";
for (auto _ : state) {
size_t n = test.get2(key).size();
benchmark::DoNotOptimize(n);
}
}
BENCHMARK(TestReturnByValue);
不,事实证明,无论 GCC 还是 Clang 都能够完全优化它:
GCC 10.2,-O3: (link to quick-bench) - 显着差异:
Clang 11 (libc++),-O3: - 更好,但仍然较慢:
结论:通过引用返回现有字符串更快。
注意:从 C++17 开始,您可以返回 std::string_view
以避免担心:
std::string_view get(std::string const& key) {
return value[key];
}