使用c++ 11的'auto'能提高性能吗?

时间:2022-11-03 07:13:55

I can see why the auto type in C++11 improves correctness and maintainability. I've read that it can also improve performance (Almost Always Auto by Herb Sutter), but I miss a good explanation.

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

  • How can auto improve performance?
  • 汽车如何提高性能?
  • Can anyone give an example?
  • 有人能举个例子吗?

4 个解决方案

#1


286  

auto can aid performance by avoiding silent implicit conversions. An example I find compelling is the following.

auto可以通过避免无声隐式转换来提高性能。我发现一个引人注目的例子如下。

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

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

See the bug? Here we are, thinking we're elegantly taking every item in the map by const reference and using the new range-for expression to make our intent clear, but actually we're copying every element. This is because std::map<Key, Val>::value_type is std::pair<const Key, Val>, not std::pair<Key, Val>. Thus, when we (implicitly) have:

看到虫子了吗?我们在这里,认为我们是优雅地使用const引用地图中的每一项,并使用新的范围-for表达式来明确我们的意图,但实际上我们复制了每个元素。这是因为std::map : value_type是std::对 ,不是std::pair 。因此,当我们(含蓄地)有: ,> ,>

std::pair<Key, Val> const& item = *iter;

Instead of taking a reference to an existing object and leaving it at that, we have to do a type conversion. You are allowed to take a const reference to an object (or temporary) of a different type as long as there is an implicit conversion available, e.g.:

我们必须进行类型转换,而不是对现有对象进行引用并将其置于该对象之上。只要存在隐式转换,您可以引用不同类型的对象(或临时对象),例如:

int const& i = 2.0; // perfectly OK

The type conversion is an allowed implicit conversion for the same reason you can convert a const Key to a Key, but we have to construct a temporary of the new type in order to allow for that. Thus, effectively our loop does:

类型转换是允许的隐式转换,原因与您可以将const键转换为键相同,但是我们必须构造新类型的临时转换,以便实现这一点。因此,我们的循环实际上是:

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

(Of course, there isn't actually a __tmp object, it's just there for illustration, in reality the unnamed temporary is just bound to item for its lifetime).

(当然,实际上并没有一个__tmp对象,它只是用于说明,实际上这个未命名的临时对象只是在其生命周期内绑定到项目)。

Just changing to:

只是改变:

for (auto const& item : m) {
    // do stuff
}

just saved us a ton of copies - now the referenced type matches the initializer type, so no temporary or conversion is necessary, we can just do a direct reference.

刚刚为我们保存了大量的副本——现在引用的类型与初始化类型匹配,所以不需要临时或转换,我们只需要做一个直接引用。

#2


67  

Because auto deduces the type of the initializing expression, there is no type conversion involved. Combined with templated algorithms, this means that you can get a more direct computation than if you were to make up a type yourself – especially when you are dealing with expressions whose type you cannot name!

因为自动推导初始化表达式的类型,所以不涉及类型转换。与模板化算法相结合,这意味着您可以获得比您自己编写类型更直接的计算—尤其是当您处理的表达式类型您不能命名的时候!

A typical example comes from (ab)using std::function:

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

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?);

With cmp2 and cmp3, the entire algorithm can inline the comparison call, whereas if you construct a std::function object, not only can the call not be inlined, but you also have to go through the polymorphic lookup in the type-erased interior of the function wrapper.

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

Another variant on this theme is that you can say:

这个主题的另一个变体是:

auto && f = MakeAThing();

This is always a reference, bound to the value of the function call expression, and never constructs any additional objects. If you didn't know the returned value's type, you might be forced to construct a new object (perhaps as a temporary) via something like T && f = MakeAThing(). (Moreover, auto && even works when the return type is not movable and the return value is a prvalue.)

这始终是一个引用,绑定到函数调用表达式的值,并且从不构造任何其他对象。如果您不知道返回值的类型,您可能会*通过诸如T && & f = MakeAThing()之类的东西来构造一个新对象(可能是临时的)。(另外,auto && even在返回类型不可移动且返回值为prvalue的情况下工作。)

#3


36  

There are two categories.

有两个类别。

auto can avoid type erasure. There are unnamable types (like lambdas), and almost unnamable types (like the result of std::bind or other expression-template like things).

自动可避免类型擦除。有不可名类型(如lambdas)和几乎不可名类型(如std: bind或其他表达式模板之类的结果)。

Without auto, you end up having to type erase the data down to something like std::function. Type erasure has costs.

如果没有auto,您将不得不输入诸如std::function之类的数据。类型擦除成本。

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

task1 has type erasure overhead -- a possible heap allocation, difficulty inlining it, and virtual function table invocation overhead. task2 has none. Lambdas need auto or other forms of type deduction to store without type erasure; other types can be so complex that they only need it in practice.

task1具有类型擦除开销——可能的堆分配、内联困难以及虚拟函数表调用开销。task2没有。Lambdas需要自动或其他形式的类型推导来存储,且不存在类型擦除;其他类型可能非常复杂,它们在实践中只需要它。

Second, you can get types wrong. In some cases, the wrong type will work seemingly perfectly, but will cause a copy.

