【问题标题】:Why is the auto-vectorized version of this program fragment slower than the simple version为什么这个程序片段的自动矢量化版本比简单版本慢
【发布时间】:2016-01-12 10:56:14
【问题描述】:

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

结果令人惊讶。矢量化代码在我的机器(核心 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;
}

【问题讨论】:

    标签: c++ performance visual-c++ vectorization


    【解决方案1】:

    您通过将产品存储在临时向量中增加了大量工作。

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

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

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

    for (int p = 0; p < size; p+=2)
    {
        v += tt[p] * dd[p];
        v1 += tt[p+1] * dd[p+1];
    }
    v += v1;
    

    请注意,您的数据足够大,操作也足够简单,NO优化应该能够在最简单的版本上进行改进。这包括我的样本手优化。但我认为你的测试并不完全代表你真正想要做或理解的事情。因此,对于较小的数据或更复杂的操作,我展示的方法可能会有所帮助。

    另请注意,我的版本依赖于可交换的加法。对于实数,加法是可交换的。但在浮点数中,它不是。答案可能会因您无法关心的差异而有所不同。但这取决于数据。如果您在原始序列的早期在奇数/偶数位置有较大的相反符号值相互抵消,那么通过分离偶数和奇数位置,我的“优化”将完全破坏答案。 (当然,反之亦然。例如,如果所有偶数位置都很小,而赔率包括大值相互抵消,那么原始序列产生垃圾,更改后的序列会更正确)。

    【讨论】:

    • 如果完全可以进行矢量化,它可以使用多个累加器。但是,是的,多个累加器会产生与通常的自动矢量化略有不同的舍入。太糟糕了,编译器不擅长使用多个累加器,因为它们对于使 FP 硬件饱和至关重要。例如Intel Haswell 具有 5c 延迟 FMA,每 0.5c 吞吐量一个。因此,您需要 10 个矢量累加器来保持 10 个 FMA 处于飞行状态,以使其 FMA 单元饱和。
    • 即使没有-ffast-math,您的版本也可能允许编译器使用一个累加器进行向量化(对于双精度,一个向量包含两个 64b 双精度)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-06
    • 1970-01-01
    • 1970-01-01
    • 2012-06-20
    • 1970-01-01
    相关资源
    最近更新 更多