用AVX2指令集优化浮点数组求和

  • 一、AVX2指令集介绍
  • 二、代码实现
    • 0. 数据生成
    • 1. 普通数组求和
    • 2. AVX2指令集求和:单精度浮点(float)
    • 3. AVX2指令集求和:双精度浮点(double)
  • 三、性能测试
    • 测试环境
    • 计时方式
    • 测试内容
    • 进行性能测试
      • 第一次测试
      • 第二次测试
  • 四、总结
    • 个人猜测原因:

一、AVX2指令集介绍

AVX2是SIMD(单指令多数据流)指令集,支持在一个指令周期内同时对256位内存进行操作。包含乘法,加法,位运算等功能。下附Intel官网使用文档。
Intel® Intrinsics Guide

我们本次要用到的指令有 __m256i _mm256_add_pd(__m256i a, __m256i b), __m256i _mm256_add_ps等,(p代表精度precision,s代表single,d代表double)

它们可以一次取256位的内存,并按32/64位一个浮点进行加法运算。下附官网描述。

Synopsis

__m256d _mm256_add_pd (__m256d a, __m256d b)

#include <immintrin.h>

Instruction: vaddpd ymm, ymm, ymm

CPUID Flags: AVX

Description

Add packed double-precision (64-bit) floating-point elements in a and b, and store the results in dst.

Operation

FOR j := 0 to 3i := j*64dst[i+63:i] := a[i+63:i] + b[i+63:i]
ENDFOR
dst[MAX:256] := 0

Performance

Architecture Latency Throughput (CPI)
Icelake 4 0.5
Skylake 4 0.5
Broadwell 3 1
Haswell 3 1
Ivy Bridge 3 1

二、代码实现

0. 数据生成

为了比较结果,我们生成从1到N的等差数列。这里利用模版兼容不同数据类型。由于AVX2指令集一次要操作多个数据,为了防止访存越界,我们将大小扩展到256的整数倍位比特,也就是32字节的整数倍。

uint64_t lowbit(uint64_t x)
{return x & (-x);
}uint64_t extTo2Power(uint64_t n, int i)//arraysize datasize
{while(lowbit(n) < i)n += lowbit(n);return n;
}
template <typename T>
T* getArray(uint64_t size)
{uint64_t ExSize = extTo2Power(size, 32/sizeof(T));T* arr = new T[ExSize];for (uint64_t i = 0; i < size; i++)arr[i] = i+1;for (uint64_t i = size; i < ExSize; i++)arr[i] = 0;return arr;
}

1. 普通数组求和

为了比较性能差异,我们先实现一份普通的数组求和。这里也使用模版。

template <typename T>
T simpleSum(T* arr, uint64_t size)
{T sum = 0;for (uint64_t i = 0; i < size; i++)sum += arr[i];return sum;
}

2. AVX2指令集求和:单精度浮点(float)

这里我们预开一个avx2的整形变量,每次从数组中取8个32位浮点,加到这个变量上,最后在对这8个32位浮点求和。

float avx2Sum(float* arr, uint64_t size)
{float sum[8] = {0};__m256 sum256 = _mm256_setzero_ps();__m256 load256 = _mm256_setzero_ps();for (uint64_t i = 0; i < size; i += 8){load256 = _mm256_loadu_ps(&arr[i]);sum256 = _mm256_add_ps(sum256, load256);}sum256 = _mm256_hadd_ps(sum256, sum256);sum256 = _mm256_hadd_ps(sum256, sum256);_mm256_storeu_ps(sum, sum256);sum[0] += sum[4];return sum[0];
}

这里的hadd是横向加法,具体实现类似下图,可以帮我们实现数组内求和:

3. AVX2指令集求和:双精度浮点(double)

