使用C ++ 11的“自动”可以提高性能吗?
我可以看到为什么C ++ 11中的auto
types提高了正确性和可维护性。 我读过,它也可以提高性能( 几乎总是自动通过香草萨特),但我想念一个很好的解释。
-
auto
如何提高性能? - 任何人都可以举个例子吗?
auto
可以通过避免静默隐式转换来帮助性能。 我发现一个令人信服的例子如下。
std::map<Key, Val> m; // ... for (std::pair<Key, Val> const& item : m) { // do stuff }
看到错误? 在这里,我们认为我们正在通过const引用来优雅地获取地图中的每个项目,并使用新的range-forexpression式来使我们的意图清晰,但实际上我们正在复制每个元素。 这是因为std::map<Key, Val>::value_type
是std::pair<const Key, Val>
,而不是std::pair<Key, Val>
。 因此,当我们(隐含地)有:
std::pair<Key, Val> const& item = *iter;
而不是参考一个现有的对象,并把它留在那,我们必须做一个types转换。 只要有可用的隐式转换,就可以对一个不同types的对象(或临时对象)进行const引用,例如:
int const& i = 2.0; // perfectly OK
types转换是一个允许的隐式转换,因为你可以将一个const Key
转换成一个Key
,但是我们必须构造一个临时的新types来允许这个。 因此,我们的循环有效:
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
(当然,实际上并没有__tmp
对象,只是为了说明,实际上这个未命名的临时对象只是在其生命周期中绑定的)。
只要改成:
for (auto const& item : m) { // do stuff }
只是为我们节省了大量的拷贝 – 现在引用的types与初始化types相匹配,所以不需要临时或转换,我们可以直接引用。
因为auto
推导初始化expression式的types,所以不涉及types转换。 结合模板化的algorithm,这意味着您可以得到更直接的计算,比您自己编写一个types更为直接 – 尤其是在处理不能指定types的expression式时!
一个典型的例子来自(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?);
使用cmp2
和cmp3
,整个algorithm可以内联比较调用,而如果构buildstd::function
对象,则不仅可以调用内联,还必须在types擦除的内部中进行多态查找的函数包装器。
这个主题的另一个变种是你可以说:
auto && f = MakeAThing();
这总是一个引用,绑定到函数调用expression式的值,并且永远不会构造任何附加对象。 如果你不知道返回值的types,你可能会被迫通过像T && f = MakeAThing()
这样的东西来构造一个新的对象(可能是临时的T && f = MakeAThing()
。 (而且, auto &&
甚至在返回types不可移动且返回值为prvalue时起作用。
有两个类别。
auto
可以避免types擦除。 有不可思议的types(如lambdas)和几乎不可能的types(如std::bind
或其他expression式模板的结果)。
没有auto
,你最终不得不键入删除数据类似std::function
。 types擦除有成本。
std::function<void()> task1 = []{std::cout << "hello";}; auto task2 = []{std::cout << " world\n";};
task1
具有types擦除开销 – 可能的堆分配,内联难度以及虚拟函数表调用开销。 task2
没有。 兰姆达斯需要自动或其他forms的types扣除存储没有types擦除; 其他types可能如此复杂以至于实际上只需要它。
其次,你可以得到错误的types。 在某些情况下,错误的types看起来是完美的,但会造成副本。
Foo const& f = expression();
将编译如果expression()
返回Bar const&
或Bar
,甚至Bar&
,其中Foo
可以从Bar
构build。 一个临时的Foo
将被创build,然后绑定到f
,并且其生命周期将被延长直到f
消失。
程序员可能意味着Bar const& f
,并不打算在那里创build一个副本,但不pipe副本是什么。
最常见的例子是*std::map<A,B>::const_iterator
,它是std::pair<A const, B> const&
不是std::pair<A,B> const&
,但是错误是一种默默地降低性能的错误类别。 你可以从std::pair<const A, B>
构造一个std::pair<A, B>
std::pair<const A, B>
。 (地图上的键是常量,因为编辑它是一个坏主意)
@Barry和@KerrekSB首先在他们的答案中说明了这两个原则。 这只是试图在一个答案中强调两个问题,其目的在于解决问题而不是以示例为中心。
现有的三个答案举例说明了使用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;
导致了不同的产出。 诚然,这主要是由于Eigens惰性评估,但该代码是/应该是(库)用户透明。
虽然性能在这里没有受到很大的影响,但使用auto
来避免无意的pessimization可能被归类为不成熟的优化,或者至less是错误的)。