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

问题描述

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

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

一个使用内在的方式,我对每个数组的四个元素执行内在操作,我使用 _mm_mullo_epi32 将它们中的每一个相乘,然后使用 2 水平相加 {{1 }} 获取当前 4 个元素的总和,然后将其加到 dot_product,然后继续下一个 4 个元素,然后重复直到达到计算的限制 _mm_hadd_epi32,然后我计算其他剩余元素使用正常方式避免计算出数组的内存,然后我比较两者的性能

具有两种点积函数的头文件

vec_loop

cpp 代码,用于测量每个函数所花费的时间

// 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

编译:

  • 内在版本:// 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,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; }
  • 普通版:g++ main.cpp -DVECTOR -msse4.1 -o main.o

我的机器:

  • 架构:x86_64
  • cpu:1
  • cpu 内核:4
  • 每核线程数:1
  • 型号名称:Intel(R) Pentium(R) cpu N3700 @ 1.60GHz
  • 一级缓存:96 KiB
  • L1i 缓存:128 KiB
  • 二级缓存:2 MiB
  • 一些标志:sse、sse2、sse4_1、sse4_2

g++ main.cpp -DSCALAR -msse4.1 -o main.o中有main.cpp数组的10000000个元素,当我在我的机器上编译上面的代码时,似乎内部函数运行速度比普通版本慢,大多数时候,内部函数需要int,有时甚至更长,而普通代码只需要97529675 nanoseconds,这里我认为我的内部函数应该运行得更快如果优化标志关闭,但结果确实有点慢。

所以我的问题是:

  • 为什么我的内在函数运行速度较慢? (我做错了什么吗?)
  • 如何纠正我的内在实现,正确的方法是什么?
  • 即使优化标志关闭,编译器也会自动矢量化/展开正常代码
  • 鉴于我的机器的规格,获得点积的最快方法是什么?

希望有人能帮忙,谢谢

解决方法

因此,根据@Peter Cordes、@Qubit 和@j6t 的建议,我稍微调整了代码,现在只在循环内进行乘法运算,然后将水平加法移到循环外。 .. 它设法将内在版本的性能从 Thread 左右提高到 97529675 nanoseconds 左右,这比我之前的实现要快得多,编译标志相同,10000000 int 数组的元素。

这里是 main.hpp 中的新函数

56444187 nanoseconds

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...