为什么积累比简单的循环更快?

时间:2012-11-06 01:54:23

标签: c++ performance

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;
}

3 个答案:

答案 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,我将sumastd::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次,否则生成的代码几乎完全相同。