我正在尝试编写一些代码并说服自己按值传递,按引用传递(rvalue
和lvalue
参考)应该对绩效产生重大影响(related question)。后来我想出了下面的代码,我想性能差异应该是可见的。
#include <iostream>
#include <vector>
#include <chrono>
#define DurationTy std::chrono::duration_cast<std::chrono::milliseconds>
typedef std::vector<int> VectTy;
size_t const MAX = 10000u;
size_t const NUM = MAX / 10;
int randomize(int mod) { return std::rand() % mod; }
VectTy factory(size_t size, bool pos) {
VectTy vect;
if (pos) {
for (size_t i = 0u; i < size; i++) {
// vect.push_back(randomize(size));
vect.push_back(i);
}
} else {
for (size_t i = 0u; i < size * 2; i++) {
vect.push_back(i);
// vect.push_back(randomize(size));
}
}
return vect;
}
long d1(VectTy vect) {
long sum = 0;
for (auto& v : vect) sum += v;
return sum;
}
long d2(VectTy& vect) {
long sum = 0;
for (auto& v : vect) sum += v;
return sum;
}
long d3(VectTy&& vect) {
long sum = 0;
for (auto& v : vect) sum += v;
return sum;
}
int main(void) {
{
auto start = std::chrono::steady_clock::now();
long total = 0;
for (size_t i = 0; i < NUM; ++i) {
total += d1(factory(MAX, i % 2)); // T1
}
auto end = std::chrono::steady_clock::now();
std::cout << total << std::endl;
auto elapsed = DurationTy(end - start);
std::cerr << elapsed.count() << std::endl;
}
{
auto start = std::chrono::steady_clock::now();
long total = 0;
for (size_t i = 0; i < NUM; ++i) {
VectTy vect = factory(MAX, i % 2); // T2
total += d1(vect);
}
auto end = std::chrono::steady_clock::now();
std::cout << total << std::endl;
auto elapsed = DurationTy(end - start);
std::cerr << elapsed.count() << std::endl;
}
{
auto start = std::chrono::steady_clock::now();
long total = 0;
for (size_t i = 0; i < NUM; ++i) {
VectTy vect = factory(MAX, i % 2); // T3
total += d2(vect);
}
auto end = std::chrono::steady_clock::now();
std::cout << total << std::endl;
auto elapsed = DurationTy(end - start);
std::cerr << elapsed.count() << std::endl;
}
{
auto start = std::chrono::steady_clock::now();
long total = 0;
for (size_t i = 0; i < NUM; ++i) {
total += d3(factory(MAX, i % 2)); // T4
}
auto end = std::chrono::steady_clock::now();
std::cout << total << std::endl;
auto elapsed = DurationTy(end - start);
std::cerr << elapsed.count() << std::endl;
}
return 0;
}
我使用gcc
选项在clang
(4.9.2)和-std=c++11
(主干)上对其进行了测试。
但是我发现在使用 clang T2
进行编译时仅需要更多时间(一次运行,以毫秒为单位,755,924,752,750)。我还编译了-fno-elide-constructors
版本,但结果相似。
(更新:在Mac OS X上使用Clang(主干)编译时,T1
,T3
,T4
会有轻微的性能差异。)< / p>
我的问题:
T1
,T2
,T3
之间的潜在性能差距? (你可以看到我也试图在factory
中避免使用RVO。)T2
应用的优化是什么?答案 0 :(得分:1)
这是因为r值引用。你通过值传递std :: vector - 编译器指出它有移动构造函数并优化要移动的副本。
有关rvalue refs的详细信息,请参阅以下链接:http://thbecker.net/articles/rvalue_references/section_01.html
更新: 以下三种方法相当于:
在这里,你直接在函数d1
中返回工厂,编译器知道返回的值是临时的,std::vector (VectTy)
有一个移动构造函数定义 - 它只调用移动构造函数(所以这个功能相当于d3
long d1(VectTy vect) {
long sum = 0;
for (auto& v : vect) sum += v;
return sum;
}
这里你通过引用传递,所以没有copy-OTOH,这不应该编译。除非你使用MSVC-在这种情况下你应该禁用语言扩展
long d2(VectTy& vect) {
long sum = 0;
for (auto& v : vect) sum += v;
return sum;
}
当然这里不会有任何副本,你正在将临时矢量(rvalue)从工厂移到d3
long d3(VectTy&& vect) {
long sum = 0;
for (auto& v : vect) sum += v;
return sum;
}
如果要重现复制性能问题,请尝试推出自己的矢量类:
template<class T>
class MyVector
{
private:
std::vector<T> _vec;
public:
MyVector() : _vec()
{}
MyVector(const MyVector& other) : _vec(other._vec)
{}
MyVector& operator=(const MyVector& other)
{
if(this != &other)
this->_vec = other._vec;
return *this;
}
void push_back(T t)
{
this->_vec.push_back(t);
}
};
并使用此代替std::vector
,您肯定会遇到您正在寻找的性能问题
答案 1 :(得分:0)
如果您尝试从中构建第二个vector<T>
,则vector<T>
类型的右值将被另一个vector<T>
窃取。如果您指定,它可能会被盗,或者其内容可能被移动,或者其他内容(标准中未详细说明)。
从相同的右值类型构造称为移动构造。对于向量,(在大多数实现中)它包括读取3个指针,写入3个指针和清除3个指针。无论载体保存多少数据,这都是一种廉价的操作。
factory
中的任何内容都不会阻止NRVO(一种省略)。无论如何,当您返回一个局部变量(在C ++ 11中与返回值类型完全匹配时,或在C ++ 14中找到兼容的rvalue构造函数)时,如果不发生省略,则将其隐式视为rvalue。因此,factory
中的参数将被返回值省略,或者移动它的内容。成本的不同是微不足道的,然后无论如何都可以优化任何差异。
您的三个功能d1
d2
和d3
应该更好地称为&#34;按值&#34;,&#34; by-lvalue&#34;和&#34; by-rvalue&#34;。
调用L1的返回值为d1
的参数。如果这个省略失败(比如你阻止它),它就变成了一个移动构造,这种构造更加昂贵。
呼叫L2强制复制。
呼叫L3没有副本,L4也没有。
现在,根据as-if规则,如果你可以证明它没有任何副作用,你甚至可以跳过副本(或者更确切地说,如果消除它是标准下的有效变体,可能会发生什么)。 gcc可能正在这样做,这可能解释为什么L2不慢。
无意义任务基准测试的问题在于,如果编译器能够证明任务毫无意义,它可以消除它。
但我对L1 L3和L4是完全相同并不感到惊讶,因为标准要求它们在成本上基本相同,直到几个指针改组。