double avx2Sum(double* arr, uint64_t size)
{double sum[4] = {0};__m256d sum256 = _mm256_setzero_pd();__m256d load256 = _mm256_setzero_pd();for (uint64_t i = 0; i < size; i += 4){load256 = _mm256_loadu_pd(&arr[i]);sum256 = _mm256_add_pd(sum256, load256);}sum256 = _mm256_hadd_pd(sum256, sum256);_mm256_storeu_pd(sum, sum256);sum[0] += sum[2];return sum[0];
}

三、性能测试

测试环境

Device Description
CPU Intel Core i9-9880H 8-core 2.3GHz
Memory DDR4-2400MHz Dual-Channel 32GB
complier Apple Clang-1300.0.29.30

计时方式

利用chrono库获取系统时钟计算运行时间,精确到毫秒级

uint64_t getTime()
{uint64_t timems = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();return timems;
}

测试内容

对1到1e9求和,答案应该为500000000500000000, 分别测试float和double。

 uint64_t N = 1e9;// compare the performance of normal add and avx2 adduint64_t start, end;// test floatcout << "compare float sum: " << endl;float* arr3 = getArray<float>(N);start = getTime();float sum3 = simpleSum(arr3, N);end = getTime();cout << "float simpleSum time: " << end - start << endl;cout << "float simpleSum sum: " << sum3 << endl;start = getTime();sum3 = avx2Sum(arr3, N);end = getTime();cout << "float avx2Sum time: " << end - start << endl;cout << "float avx2Sum sum: " << sum3 << endl;delete[] arr3;cout << endl << endl;// test doublecout << "compare double sum: " << endl;double* arr4 = getArray<double>(N);start = getTime();double sum4 = simpleSum(arr4, N);end = getTime();cout << "double simpleSum time: " << end - start << endl;cout << "double simpleSum sum: " << sum4 << endl;start = getTime();sum4 = avx2Sum(arr4, N);end = getTime();cout << "double avx2Sum time: " << end - start << endl;cout << "double avx2Sum sum: " << sum4 << endl;delete[] arr4;cout << endl << endl;

进行性能测试

第一次测试

  • 测试命令
g++ -mavx2 avx_big_integer.cpp
./a.out
  • 测试结果
方法 耗时(ms)
AVX2加法 单精度 615
普通加法 单精度 2229
AVX2加法 双精度 1237
普通加法 双精度 2426

这里能看到单精度下已经出现了比较明显的误差,并且由于普通求和和avx2求和的加法顺序不一样,导致误差值也不一样。

第二次测试

  • 测试命令
    现在我们再开启O2编译优化试一试:
g++ -O2 -mavx2 avx_big_integer.cpp
./a.out
  • 测试结果
方法 耗时(ms)
AVX2加法 32位 244
普通加法 32位 1012
AVX2加法 64位 476
普通加法 64位 1292

我们发现,比起上一次对整形的测试,浮点型在开启O2优化后反而是AVX2指令集加法得到了明显的提升。

四、总结

可见在进行浮点运算时,用avx2指令集做并行优化,能得到比起整形更好的效果。

个人猜测原因:

  1. 浮点型加法器比整形加法器复杂许多,流水线操作的效果不那么明显。
  2. 有可能CPU内的浮点加法器少于整形加法器,导致O2优化乱序执行时的优化效果不如整形理想。
  3. AVX2指令集可能针对浮点运算有专门的优化,使得浮点运算性能和整形运算更为接近。

