1
73
|
2
37
不 为了提高效率。这是一个糟糕的想法,因为所讨论的函数通常应该作为一个通过迭代器生成其输出的通用算法来编写。几乎任何接受或返回容器而不是在迭代器上操作的代码都应该被视为可疑代码。 不要误解我的意思:有时传递像对象(例如字符串)这样的集合是有意义的,但是对于引用的示例,我认为传递或返回向量是个糟糕的主意。 |
3
18
要点是: 复制省略和RVO 可以 避免“可怕的副本”(编译器不需要实现这些优化,在某些情况下它不能被应用) 允许 保证 那个。 如果您可以放弃旧的编译器/STL实现,那么可以自由地返回向量(并确保您自己的对象也支持它)。如果您的代码库需要支持“较少”的编译器,请坚持使用旧样式。 不幸的是,这对您的接口有很大的影响。如果C++ 0x不是选项,并且需要保证,那么在某些场景中,可以使用引用计数或复制写对象。不过,多线程也有缺点。 (我希望C++中的一个答案简单明了,没有条件)。 |
4
13
事实上,由于C++ 11,成本
这个
构造 自毁 它)仍然存在,当您希望重用向量的容量时,使用输出参数而不是按值返回仍然很有用。这在中被记录为一个例外 F.20 的C++核心指南。 让我们比较一下:
使用:
现在,假设我们需要调用这些方法
使用
使用
在第一个例子中,有许多不必要的动态分配/取消分配发生,在第二个例子中,通过使用输出参数旧的方式,重用已经分配的内存来防止这些发生。这个优化是否值得做取决于分配/取消分配的相对成本与计算/改变值的成本相比。 基准
让我们来玩玩
更具体地说,让我们使用vecSize*numIter=2^31=2147483648,因为我有16GB的RAM,这个数字确保分配的内存不超过8GB(sizeof(int)=4),确保我没有交换到磁盘(所有其他程序都已关闭,运行测试时我有~15GB可用)。 代码如下:
表示法:mem(v)=v.size()*sizeof(int)=v.size()*4在我的平台上。
毫不奇怪,当
什么时候?
我们可以注意到time1从
从
什么时候?
请注意time2的曲线如何比time1的曲线更平滑:不仅重新使用向量容量通常更快,而且可能更重要的是,它更平滑 . 另外请注意,在最佳情况下,我们能够在0.5秒内执行20亿个64位整数的加法,这在4.2Ghz 64位处理器上是非常理想的。我们可以通过并行化计算来更好地使用所有8个核(上面的测试一次只使用一个核,我通过在监视CPU使用情况的同时重新运行测试来验证这一点)。当mem(v)=16kB时达到最佳性能,这是一级缓存的数量级(i7-7700K的一级数据缓存为4x32kB)。
当然,实际需要对数据进行的计算越多,差异的相关性就越小。下面是我们替换的结果
其他平台上的结果可能不同。像往常一样,如果性能很重要,请为您的特定用例编写基准测试。 |
5
6
以前,使用MSVC 2008的vtune中显示的许多热点都归结为字符串复制。我们有这样的代码:
... 请注意,我们使用了自己的字符串类型(这是必需的,因为我们提供了一个软件开发工具包,插件编写器可以使用不同的编译器,因此可以使用不同的、不兼容的std::String/std::wstring实现)。 我对显示String::String(const String&的调用图采样分析会话做了一个简单的更改占用大量时间。上面示例中的方法是最大的贡献者(实际上,分析会话显示内存分配和释放是最大的热点之一,而字符串复制构造函数是分配的主要贡献者)。 我做的改变很简单:
然而,这让世界变得不同了!热点在随后的profiler会话中消失了,除此之外,我们还做了大量彻底的单元测试来跟踪我们的应用程序性能。在这些简单的更改之后,各种性能测试时间都显著减少。 结论:我们并没有使用绝对最新的编译器,但我们似乎仍然不能依赖编译器优化复制以可靠地返回值(至少不是在所有情况下)。对于那些使用像MSVC 2010这样的新编译器的情况可能不是这样。我期待着当我们可以使用C++ 0x并简单地使用R值引用,而不必担心我们通过值返回复杂的类来对代码进行悲观。 [编辑] 正如Nate指出的,RVO适用于返回函数内部创建的临时变量。在我的例子中,没有这样的临时变量(除了构造空字符串的无效分支),因此RVO不适用。 |
6
3
|
7
2
如果性能是一个真正的问题,那么您应该意识到移动语义并不是一个真正的问题 比复制更快。例如,如果有一个字符串使用 小串优化 |