第二,你可能会弄错类型。在某些情况下,错误的类型似乎可以完美地工作,但是会导致拷贝。

Foo const& f = expression();

will compile if expression() returns Bar const& or Bar or even Bar&, where Foo can be constructed from Bar. A temporary Foo will be created, then bound to f, and its lifetime will be extended until f goes away.

将编译if expression()返回Bar const&or Bar,甚至Bar&,其中Foo可以从Bar构造。一个临时的Foo将被创建,然后绑定到f,它的生存期将被延长,直到f消失。

The programmer may have meant Bar const& f and not intended to make a copy there, but a copy is made regardless.

程序员可能指的是Bar const&f,并不打算在那里进行复制,但无论如何都要进行复制。

The most common example is the type of *std::map<A,B>::const_iterator, which is std::pair<A const, B> const& not std::pair<A,B> const&, but the error is a category of errors that silently cost performance. You can construct a std::pair<A, B> from a std::pair<const A, B>. (The key on a map is const, because editing it is a bad idea)

最常见的例子是*std::map :::const_iterator的类型,它是std:::pair constst & not std::pair const&&,但是错误是一种类型的错误,这种错误会悄无声息地降低性能。可以从std::对< a, B>构造std::对 。(地图上的关键是const,因为编辑它是个坏主意) ,b> ,b>

Both @Barry and @KerrekSB first illustrated these two principles in their answers. This is simply an attempt to highlight the two issues in one answer, with wording that aims at the problem rather than being example-centric.

@Barry和@KerrekSB在他们的回答中首先阐述了这两个原则。这只是试图在一个答案中突出这两个问题,使用针对问题而不是以示例为中心的措辞。

#4


7  

The existing three answers give examples where using auto helps “makes it less likely to unintentionally pessimize” effectively making it "improve performance".

现有的三个答案给出了使用自动帮助使它更不容易无意地悲观的例子,有效地使它“提高性能”。

There is a flip side to the the coin. Using auto with objects that have operators that don't return the basic object can result in incorrect (still compilable and runable) code. For example, this question asks how using auto gave different (incorrect) results using the Eigen library, i.e. the following lines

硬币有另一面。使用auto与具有不返回基本对象的操作符的对象一起使用会导致不正确的(仍然可编译和可运行的)代码。例如,这个问题问如何使用自动生成不同(不正确)的结果,使用Eigen库,也就是下面几行

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;

resulted in different output. Admittedly, this is mostly due to Eigens lazy evaluation, but that code is/should be transparent to the (library) user.

导致不同的输出。无可否认,这主要是由于Eigens延迟评估,但该代码/应该对(库)用户透明。

While performance hasn't been greatly affected here, using auto to avoid unintentional pessimization might be classified as premature optimization, or at least wrong ;).

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

#1


286  

auto can aid performance by avoiding silent implicit conversions. An example I find compelling is the following.

auto可以通过避免无声隐式转换来提高性能。我发现一个引人注目的例子如下。

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

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

See the bug? Here we are, thinking we're elegantly taking every item in the map by const reference and using the new range-for expression to make our intent clear, but actually we're copying every element. This is because std::map<Key, Val>::value_type is std::pair<const Key, Val>, not std::pair<Key, Val>. Thus, when we (implicitly) have:

看到虫子了吗?我们在这里,认为我们是优雅地使用const引用地图中的每一项,并使用新的范围-for表达式来明确我们的意图,但实际上我们复制了每个元素。这是因为std::map : value_type是std::对 ,不是std::pair 。因此,当我们(含蓄地)有: ,> ,>

std::pair<Key, Val> const& item = *iter;

Instead of taking a reference to an existing object and leaving it at that, we have to do a type conversion. You are allowed to take a const reference to an object (or temporary) of a different type as long as there is an implicit conversion available, e.g.:

我们必须进行类型转换,而不是对现有对象进行引用并将其置于该对象之上。只要存在隐式转换,您可以引用不同类型的对象(或临时对象),例如:

int const& i = 2.0; // perfectly OK

The type conversion is an allowed implicit conversion for the same reason you can convert a const Key to a Key, but we have to construct a temporary of the new type in order to allow for that. Thus, effectively our loop does:

类型转换是允许的隐式转换,原因与您可以将const键转换为键相同,但是我们必须构造新类型的临时转换,以便实现这一点。因此,我们的循环实际上是:

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

(Of course, there isn't actually a __tmp object, it's just there for illustration, in reality the unnamed temporary is just bound to item for its lifetime).

(当然,实际上并没有一个__tmp对象,它只是用于说明,实际上这个未命名的临时对象只是在其生命周期内绑定到项目)。

Just changing to:

只是改变:

for (auto const& item : m) {
    // do stuff
}

just saved us a ton of copies - now the referenced type matches the initializer type, so no temporary or conversion is necessary, we can just do a direct reference.

刚刚为我们保存了大量的副本——现在引用的类型与初始化类型匹配,所以不需要临时或转换,我们只需要做一个直接引用。

#2


67  

Because auto deduces the type of the initializing expression, there is no type conversion involved. Combined with templated algorithms, this means that you can get a more direct computation than if you were to make up a type yourself – especially when you are dealing with expressions whose type you cannot name!

