知方号

知方号

C/C++指令集介绍以及优化(主要针对SSE优化)<阿尔法指令集>

C/C++指令集介绍以及优化(主要针对SSE优化)

前言:最近在做一些OpenCV的优化相关的东西,发现OpenCV现在的执行效率很高的原因一部分是来自于底层的优化,比如指令集优化,但是一直没找到比较系统性的关于CPU指令集优化的文章或者是书籍,于是自己打算做一个总结,鉴于水平有限,有不正确的地方还望有大佬指正。

一、CPU指令集优化的概述

1.1 数据并行的两种实现

在计算机体系中,数据并行有两种实现路径:

MIMD(Multiple Instruction Multiple Data,多指令流多数据流)SIMD(Single Instruction Multiple Data,单指令流多数据流)。

其中MIMD的表现形式主要有多发射、多线程、多核心,在当代设计的以处理能力为目标驱动的处理器中,均能看到它们的身影。

同时,随着多媒体、大数据、人工智能等应用的兴起,为处理器赋予SIMD处理能力变得愈发重要,因为这些应用存在大量细粒度、同质、独立的数据操作,而SIMD天生就适合处理这些操作。

SIMD结构有三种变体:向量体系结构、多媒体SIMD指令集扩展和图形处理单元。

注意:SIMD本身并不是一种指令集,而是一种处理思想哦,现在的一些指令集都支持SIMD。

 

1.2 各个CPU指令集的发展简介

(1)MMX指令——Multi Media eXtension,多媒体扩展指令集

1996年,MMX指令集率先在Pentium处理器中使用,MMX指令集支持算数、比较、移位等运算,MMX指令集的向量寄存器是64bit。

(2)SSE指令集系列——Streaming SIMD Extensions,单指令多数据流扩展

SSE在1999年率先在Pentium3中出现,向量寄存器由MMX的64bit拓展到128bit;SSE2在2002年出现,包括了SIMD的浮点和整型运算的指令以及整型和浮点数据之间的转换;SSE3在2004年出现,支持不对其访问,处理虚数运算的复杂指令以及水平加减操作运算指令;SSE4.1在2006年出现,加入了处理字符串文本和面向应用的优化指令;SSE4.2指令

总结:所有的SSE系列指令的向量寄存器都是128bit哦。

(3)AVX指令集系列——Advanced Vector Extensions

AVX指令集是Sandy Bridge和Larrabee架构下的新指令集,AVX是在之前的SSE128位扩展到和256位的单指令多数据流。

AVX出现在2008年,由128bit拓展到256bit,增强了数据重排和灵活的不对齐地址访问;AVX2出现在2011年,增加了256bit的整数向量操作,融合乘加,跨通道数据重排等等;AVX-512出现在2014年,由256bit拓展到512bit;

(4)Intel IMCI指令集

IMCI出现在2010年,向量寄存器长度拓展到512bit。

(5)其他指令集

AES、FMA3、EM64-T、VT-x等等

这里有一张指令集大致的发展过程表格

1.3 关于CPU指令的说明

上面的一些指令集,都是针对Intel的CPU指令的,各个芯片厂商都有相应的指令集,只不过是名称不一样,如AMD的也同样包含很多指令集,这里就不介绍了。

其他 SIMD 扩展部件还包括

摩托罗拉 PowerPC 处理器的 AltiVec、Sun 公司 SPARC 处理器中的 VIS、HP 公司 PA-RISC 处理器中的 MAX、DEC 公司 Alpha 处理器中的 MVI-2、MIPS公司 V 处理器中的 MDMX、AMD处理器中的3DNow!、ARM内核中的NEON、CEVA公司的VCU 等。

另外,SIMD 扩展部件最初仅用于多媒体领域和数字信号处理器中,后来,研究人员将SIMD 扩展部件应用到高性能计算机中,如,IBM 的超级计算机 BlueGene/L 和国产的神威蓝光超级计算机中都集成短向量扩展部件。国产处理器中,龙芯、迈创以及魂芯一号都含有 SIMD 扩展部件。

1.4 如何查看自己的CPU支持哪一些指令集

