首页 > 解决方案 > 我获取 int 数组点积的内在函数比普通代码慢,我做错了什么?

问题描述

我正在尝试了解内在以及如何正确利用和优化它,我决定实现一个函数来获取两个数组的点积作为学习的起点。

我创建了两个函数来获取整数数组的点积,int一个以正常方式编码,您循环遍历两个数组的每个元素,然后与每个元素执行乘法,然后将结果乘积相加/累加/求和以获得点积。

另一个使用内在的方式,我对每个数组的四个元素执行内在操作,我使用它们中的每一个相乘_mm_mullo_epi32,然后使用 2 个水平加法 _mm_hadd_epi32来获得当前 4 个元素的总和,然后我将它加到dot_product,然后继续下一个四个元素,然后重复直到达到计算的 limit 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.cpp10000000个元素的int数组中,当我在我的机器上编译上面的代码时,内在函数似乎比普通版本运行得慢,大多数时候,内在函数需要大约97529675 nanoseconds,有时甚至更长,而普通代码只需要87568313 nanoseconds,在这里我认为如果优化标志关闭,我的内在函数应该运行得更快,但事实证明它确实有点慢。

所以我的问题是:

我希望有人可以帮助,谢谢

标签: c++cpusseintrinsicsdot-product

解决方案


因此,根据@Peter Cordes、@Qubit 和@j6t的建议,我对代码进行了一些调整,现在我只在循环内进行乘法运算,然后将水平加法移到了循环外……它设法提高了内部版本从 around 97529675 nanoseconds,到 around56444187 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;
}

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


推荐阅读