因为自动推导初始化表达式的类型,所以不涉及类型转换。与模板化算法相结合,这意味着您可以获得比您自己编写类型更直接的计算—尤其是当您处理的表达式类型您不能命名的时候!

A typical example comes from (ab)using std::function:

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

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?);

With cmp2 and cmp3, the entire algorithm can inline the comparison call, whereas if you construct a std::function object, not only can the call not be inlined, but you also have to go through the polymorphic lookup in the type-erased interior of the function wrapper.

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

Another variant on this theme is that you can say:

这个主题的另一个变体是:

auto && f = MakeAThing();

This is always a reference, bound to the value of the function call expression, and never constructs any additional objects. If you didn't know the returned value's type, you might be forced to construct a new object (perhaps as a temporary) via something like T && f = MakeAThing(). (Moreover, auto && even works when the return type is not movable and the return value is a prvalue.)

这始终是一个引用,绑定到函数调用表达式的值,并且从不构造任何其他对象。如果您不知道返回值的类型,您可能会*通过诸如T && & f = MakeAThing()之类的东西来构造一个新对象(可能是临时的)。(另外,auto && even在返回类型不可移动且返回值为prvalue的情况下工作。)

#3


36  

There are two categories.

有两个类别。

auto can avoid type erasure. There are unnamable types (like lambdas), and almost unnamable types (like the result of std::bind or other expression-template like things).

自动可避免类型擦除。有不可名类型(如lambdas)和几乎不可名类型(如std: bind或其他表达式模板之类的结果)。

Without auto, you end up having to type erase the data down to something like std::function. Type erasure has costs.

如果没有auto,您将不得不输入诸如std::function之类的数据。类型擦除成本。

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

task1 has type erasure overhead -- a possible heap allocation, difficulty inlining it, and virtual function table invocation overhead. task2 has none. Lambdas need auto or other forms of type deduction to store without type erasure; other types can be so complex that they only need it in practice.

task1具有类型擦除开销——可能的堆分配、内联困难以及虚拟函数表调用开销。task2没有。Lambdas需要自动或其他形式的类型推导来存储,且不存在类型擦除;其他类型可能非常复杂,它们在实践中只需要它。

Second, you can get types wrong. In some cases, the wrong type will work seemingly perfectly, but will cause a copy.

第二,你可能会弄错类型。在某些情况下,错误的类型似乎可以完美地工作,但是会导致拷贝。

Foo const& f = expression();

will compile if expression() returns Bar const& or Bar or even Bar&, where Foo can be constructed from Bar. A temporary Foo will be created, then bound to f, and its lifetime will be extended until f goes away.

将编译if expression()返回Bar const&or Bar,甚至Bar&,其中Foo可以从Bar构造。一个临时的Foo将被创建,然后绑定到f,它的生存期将被延长,直到f消失。

The programmer may have meant Bar const& f and not intended to make a copy there, but a copy is made regardless.

程序员可能指的是Bar const&f,并不打算在那里进行复制,但无论如何都要进行复制。

The most common example is the type of *std::map<A,B>::const_iterator, which is std::pair<A const, B> const& not std::pair<A,B> const&, but the error is a category of errors that silently cost performance. You can construct a std::pair<A, B> from a std::pair<const A, B>. (The key on a map is const, because editing it is a bad idea)

最常见的例子是*std::map :::const_iterator的类型,它是std:::pair constst & not std::pair const&&,但是错误是一种类型的错误,这种错误会悄无声息地降低性能。可以从std::对< a, B>构造std::对 。(地图上的关键是const,因为编辑它是个坏主意) ,b> ,b>

Both @Barry and @KerrekSB first illustrated these two principles in their answers. This is simply an attempt to highlight the two issues in one answer, with wording that aims at the problem rather than being example-centric.

@Barry和@KerrekSB在他们的回答中首先阐述了这两个原则。这只是试图在一个答案中突出这两个问题,使用针对问题而不是以示例为中心的措辞。

#4


7  

The existing three answers give examples where using auto helps “makes it less likely to unintentionally pessimize” effectively making it "improve performance".

现有的三个答案给出了使用自动帮助使它更不容易无意地悲观的例子,有效地使它“提高性能”。

There is a flip side to the the coin. Using auto with objects that have operators that don't return the basic object can result in incorrect (still compilable and runable) code. For example, this question asks how using auto gave different (incorrect) results using the Eigen library, i.e. the following lines

硬币有另一面。使用auto与具有不返回基本对象的操作符的对象一起使用会导致不正确的(仍然可编译和可运行的)代码。例如,这个问题问如何使用自动生成不同(不正确)的结果,使用Eigen库,也就是下面几行

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;

resulted in different output. Admittedly, this is mostly due to Eigens lazy evaluation, but that code is/should be transparent to the (library) user.

导致不同的输出。无可否认,这主要是由于Eigens延迟评估,但该代码/应该对(库)用户透明。

While performance hasn't been greatly affected here, using auto to avoid unintentional pessimization might be classified as premature optimization, or at least wrong ;).

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