用AVX2指令集优化浮点数组求和相关推荐

  1. [C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)...

    作者:zyl910. 本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE.AVX指令集.因使用了stdint.zintrin.ccpuid这三个模块, ...

  2. [C] 跨平台使用Intrinsic函数范例3——使用MMX、SSE2指令集 处理 32位整数数组求和...

    作者:zyl910. 本文面对对SSE等SIMD指令集有一定基础的读者,以32位整数数组求和为例演示了如何跨平台使用MMX.SSE2指令集.支持vc.gcc编译器,在Windows.Linux.Mac ...

  3. ARM NEON指令集优化理论与实践

    ARM NEON指令集优化理论与实践 一.简介 NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bi ...

  4. python 依据某几列累加求和_关于Python数组求和的四个问题及详解,让你更加爱Python!...

    总结了四个数求和的问题及详解,如果你正在学习Python的话,可以多学习一下. | 问题一:专题概述 代码相关 本节的内容 通过第一个问题来初步了解数组求和的两种常用方法 Two Sum 给定一个整数 ...

  5. Intel的AVX2指令集解读

    原文链接:http://blog.csdn.net/vbskj/article/details/38408213 在Intel Sandy Bridge微架构中,Intel引入了256位SIMD扩展A ...

  6. java 数组怎么求和,感动,我终于学会了Java对数组求和

    前言 看到题目是不是有点疑问:你确定你没搞错?!数组求和???遍历一遍累加起来不就可以了吗??? 是的,你说的都对,都听你的,但是我说的就是数组求和,并且我也确实是刚刚学会.╮(╯▽╰)╭ 继续看下去 ...

  7. 感动,我终于学会了用Java对数组求和

    前言 看到题目是不是有点疑问:你确定你没搞错?!数组求和???遍历一遍累加起来不就可以了吗??? 是的,你说的都对,都听你的,但是我说的就是数组求和,并且我也确实是刚刚学会.╮(╯▽╰)╭ 继续看下去 ...

  8. neon 指令 c语言,Neon指令集优化快速入门教程

    Neon指令集优化快速入门教程 Neon指令集优化快速入门教程 文章目录 1. Neon是什么? 2.Neon为什么速度快 3. Neon基础概念 4. Neon的C语言接口 C语言程序中集成Neon ...

  9. neon浮点运算_ARM NEON指令集优化理论与实践

    ARM NEON指令集优化理论与实践 一.简介 NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bi ...

  10. 【genius_platform软件平台开发】第二十八讲:NEON指令集优化(附实例)

    当在ARM芯片上进行一些例如图像处理等计算的时候,常常会因为计算量太大造成计算帧率较低的情况.因而,需要选择一种更加简单快捷的计算方式以获得处理速度上的提升.ARM NEON就是一个不错的选择. ※ ...

最新文章

  1. java配置中心开源项目_配置中心搭建(spring-cloud-config-server)
  2. 安卓天天练练(十一)用list绑数据
  3. recycleview 嵌套高度问题_简单解决RecyclerView嵌套的RecyclerView条目显示不全和宽度不能铺满...
  4. Matlab | 数字信号处理:Matlab语言的基本使用方法(matlab代码版)
  5. (赞助5本)谷歌官方推荐的 TensorFlow 2 “豹书”来了!
  6. Marketing Cloud extension field technical name
  7. python仿真智能驾驶_基于Python的3R机器人运动仿真
  8. .NET Core开发实战(定义API的最佳实践)Source Generators版
  9. Java 获取集合长度
  10. hdu1066(经典题)
  11. ES6中的扩展运算符
  12. 5、Fiddler如何捕获HTTPS会话
  13. loadrunner回放时IE模拟器弹出windows安全警告
  14. 单总线CPU微程序条件判别测试逻辑
  15. keil4找不到c语言头文件路径,keil4中头文件路径设置的方法汇总
  16. SQL如何还原数据库
  17. 北航MOOC系统Android客户端NABC
  18. 很激励人的一首歌《逍遥叹》(ZT)
  19. SpaceSyntax【空间句法】之DepthMapX学习:第四篇 凸多边形图分析[未完]
  20. JavaWeb中 pojo、entity、Dao、bo的含义

热门文章

  1. keytool 使用
  2. cef注入js_CefSharp JavaScript注入
  3. 阿里矢量图库使用方法
  4. 简述数学建模的过程_数学建模
  5. JSESSIONID是什么
  6. 【对比】新、旧QC七大手法的异同
  7. vue打包后hbuilder和cordova模拟器运行一片空白
  8. Paraview快捷键操作
  9. udp 消息转发 服务器,UDP消息发送
  10. 求解哈夫曼编码Java实现,哈夫曼编码(Java)