C++ 为什么这个程序片段的自动矢量化版本比简单版本慢

C++ 为什么这个程序片段的自动矢量化版本比简单版本慢,c++,performance,visual-c++,vectorization,C++,Performance,Visual C++,Vectorization,在一个更大的数值计算中,我必须完成一个简单的任务,将两个向量的元素的乘积求和。由于这项任务需要经常完成,我尝试利用编译器(VC2015)的自动矢量化功能。我引入了一个临时向量,其中乘积保存在第一个循环中,然后在第二个循环中执行求和。优化设置为完整,首选快速代码。这样,编译器对第一个循环进行了矢量化(我从编译器输出中知道这一点) 结果令人惊讶。矢量化代码在我的机器(core i5-4570 3.20 GHz)上的执行速度比简单代码慢3倍。有人能解释一下为什么以及什么可以提高性能吗?我已经将两个版本

在一个更大的数值计算中,我必须完成一个简单的任务,将两个向量的元素的乘积求和。由于这项任务需要经常完成,我尝试利用编译器(VC2015)的自动矢量化功能。我引入了一个临时向量,其中乘积保存在第一个循环中,然后在第二个循环中执行求和。优化设置为完整,首选快速代码。这样,编译器对第一个循环进行了矢量化(我从编译器输出中知道这一点)

结果令人惊讶。矢量化代码在我的机器(core i5-4570 3.20 GHz)上的执行速度比简单代码慢3倍。有人能解释一下为什么以及什么可以提高性能吗?我已经将两个版本的算法片段放入了一个最小的运行示例中,我使用自己进行了测试:

#include "stdafx.h"
#include <vector>
#include <Windows.h>
#include <iostream>

using namespace std;

int main()
{
    // Prepare timer
    LARGE_INTEGER freq,c_start,c_stop;
    QueryPerformanceFrequency(&freq);

    int size = 20000000; // size of data
    double v = 0;

    // Some data vectors. The data inside doesn't matter
    vector<double> vv(size);
    vector<double> tt(size);
    vector<float> dd(size);

    // Put random values into the vectors
    for (int i = 0; i < size; i++)
    {
        tt[i] = rand();
        dd[i] = rand();
    }

    // The simple version of the algorithm fragment
    QueryPerformanceCounter(&c_start); // start timer
    for (int p = 0; p < size; p++)
    {
        v += tt[p] * dd[p];
    }
    QueryPerformanceCounter(&c_stop); // Stop timer

    cout << "Simple version took: " << ((double)(c_stop.QuadPart - c_start.QuadPart)) / ((double)freq.QuadPart) << " s" << endl;
    cout << v << endl; // We use v once. This avoids its calculation to be optimized away.

    // The version that is auto-vectorized

    for (int i = 0; i < size; i++)
    {
        tt[i] = rand();
        dd[i] = rand();
    }

    v = 0;
    QueryPerformanceCounter(&c_start); // start timer
    for (int p = 0; p < size; p++) // This loop is vectorized according to compiler output
    {
        vv[p] = tt[p] * dd[p];
    }
    for (int p = 0; p < size; p++)
    {
        v += vv[p];
    }
    QueryPerformanceCounter(&c_stop); // Stop timer

    cout << "Vectorized version took: " << ((double)(c_stop.QuadPart - c_start.QuadPart)) / ((double)freq.QuadPart) << " s" << endl;
    cout << v << endl; // We use v once. This avoids its calculation to be optimized away.

    cin.ignore();

    return 0;
}
#包括“stdafx.h”
#包括
#包括
#包括
使用名称空间std;
int main()
{
//准备计时器
大整数频率,c_启动,c_停止;
QueryPerformanceFrequency(&freq);
int size=20000000;//数据大小
双v=0;
//一些数据向量。里面的数据无关紧要
向量vv(大小);
向量tt(大小);
向量dd(大小);
//将随机值放入向量中
对于(int i=0;icout通过将产品存储在临时向量中,您添加了大量工作

对于如此简单的大数据计算,通过矢量化节省的CPU时间并不重要,重要的只是内存引用

您添加了内存引用,因此运行速度较慢

我本以为编译器会优化该循环的原始版本。我怀疑优化会影响执行时间(因为不管如何,它都是由内存访问控制的)。但它应该在生成的代码中可见。如果您想手动优化这样的代码,那么使用临时向量总是错误的。正确的方向如下(为简单起见,我假设
size
为偶数):

for(int p=0;p
请注意,您的数据足够大,操作也足够简单,NO优化应该能够在最简单的版本上进行改进。这包括我的手动优化示例。但我假设您的测试并不完全代表您真正想做或理解的内容。因此,使用较小的数据或更复杂的手术,我展示的方法可能会有所帮助


另外请注意,我的版本依赖于加法是可交换的。对于实数,加法是可交换的。但在浮点运算中,它不是。答案可能会有太小的差异,您不必在意。但这取决于数据。如果在ori的早期,在奇数/偶数位置有较大的相反符号值,则会相互抵消基纳序列,然后通过分离偶数和奇数位置,我的“优化”将彻底破坏答案。(当然,相反的情况也可能是这样。例如,如果所有偶数位置都很小,并且赔率中包含了相互抵消的大值,那么原始序列会产生垃圾,而更改后的序列会更正确).

如果矢量化可以实现,它可以使用多个累加器。但是,是的,多个累加器将产生与通常的自动矢量化略有不同的舍入。太糟糕了,编译器不擅长使用多个累加器,因为它们对FP硬件饱和至关重要。例如,Intel Haswell有5c延迟FMA,带on因此,您需要10个向量累加器来保持10个FMA在飞行中饱和其FMA单元。您的版本可能允许编译器使用一个累加器进行向量化,即使没有
-ffast math
,但是(对于双精度,一个向量包含两个64b双精度)。
for (int p = 0; p < size; p+=2)
{
    v += tt[p] * dd[p];
    v1 += tt[p+1] * dd[p+1];
}
v += v1;