C++ SIMD性能优化

22 篇文章 0 订阅
21 篇文章 0 订阅
// 使用SIMD指令优化的向量加法
//<mmintrin.h> MMX
//<xmmintrin.h>	SSE
//<emmintrin.h>	SSE2
//<pmmintrin.h>	SSE3
//<tmmintrin.h>	SSSE3
//<smmintrin.h>	SSE4.1
//<nmmintrin.h> SSE4.2
//<wmmintrin.h> AES
//<immintrin.h>	AVX, AVX2, FMA, BMI, POPCNT, AVX512
//<x86intrin.h>	Auto(GCC)
//<intrin.h> Auto(MSVC)
#include <emmintrin.h> // 包含SSE2指令集
#include <valarray>
#include <iostream>
#include <chrono>
#include <vector>

__m128i vector_add(__m128i a, __m128i b) {
    return _mm_add_epi32(a, b);
}

void add_vectors(int* a, int* b, int* c, int size) {
    for (int i = 0; i < size; i += 4) {
        __m128i va = _mm_load_si128((__m128i*)(a + i));
        __m128i vb = _mm_load_si128((__m128i*)(b + i));
        __m128i vc = _mm_add_epi32(va, vb);
        _mm_store_si128((__m128i*)(c + i), vc);
    }
}

int test() {
    std::valarray<float> a = { 1.0, 2.0, 3.0, 4.0 };
    std::valarray<float> b = { 5.0, 6.0, 7.0, 8.0 };
    std::valarray<float> c = a + b;
    for (auto& element : c) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    // 数据量小
    std::vector<int> a1(10);
    std::vector<int> b1(10);
    for (size_t i = 0; i < 10; i++)
    {
        a1[i] = i;
        b1[i] = i + 1;
    }
    std::vector<int> c1(10);

    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
    add_vectors(&a1[0], &b1[0], &c1[0], 4);
    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
    std::cout << "simd cost " << (t2 - t1).count() << std::endl;

    std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
    for (size_t i = 0; i < c1.size(); ++i)
        c1.at(i) = a1.at(i) + b1.at(i);
    std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
    std::cout << "cost " << (t4 - t3).count() << std::endl;


    // 数据量大
    std::vector<int> a2(10000);
    std::vector<int> b2(10000);
    for (size_t i = 0; i < 10000; i++)
    {
        a2[i] = i;
        b2[i] = i + 1;
    }
    std::vector<int> c2(10000);

    t1 = std::chrono::steady_clock::now();
    add_vectors(&a2[0], &b2[0], &c2[0], 4);
    t2 = std::chrono::steady_clock::now();
    std::cout << "simd cost " << (t2 - t1).count() << std::endl;

   t3 = std::chrono::steady_clock::now();
    for (size_t i = 0; i < c2.size(); ++i)
        c2.at(i) = a2.at(i) + b2.at(i);
   t4 = std::chrono::steady_clock::now();
    std::cout << "cost " << (t4 - t3).count() << std::endl;

    //for (const auto& ele : c1)
    //    std::cout << ele << " ";
    return 0;
}

输出

6 8 10 12
simd cost 500
cost 400
simd cost 5700
cost 49200


总结: 数据运算量小无效果,数据运算量大效果提升显著

为什么需要 SIMD?单个指令处理四个数据

这种单个指令处理多个数据的技术称为 SIMD(single-instruction multiple-data)。
他可以大大增加计算密集型程序的吞吐量。
因为 SIMD 把 4 个 float 打包到一个 xmm 寄存器里同时运算,很像数学中矢量的逐元素加法。因此 SIMD 又被称为矢量,而原始的一次只能处理 1 个 float 的方式,则称为标量。
在一定条件下,编译器能够把一个处理标量 float 的代码,转换成一个利用 SIMD 指令的,处理矢量 float 的代码,从而增强你程序的吞吐能力!
通常认为利用同时处理 4 个 float 的 SIMD 指令可以加速 4 倍。但是如果你的算法不适合 SIMD,则可能加速达不到 4 倍;也有因为 SIMD 让访问内存更有规律,节约了指令解码和指令缓存的压力等原因,出现加速超过 4 倍的情况。

addss 是什么意思?
    addps %xmm1, %xmm0
    有大量 ss 结尾的指令则说明矢量化失败;如果看到大多数都是 ps 结尾则说明矢量化成功
    addss:一个 float 加法。
    addsd:一个 double 加法。
    addps:四个 float 加法。
    addpd:两个 double 加法。


__restrict 是一个提示性的关键字,是程序员向编译器保证:这些指针之间不会发生重叠!
从而他可以放心地优化成功:所有非 const 的指针都声明 __restrict

OpenMP启用simd优化
#pragma omp simd 或 #pragma GCC ivdep
gcc -fopenmp -O3

循环: #pragma GCC unroll 4

在 struct 后加上 alignas(要对齐到的字节数) 
结构体大小若不是 2 的整数幂,往往会导致 SIMD 优化失败

std::assume_aligned<16>(a)  判断是否是16字节对齐


结构体的内存布局:AOS 与 SOA
AOS(Array of Struct)单个对象的属性紧挨着存
xyzxyzxyzxyz
SOA(Struct of Array)属性分离存储在多个数组
xxxxyyyyzzzz
AOS 必须对齐到 2 的幂才高效,SOA 就不需要。
AOS 符合直觉,不一定要存储在数组这种线性结构,而 SOA 可能无法保证多个数组大小一致。
SOA 不符合直觉,但通常是更高效的!

请勿用全局的数学函数,他们是 C 语言的遗产。始终用 std::sin, std::pow, std::sqrt 

参考

GitHub - parallel101/simdtutor: x86-64 SIMD矢量优化系列教程

GitHub - google/highway: Performance-portable, length-agnostic SIMD with runtime dispatch

https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html


创作不易,小小的支持一下吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码力码力我爱你

创作不易,小小的支持一下吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值