推荐一款小工具 ,名字叫做 CPU-Z,

查看相应的GPU的工具叫做 GPU-Z,

我的CPU信息如下:

在Linux下可以使用cat /proc/cpuinfo来查看CPU支持哪些指令集。

 

二、关于指令集的一些问题集中回答

2.1 几个问题

(1)浮点计算 vs 整数计算 为什么要分开讲呢?因为在指令集中也是分开的,另外,由于浮点数占4个字节或者8个字节,而整数却可以分别占1,2,4个字节按照应用场合不同使用的不同,因此向量化加速也不同。因此一个指令最多完成4个浮点数计算。而可以完成16个int8_t数据的计算。

(2)优化技巧 注意指令的顺序,为什么呢,因为CPU是流水线工作的,因此相邻的指令开始的执行的时间并非一个指令执行完毕之后才会开始,但是一旦遇到数据联系,这时候会发生阻塞,如果我们很好的安排指令的顺序,使得数据相关尽量少发生,或者发生的时候上一个指令已经执行完了。因此注意稍微修改指令的执行顺序就会使得代码变快。  

2.2 指令集的一些问题

(1)没有统一的移植标准。

就以SSE指令而言。SSE的指令集是X86架构CPU特有的,对于ARM架构、MIPS架构等CPU是不支持的,所以使用了SSE指令集的程序,是不具备可移植标准的。

不仅如此,前面说过Intel和AMD对于同样的128bit向量的指令语法是不一样的,所以,在Intel之下所写的代码并不能一直到AMD的机器上进行指令集加速,其它的也一样,也就是说,写的某一种指令加速代码,不具备完全的可移植性。

SIMD指令,可以一次性装载多个元素到寄存器。如果是128位宽度,则可以一次装载4个单精度浮点数。这4个float可以一次性地参与乘法计算,理论上可提速4倍。不同的平台有不同的SIMD指令集,如Intel平台的指令集有MMX、SSE、AVX2、AVX512等(后者是对前者的扩展,本质一样),ARM平台是128位的NEON指令集。如果你希望用SIMD给算法加速,你首先需要学习不同平台的SIMD指令集,并为不同的平台写不同的代码,最后逐个测试准确性。这样无法实现write once, run anywhere的目标。

(2)针对指令集没办法转移的解决方案

OpenCV 4.x中提供了强大的统一向量指令(universal intrinsics),使用这些指令可以方便地为算法提速。所有的计算密集型任务皆可使用这套指令加速,并不是专门针对计算机视觉算法。目前OpenCV的代码加速实现基本上都基于这套指令。OpenCV设计了一套统一的向量指令universal intrinsics,可以让你写一份代码,在不同平台上都可以实现向量加速

2.3 指令集优化代码的一般步骤

(1)第一步:即所谓的load步骤

指的是需要将数据从内存加载(load)到CPU的内存储里面;

(2)第二步:即所谓的运算

将加载进来的数据进行加减乘除等等运算;

(3)第三步:即所谓的store步骤

将运算的结果需要重新存储到内存里面;

 

三、SSE指令集的使用说明

SSE本质上类似于一个向量处理器,所谓的向量处理器实际上就是进行向量的运算,

包括了4个主要部分:单精确度浮点数运算指令、整数运算指令(为MMX的延伸,并与MMX使用同样的暂存器)、Cache控制指令、状态控制指令。

 

3.1 如何使用SSE指令

       使用SSE指令有两种方式:

一是直接在C/C++中嵌入(汇编)指令;二是使用Intel C++ Compiler或是Microsoft Visual C++中提供的支持SSE指令集的intrinsics内联函数。

从代码可读和维护角度讲,推荐使用intrinsics内联函数的形式。intrinsics是对MMX、SSE等指令集的一种封装,以函数的形式提供,使得程序员更容易编写和使用这些高级指令,在编译的时候,这些函数会被内联为汇编,不会产生函数调用的开销。想要使用SSE指令,则需要包含对应的头文件:

#include   //mmx#include  //sse#include  //sse2#include  //sse3

备注:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至lizi9903@foxmail.com举报,一经查实,本站将立刻删除。