SIMD 编程的优势与SIMD指令:SSE/AVX 与编程demo

2023-10-14 06:58

本文主要是介绍SIMD 编程的优势与SIMD指令:SSE/AVX 与编程demo,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

资源:https://download.csdn.net/download/Rong_Toa/18745608

《Benefits of SIMD Programming | SIMD的优势》

目录

SIMD指令编程demo

正常代码

一次循环计算4次

使用SSE指令

使用AVX指令

性能对比

更多参考


SIMD指令编程demo


本文更新于 2018.10.24

本demo主要使用矩阵相乘, 演示了Intel SSE和AVX内部指令(intrinsics)的显式使用, 并对比了使用gcc和icc(Intel C/C++编译器)使用不同编译选项编译后的代码性能.

完整源码见: https://raw.githubusercontent.com/zzqcn/storage/master/code/c/simd_multiply.c

参考: https://software.intel.com/zh-cn/articles/ticker-tape-part-2

本文的软硬件环境如下:

  • CPU: Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz
  • 操作系统: CentOS Linux release 7.0.1406
  • 内核: 3.10.0-123.el7.x86_64
  • gcc: 4.8.5 20150623
  • icc: 19.0.0.120 20180804

正常代码


正常代码如下, 直接使用基本算法计算2个矩阵a和b, 结果放在c中:

void multiply(void) {unsigned i;for(i=0; i<N; i++) {c[i] = a[i] * b[i];}
}

一次循环计算4次


可以将正常代码改为一次循环内计算4次乘法, 某种情况下可以提升性能:

void multiply(void) {unsigned i;for(i=0; i<(N & ((~(unsigned)0x3))); i+=4) {c[i]   = a[i]   * b[i];c[i+1] = a[i+1] * b[i+1];c[i+2] = a[i+2] * b[i+2];c[i+3] = a[i+3] * b[i+3];}for(; i<N; i++) {c[i] = a[i] * b[i];}
}

使用SSE指令


Intel SSE指令通过128bit位宽的专用寄存器, 支持一次操作128bit数据. float是单精度浮点数, 占32bit, 那么可以使用一条SSE指令一次计算4个float数:

void multiply(void) {unsigned i;__m128 A, B, C;for(i=0; i<(N & ((~(unsigned)0x3))); i+=4) {A = _mm_load_ps(&a[i]);B = _mm_load_ps(&b[i]);C = _mm_mul_ps(A, B);_mm_store_ps(&c[i], C);}for(; i<N; i++) {c[i] = a[i] * b[i];}
}

注意这些SSE指令要求参数中的内存地址必须对齐于16字节边界, 所以可以用以下函数分配内存:

a = (float*) _mm_malloc(N*sizeof(float), 16);
b = (float*) _mm_malloc(N*sizeof(float), 16);
c = (float*) _mm_malloc(N*sizeof(float), 16);

要使用这些intrinsics, 需要包含x86intrin.h头文件.

 

使用AVX指令


较新的Intel CPU都支持AVX指令集, 它可以一次操作256bit数据, 是SSE的2倍. 使用AVX的代码如下:

void multiply(void) {unsigned i;__m256 A, B, C;for(i=0; i<(N & ((~(unsigned)0x7))); i+=8) {A = _mm256_load_ps(&a[i]);B = _mm256_load_ps(&b[i]);C = _mm256_mul_ps(A, B);_mm256_store_ps(&c[i], C);}for(; i<N; i++) {c[i] = a[i] * b[i];}
}

AVX指令要求内存地址对齐于32字节边界, 所以内存分配代码改为:

a = (float*) _mm_malloc(N*sizeof(float), 32);
b = (float*) _mm_malloc(N*sizeof(float), 32);
c = (float*) _mm_malloc(N*sizeof(float), 32);

 

性能对比


我分别使用gcc和icc以默认选项和-O3选项编译了以上3种版本的代码, 其中用gcc编译AVX版代码时需要加-mavx选项.

代码执行时间如下(单位毫秒):

代码版本gccgcc -O3iccicc -O3
原始946438405404
每次计算4项780438439442
SSE680439405406
AVX545447407406

代码执行时间是连续运行10次取的平均值. 某些时候执行时间起伏时间较大. 下图是根据上表生成的对比图:

由上图可知:

  • 现代编译器在-O3编译时会对代码进行充分优化, 使得本文中的代码无论使不使用SIMD指令性能差距不大
  • gcc编译器默认编译时未对代码进行充分优化, 使得不同算法的代码性能差距较大
  • intel编译器对-O3编译选项不敏感, 总是会自动优化代码

更多参考

  • Intel 64 and IA-32 Architectures Software Developer’s Manual
  • Intel Intrinsics Guide

 

这篇关于SIMD 编程的优势与SIMD指令:SSE/AVX 与编程demo的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/208875

相关文章

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

16.Spring前世今生与Spring编程思想

1.1.课程目标 1、通过对本章内容的学习,可以掌握Spring的基本架构及各子模块之间的依赖关系。 2、 了解Spring的发展历史,启发思维。 3、 对 Spring形成一个整体的认识,为之后的深入学习做铺垫。 4、 通过对本章内容的学习,可以了解Spring版本升级的规律,从而应用到自己的系统升级版本命名。 5、Spring编程思想总结。 1.2.内容定位 Spring使用经验

IPython小白教程:提升你的Python交互式编程技巧,通俗易懂!

IPython是一个增强的Python交互式shell,它提供了丰富的功能和便捷的交互方式,使得Python开发和数据分析工作更加高效。本文将详细介绍IPython的基本概念、使用方法、主要作用以及注意事项。 一、IPython简介 1. IPython的起源 IPython由Fernando Pérez于2001年创建,旨在提供一个更高效的Python交互式编程环境。 2. IPyt

从《深入设计模式》一书中学到的编程智慧

软件设计原则   优秀设计的特征   在开始学习实际的模式前,让我们来看看软件架构的设计过程,了解一下需要达成目标与需要尽量避免的陷阱。 代码复用 无论是开发何种软件产品,成本和时间都最重要的两个维度。较短的开发时间意味着可比竞争对手更早进入市场; 较低的开发成本意味着能够留出更多营销资金,因此能更广泛地覆盖潜在客户。 代码复用是减少开发成本时最常用的方式之一。其意图

Java并发编程—阻塞队列源码分析

在前面几篇文章中,我们讨论了同步容器(Hashtable、Vector),也讨论了并发容器(ConcurrentHashMap、CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便。今天我们来讨论另外一类容器:阻塞队列。   在前面我们接触的队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了D

剑指offer—编程题7(用两个栈实现一个队列)

题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail 和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。 代码如下: [java]  view plain copy print ? public class Test07 {       /**       * 用两个栈模拟的队列       *

剑指Offer—编程题4 ( 替换空格)

一、题目:替换空格 题目:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.”。    在网络编程中,如果URL参数中含有特殊字符,如空格、'#'等,可能导致服务器端无法获得正确的参数值。我们需要将这些特殊符号转换成服务器可以识别的字符。转换的规则是在'%'后面跟上ASCII码的两位十六进制的表示。

剑指Offer—编程题56(链表中环的入口地址)

题目:一个链表中包含环,如何找出环的入口结点? 解题思路   可以用两个指针来解决这个问题。先定义两个指针P1和P2指向链表的头结点。如果链表中环有n个结点,指针P1在链表上向前移动n步,然后两个指针以相同的速度向前移动。当第二个指针指向环的入口结点时,第一个指针已经围绕着环走了一圈又回到了入口结点。    剩下的问题就是如何得到环中结点的数目。我们在面试题15的第二个相关题目时用到