我获取 int 数组的点积的内在函数比普通代码慢,我做错了什么?

my intrinsic function in getting the dot product of an int array is slower than the normal code, what am I doing wrong?

我正在尝试了解 intrinsic 以及如何正确利用和优化它,我决定实现一个功能来得到两个数组的点积作为学习的起点

我创建了两个函数来获取整数数组的点积 int,其中一个以正常方式编码,您循环遍历两个数组的每个元素,然后对每个元素执行乘法,然后 add/accumulate/sum结果乘积得到点积。

另一种使用内在的方式是,我对每个数组的四个元素执行内在运算,我用 _mm_mullo_epi32 将它们中的每一个相乘,然后使用 2 水平加法 _mm_hadd_epi32 得到当前 4 个元素的总和,之后我将它加到 dot_product,然后继续下一个四个元素,然后重复直到我得到计算的限制 vec_loop,然后我用正常的方式计算其他剩余的元素,避免计算出数组的内存,然后我比较两者的性能。

两种点积函数的头文件:

// main.hpp
#ifndef main_hpp
#define main_hpp

#include <iostream>
#include <immintrin.h>

template<typename T>
T scalar_dot(T* a, T* b, size_t len){
    T dot_product = 0;
    for(size_t i=0; i<len; ++i) dot_product += a[i]*b[i];
    return dot_product;
}

int sse_int_dot(int* a, int* b, size_t len){
    
    size_t vec_loop = len/4;
    size_t non_vec = len%4;
    size_t start_non_vec_i = len-non_vec;

    int dot_prod = 0;

    for(size_t i=0; i<vec_loop; ++i)
    {
        __m128i va = _mm_loadu_si128((__m128i*)(a+(i*4)));
        __m128i vb = _mm_loadu_si128((__m128i*)(b+(i*4)));
        va = _mm_mullo_epi32(va,vb);
        va = _mm_hadd_epi32(va,va);
        va = _mm_hadd_epi32(va,va);
        dot_prod += _mm_cvtsi128_si32(va);
    }

    for(size_t i=start_non_vec_i; i<len; ++i) dot_prod += a[i]*b[i];

    return dot_prod;
}

#endif

cpp代码测量每个函数所用时间

// main.cpp
#include <iostream>
#include <chrono>
#include <random>
#include "main.hpp"

int main()
{
    // generate random integers
    unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count();
    std::mt19937_64 rand_engine(seed);
    std::mt19937_64 rand_engine2(seed/2);
    std::uniform_int_distribution<int> random_number(0,9);

    size_t LEN = 10000000;

    int* a = new int[LEN];
    int* b = new int[LEN];

    for(size_t i=0; i<LEN; ++i)
    {
        a[i] = random_number(rand_engine);
        b[i] = random_number(rand_engine2);
    }

    #ifdef SCALAR
    int dot1 = 0;
    #endif
    #ifdef VECTOR
    int dot2 = 0;
    #endif

    // timing
    auto start = std::chrono::high_resolution_clock::now();
    #ifdef SCALAR
    dot1 = scalar_dot(a,b,LEN);
    #endif
    #ifdef VECTOR
    dot2 = sse_int_dot(a,b,LEN);
    #endif
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end-start);
    std::cout<<"proccess taken "<<duration.count()<<" nanoseconds\n";

    #ifdef SCALAR
    std::cout<<"\nScalar : Dot product = "<<dot1<<"\n";
    #endif
    #ifdef VECTOR
    std::cout<<"\nVector : Dot product = "<<dot2<<"\n";
    #endif

    return 0;
}

编译:

我的机器:

main.cpp 中有 10000000int 数组的元素,当我编译上面的代码时在我的机器上,似乎内部函数 运行 比普通版本慢,大多数时候,内部函数需要大约 97529675 nanoseconds 有时甚至更长,而普通代码只需要大约 87568313 nanoseconds,在这里我认为如果优化标志关闭,我的内部函数应该 运行 更快,但事实证明它确实有点慢。

所以我的问题是:

希望有人能帮忙,谢谢

所以根据 @Peter Cordes、@Qubit 和 @j6t 的建议,我稍微修改了代码,我现在只在循环内做乘法,然后我移动了水平在循环外添加...它设法将内部版本的性能从 97529675 nanoseconds 左右提高到 56444187 nanoseconds 左右,这比我以前的实现快得多,具有相同的编译标志和 10000000 个 int 数组元素。

这是 main.hpp

的新函数
int _sse_int_dot(int* a, int* b, size_t len){
        
    size_t vec_loop = len/4;
    size_t non_vec = len%4;
    size_t start_non_vec_i = len-non_vec;

    int dot_product;
    __m128i vdot_product = _mm_set1_epi32(0);

    for(size_t i=0; i<vec_loop; ++i)
    {
        __m128i va = _mm_loadu_si128((__m128i*)(a+(i*4)));
        __m128i vb = _mm_loadu_si128((__m128i*)(b+(i*4)));
        __m128i vc = _mm_mullo_epi32(va,vb);
        vdot_product = _mm_add_epi32(vdot_product,vc);
    }

    vdot_product = _mm_hadd_epi32(vdot_product,vdot_product);
    vdot_product = _mm_hadd_epi32(vdot_product,vdot_product);
    dot_product = _mm_cvtsi128_si32(vdot_product);

    for(size_t i=start_non_vec_i; i<len; ++i) dot_product += a[i]*b[i];

    return dot_product;
}

如果此代码还有更多需要改进的地方,请指出,现在我将把它留在这里作为答案。