当std::accumulate
比简单的for
周期更快时,我正在测试算法并遇到这种奇怪的行为。
查看生成的汇编程序我并不是更明智:-)似乎for
周期优化为MMX指令,而累积则扩展为循环。
这是代码。该行为以-O3
优化级别gcc 4.7.1
#include <vector>
#include <chrono>
#include <iostream>
#include <random>
#include <algorithm>
using namespace std;
int main()
{
const size_t vsize = 100*1000*1000;
vector<int> x;
x.reserve(vsize);
mt19937 rng;
rng.seed(chrono::system_clock::to_time_t(chrono::system_clock::now()));
uniform_int_distribution<uint32_t> dist(0,10);
for (size_t i = 0; i < vsize; i++)
{
x.push_back(dist(rng));
}
long long tmp = 0;
for (size_t i = 0; i < vsize; i++)
{
tmp += x[i];
}
cout << "dry run " << tmp << endl;
auto start = chrono::high_resolution_clock::now();
long long suma = accumulate(x.begin(),x.end(),0);
auto end = chrono::high_resolution_clock::now();
cout << "Accumulate runtime " << chrono::duration_cast<chrono::nanoseconds>(end-start).count() << " - " << suma << endl;
start = chrono::high_resolution_clock::now();
suma = 0;
for (size_t i = 0; i < vsize; i++)
{
suma += x[i];
}
end = chrono::high_resolution_clock::now();
cout << "Manual sum runtime " << chrono::duration_cast<chrono::nanoseconds>(end-start).count() << " - " << suma << endl;
return 0;
}
答案 0 :(得分:9)
当你将0
传递给累积时,你会使用int而不是long long来累积它。
如果您像这样编写手动循环,它将是等效的:
int sumb = 0;
for (size_t i = 0; i < vsize; i++)
{
sumb += x[i];
}
suma = sumb;
或者您可以像这样调用累积:
long long suma = accumulate(x.begin(),x.end(),0LL);
答案 1 :(得分:6)
使用Visual Studio 2012我有一些不同的结果
// original code
Accumulate runtime 93600 ms
Manual sum runtime 140400 ms
请注意,原始std::accumulate
代码不等同于for
循环,因为std::accumulate
的第三个参数是int
0值。它使用int
执行求和,并且仅在结尾处将结果存储在long long
中。将第三个参数更改为0LL
会强制算法使用long long
累加器,并导致以下时间。
// change std::accumulate initial value -> 0LL
Accumulate runtime 265200 ms
Manual sum runtime 140400 ms
由于最终结果符合int
,我将suma
和std::accumulate
更改为仅使用int
值。在此更改之后,MSVC 2012编译器能够自动向量化 for
循环,并导致以下时间。
// change suma from long long to int
Accumulate runtime 93600 ms
Manual sum runtime 46800 ms
答案 2 :(得分:2)
在修复累积问题后,其他人注意到我使用Visual Studio 2008&amp; 2010年累积确实比手动循环更快。
看看反汇编我看到一些额外的迭代器检查在手动循环中完成,所以我切换到一个原始数组来消除它。
以下是我最终测试的内容:
#include <Windows.h>
#include <iostream>
#include <numeric>
#include <stdlib.h>
int main()
{
const size_t vsize = 100*1000*1000;
int* x = new int[vsize];
for (size_t i = 0; i < vsize; i++) x[i] = rand() % 1000;
LARGE_INTEGER start,stop;
long long suma = 0, sumb = 0, timea = 0, timeb = 0;
QueryPerformanceCounter( &start );
suma = std::accumulate(x, x + vsize, 0LL);
QueryPerformanceCounter( &stop );
timea = stop.QuadPart - start.QuadPart;
QueryPerformanceCounter( &start );
for (size_t i = 0; i < vsize; ++i) sumb += x[i];
QueryPerformanceCounter( &stop );
timeb = stop.QuadPart - start.QuadPart;
std::cout << "Accumulate: " << timea << " - " << suma << std::endl;
std::cout << " Loop: " << timeb << " - " << sumb << std::endl;
delete [] x;
return 0;
}
Accumulate: 633942 - 49678806711
Loop: 292642 - 49678806711
使用此代码,手动循环很容易累积。最大的区别是编译器将手动循环展开4次,否则生成的代码几乎完全相同。