可以使用C++ 11的'auto'来提高性能吗?

DaB*_*ain 225 c++ performance auto c++11

我可以看到为什么autoC++ 11中的类型提高了正确性和可维护性.我读过它也可以提高性能(Herb Sutter 几乎总是自动),但我错过了一个很好的解释.

  • 如何auto提高性能?
  • 谁能举个例子?

Bar*_*rry 304

auto可以通过避免静默隐式转换来提高性能.我发现一个令人信服的例子如下.

std::map<Key, Val> m;
// ...

for (std::pair<Key, Val> const& item : m) {
    // do stuff
}
Run Code Online (Sandbox Code Playgroud)

看到这个bug?在这里,我们认为我们优雅地通过const引用获取地图中的每个项目并使用新的range-for表达式来明确我们的意图,但实际上我们正在复制每个元素.这是因为std::map<Key, Val>::value_typestd::pair<const Key, Val>,没有std::pair<Key, Val>.因此,当我们(隐含地)具有:

std::pair<Key, Val> const& item = *iter;
Run Code Online (Sandbox Code Playgroud)

我们必须进行类型转换,而不是引用现有对象并将其留在那里.只要存在可用的隐式转换,就可以对不同类型的对象(或临时)进行const引用,例如:

int const& i = 2.0; // perfectly OK
Run Code Online (Sandbox Code Playgroud)

该类型转换是可以转换一个同样的原因被允许的隐式转换const KeyKey,但我们必须建立一个临时的新型的,以允许这一点.因此,我们的循环有效:

std::pair<Key, Val> __tmp = *iter;       // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it
Run Code Online (Sandbox Code Playgroud)

(当然,实际上并没有一个__tmp对象,它只是用于说明,实际上未命名的临时item用于它的生命周期).

只需改为:

for (auto const& item : m) {
    // do stuff
}
Run Code Online (Sandbox Code Playgroud)

刚刚保存了大量的副本 - 现在引用的类型与初始化程序类型匹配,因此不需要临时或转换,我们可以直接引用.

  • 我仍然认为这不是"自动"提高性能的证明.这只是一个例子,"`auto`有助于防止破坏性能的程序员错误".我认为两者之间存在着微妙但重要的区别.仍然是+1. (35认同)
  • @Barry你能解释为什么编译器会愉快地制作副本而不是抱怨试图将`std :: pair <const Key,Val> const&`视为`std :: pair <Key,Val> const&`?C++ 11的新手,不知道range-for和`auto`是如何发挥作用的. (18认同)

Ker*_* SB 69

因为auto推导出初始化表达式的类型,所以不涉及类型转换.结合模板化算法,这意味着您可以获得比自己构成类型更直接的计算 - 特别是在处理类型无法命名的表达式时!

一个典型的例子来自(ab)使用std::function:

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1);  // bad
auto cmp2 = std::bind(f, _2, 10, _1);                       // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); };            // also good

std::stable_partition(begin(x), end(x), cmp?);
Run Code Online (Sandbox Code Playgroud)

使用cmp2cmp3,整个算法可以内联比较调用,而如果你构造一个std::function对象,不仅不能内联调用,而且你还必须在函数包装器的类型擦除内部中进行多态查找.

这个主题的另一个变体是你可以说:

auto && f = MakeAThing();
Run Code Online (Sandbox Code Playgroud)

这始终是一个引用,绑定到函数调用表达式的值,并且永远不会构造任何其他对象.如果您不知道返回值的类型,您可能会被迫通过类似的东西构造一个新对象(可能是一个临时对象)T && f = MakeAThing().(此外,auto &&即使返回类型不可移动且返回值为prvalue ,也能正常工作.)

  • "不仅可以不打电话" - 那为什么呢?你的意思是,如果`std :: bind`,`std :: function`和`std :: stable_partition`的相关特化都被内联了,那么原则上某些东西会阻止调用在数据流分析后被虚拟化?或者只是在实践中没有C++编译器会积极地内联以解决混乱? (2认同)

Yak*_*ont 41

有两类.

auto可以避免类型擦除.有不可思议的类型(如lambda)和几乎不可知的类型(如结果std::bind或其他表达模板之类的东西).

如果没有auto,您最终必须将数据类型删除到类似的东西std::function.类型擦除有成本.

std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};
Run Code Online (Sandbox Code Playgroud)

task1具有类型擦除开销 - 可能的堆分配,难以内联它,以及虚函数表调用开销. task2没有.Lambdas 需要自动或其他形式的类型扣除来存储而不需要类型擦除; 其他类型可能非常复杂,以至于他们在实践中只需要它.

其次,你可以得到错误的类型.在某些情况下,错误的类型看似完美,但会产生副本.

Foo const& f = expression();
Run Code Online (Sandbox Code Playgroud)

如果expression()返回Bar const&Bar甚至可以从中构造Bar&,将编译.临时将被创建,然后被绑定,并且它的生命周期将被延长直到消失.FooBarFooff

程序员可能意味着Bar const& f而不打算在那里制作副本,但无论如何都要制作副本.

最常见的例子就是类型*std::map<A,B>::const_iterator,这是std::pair<A const, B> const&没有std::pair<A,B> const&,但错误是错误是悄无声息的性能价格比的一类.你可以构建std::pair<A, B>一个std::pair<const A, B>.(地图上的键是const,因为编辑它是一个坏主意)

@Barry和@KerrekSB都在他们的答案中首次阐述了这两个原则.这只是尝试在一个答案中突出这两个问题,其中的措辞旨在解决问题而不是以实例为中心.


Avi*_*urg 7

现有的三个答案给出了一些例子,其中使用auto帮助"使其不太可能无意中感到悲观",从而有效地使其"提高绩效".

这枚硬币还有另一面.使用auto具有不返回基本对象的运算符的对象可能导致不正确(仍可编译和可运行)代码.例如,这个问题询问如何使用auto特征库给出不同的(不正确的)结果,以下行

const auto    resAuto    = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);

std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;
Run Code Online (Sandbox Code Playgroud)

导致不同的输出.不可否认,这主要是由于Eigens懒惰评估,但该代码对于(库)用户是透明的.

虽然这里的性能没有受到太大影响,但是使用auto以避免无意的悲观可能被归类为过早优化,或者至少是错误的;).