英特尔® 高级矢量扩展指令集简介

作者 Chris Lomont

下载文章

下载 英特尔® 高级矢量扩展指令集简介 [PDF 1.4MB]

英特尔® 高级矢量扩展指令集(英特尔® AVX)是在英特尔® 架构 CPU 上执行单指令多数据 (SIMD) 运算的指令集。这些指令添加了以下特性,对之前的 SIMD 产品——MMX™ 指令和英特尔® 数据流单指令多数据扩展指令集(英特尔® SSE)进行了扩展:

  • 将 128 位 SIMD 寄存器扩展至 256 位。英特尔® AVX 的目标是在未来可以支持 512 或 1024 位。
  • 添加了 3 操作数非破坏性运算。之前在 A = A + B 类运算中执行的是 2 操作数指令,它将覆盖源操作数,而新的操作数可以执行 A = B + C 类运算,且保持原始源操作数不变。.
  • 少数几个指令采用 4 寄存器操作数,通过移除不必要的指令,支持更小、更快的代码。
  • 对于操作数的内存对齐要求有所放宽。
  • 新的扩展编码方案 (VEX) 旨在使得以后添加更加容易以及所执行的指令编码更小、速度更快。

与这些改进密切相关的是新的融合乘加 (FMA) 指令,它可以更快、更精确地执行专门运算,例如单指令 A = A * B + C。第二代英特尔® 酷睿™ CPU 中将可提供 FMA 指令。其他特性还包括处理高级加密标准 (AES) 加密和解密的指令、用于某些加密基元的紧缩 carry-less 乘法运算 (PCLMULQDQ) 以及某些用于未来指令的预留槽,如硬件随机数生成器。

 

指令集概述

新指令使用英特尔的 VEX prefix 进行编码,VEX prefix 是一个 2个或 3 个字节的前缀,旨在降低当前和未来 x86/x64 指令编码的复杂性。两个新的 VEX prefix 形成于两个过时的 32 位指令——使用 DS 的 Load Pointer(LDS-0xC4,3 字节形式)和使用 ES 的 Load Pointer(LES-0xC5, 2 字节形式),它们以 32 位模式加载 DS 和 ES 段寄存器。在 64 位模式中,操作码 LDS 和 LES 生成一个无效操作码异常,但是在英特尔® AVX 下,这些操作码可另外用作编码新指令前缀。最终,VEX 指令只能在 64 位模式中运行时使用。前缀可比之前的 x86 指令编码更多的寄存器,可用于访问新的 256 位 SIMD 寄存器或者使用 3 和 4 操作数语法。作为用户,您无需担心这个问题(除非您正在编写汇编器或反汇编器)。


注: 下文假定运算都是在 64 位模式中进行。


SIMD 指令可以在一个单一步骤中处理多个片段的数据,加速许多任务的吞吐量,包括视频编解码、图像处理、数据分析和物理模拟。在 32 位长度(称之为单精度)和64 位长度(称之为双精度)中,英特尔® AVX 指令在IEEE-754浮点值上运行。IEEE-754 是一个标准定义的、可复制的强大浮点运算,是大多数主流数字计算的标准。

较早的相关英特尔® SSE 指令还支持各种带符号和无符号整数大小,包括带符号和无符号字节(B,8 位)、字(W,16 位)、双字(DW,32 位)、四字(QW,64 位)和双四字(DQ,128 位)长度。并非所有指令都适用于所有大小组合,更多详细信息,敬请参阅“更多信息”中提供的链接。请参阅本文后文中的图 2,了解数据类型的图形表示法。

支持英特尔® AVX(和 FMA)的硬件包括 16 个 256 位 YMM 寄存器 YMM0-YMM15 和一个名为 MXCSR的 32 位控制/状态寄存器。YMM 寄存器替代了英特尔® SSE 使用的较早的 128 位 XMM 寄存器,它将 XMM 寄存器视作相应 YMM 寄存器的下层部分,如图 1 所示。

MXCSR 的 0-5 位显示设置“粘滞”位后 SIMD 浮点异常,除非使用位LDMXCSRFXRSTOR清除,否则它们将一直保持设置。设置时 7-12 位屏蔽个体异常,可通过启动进行初始设置或重置。0-5 位分别显示为无效运算、非法、被零除、溢出和精度。如欲获取详细信息,请参阅“更多信息”部分提供的链接。

 

 

 

图 1. XMM 寄存器覆盖 YMM 寄存器。


图 2 显示了英特尔® SSE 和英特尔® AVX 指令中使用的数据类型。对于英特尔® AVX,允许使用增加至 128 或 256 位的 32 位或 64 位浮点类型的倍数以及增加至 128 位的任意整数类型的倍数。

图 2. 英特尔® AVX 和英特尔® SSE 数据类型

 

指令通常分为标量版本和矢量版本,如图 3 所示。矢量版本通过将数据以并行“SIMD”模式在寄存器中处理进行运算;而标量版本则只在每个寄存器的一个条目中进行运算。这一区别减少了某些算法中的数据移动,提供了更加出色的整体吞吐量。

图 3. SIMD 与标量运算

 

当数据以 n 字节内存界限上存储的 n 字节块进行运算时,数据为内存对齐数据。例如,将 256 位数据加载至 YMM 寄存器中时,如果数据源为 256 位对齐,则数据被称为“对齐”.

对于英特尔® SSE 运算,除非明确规定,否则都需要内存对齐。例如,在英特尔® SSE 下,有针对内存对齐和内存未对齐运算的特定指令,如MOVAPD(移动对齐的紧缩双精度值)和 MOVUPD(移动非对齐的紧缩双精度值)指令。没有像这样分为两个的指令需要执行对齐访问。

英特尔® AVX 已经放宽了某些内存对齐要求,因此默认情况下,英特尔® AVX 允许未对齐的访问;但是,这样的访问将导致性能下降,因此旨在要求数据保持内存对齐的原规则仍然是不错的选择(面向 128 位访问的 16 位对齐和面向 256 位访问的 32 位对齐)。主要例外是明确指出需要内存对齐数据的 SSE 指令的VEX 扩展版本:这些指令仍然要求使用对齐的数据。其他要求对齐访问的特定指令请参阅英特尔® 高级矢量扩展指令集编程指南中的表2.4(请参阅“更多信息”中提供的链接)。

除了未对齐数据问题外,另外一个性能问题是混合使用旧的仅 XMM 的指令和较新的英特尔® AVX 指令会导致延迟,因此需要最大限度地控制 VEX 编码指令和旧的英特尔® SSE 代码之间的转换。也就是说,不要将 VEX 前缀的指令和非 VEX 前缀的指令混合使用,以实现最佳吞吐量。如果您非要这么做,请对同一 VEX/非 VEX 级别的指令进行分组,最大限度地控制二者之间的转换。此外,如果上层 YMM 位通过 VZEROUPPERVZEROALL 设置为零(编译器应自动插入),则无转换损失。该插入要求另外一个指令,因此推荐使用分析 (profiling)。

英特尔® AVX 指令类

如上所述,英特尔® AVX 增加了对许多新指令的支持并将当前的英特尔® SSE 指令扩展至新的 256 位寄存器,大部分旧的英特尔® SSE 指令都具有一个 V 前缀的英特尔® AVX 版本,以访问新的寄存器容量和 3 操作数形式。根据指令的计算方式,新英特尔® AVX 指令的数量多达几百个。

例如,旧的 2 操作数英特尔® SSE 指令ADDPS xmm1、xmm2/m128 现在可以使用 VADDPS ymm1、ymm2、ymm3/m256 形式以 3 操作数语法表示为 VADDPS xmm1、xmm2、xmm3/m128 或者 256 位寄存器。少数指令还支持 4 操作数,例如 VBLENDVPS ymm1、ymm2、ymm3/m256、ymm4 ,在一定条件下在 ymm4 中根据掩码将单精度浮点值由 ymm2 ymm3/m256 复制到 ymm1 。这是在之前的形式上进行了改进,其中 xmm0 为隐式需求,要求编译器解放 xmm0 。现在所有寄存器都明确可见,寄存器的分配有了更大的自由度。此处, m128 是一个 128 位内存位置, xmm1 是一个 128 位寄存器,以此类推。

有些新指令仅限 VEX(非英特尔® SSE 扩展指令),包括许多将数据移进或移出 YMM 寄存器的方法。以 VBROADCASTS[S/D] 为例,它将一个单一值加载至 XMM 或 YMM 寄存器的所有元素中,并提供了多种使用 VPERMILP[S/D] 将数据移至一个寄存器的方法。(方括号中的内容请参阅附录 A。)

英特尔® AVX 添加了算法指令,以使变量对单精度和双精度紧缩和标量浮点数据进行加、减、乘、除、平方根、比较、最小值、最大值和约数的运算。许多新的条件判定对于 128 位英特尔® SSE 也非常有用,提供了32 种比较类型。英特尔® AVX 还包括从之前的SIMD 中获得的指令,包括逻辑、混合、转换、测试、紧缩、解紧缩、移动、加载和存储。工具集还添加了新指令,包括非跳跃式获取(将单数据或多数据传播至 256 位目标,屏蔽移动基元进行有条件的加载和存储)、向 256 位 SIMD 寄存器中插入多 SIMD 数据或者从中提取多 SIMD 数据、在一个寄存器内转换基元进行数据处理、分支处理以及紧缩测试指令。


未来添加
英特尔® AVX 手册还列出了某些未来可能会使用到的指令,此处并未完全列出,有待补充。此处并不确保这些指令都如编写的那样可以实现。

预留两个指令(VCVTPH2PS VCVTPS2PH ),用于支持 16 位浮点转换为单和双浮点类型或者从中转换。16 位格式称之为半精度,具有一个 10 位尾数(非反向规格化数有一个隐含的以 1 开头的数,导致 11 位精度)、5 位指数(偏差 15)和 1 位符号。

拟定的 RDRAND 指令使用一个具有密码的安全硬件数字随机位生成器,为 16 位、32 位和 64 位寄存器生成随机数。成功后,进位标识被设置为 1 (CF=1 )。如果没有足够的熵值可用,进位标识将被清除 (CF=0 )。

最后,有四个指令(RDFDBASE、RDGSBASE、WRFSBASE 和 WRGSBASE)可在 64 位模式中以全部权限等级读取和写入 FS 和 GS 寄存器。

另外,以后还会添加 FMA 指令,执行类似 A = + A * B + C 的运算,右侧的加号 (+) 都可以变更为减号 (?),而且右侧的三个操作数顺序可以随意发生变化。还有交叉加法和减法形式。紧缩 FMA 指令可以执行具有 256 位矢量的 8 个单精度 FMA 运算或者 4 个双精度 FMA 运算。

A = A * B + C 这类的 FMA 运算要优于每次执行一个步骤,因为中间结果被视为无限精度,会在存储上执行约数计算,因此计算更为精确。这一单个约数是为“融合”前缀提供的。它们也比按步骤执行运算的速度要快。

每个指令对于操作数 A、B 和 C 都有三种形式的顺序,每个顺序对应一个 3 位扩展:形式 132 执行 A = AC + B,形式 213 执行 A = BA + C,形式 231 执行 A = BC + A。顺序数仅代表表达式右侧操作数的顺序。

可用性与支持

在硬件中检测英特尔® AVX 特性的可用性需要使用 CPUID 指令在 CPU 和操作系统中查询支持,稍后会做详细说明。2011 年第一季度推出的第二代英特尔® 酷睿™ 处理器(代号为 Sandy Bridge 的英特尔® 微架构)是英特尔首款支持英特尔® AVX 技术的处理器。这些处理器没有新的 FMA 指令。为了能够在没有硬件支持的情况下进行开发和测试,免费的英特尔® 软件开发仿真器(请参阅“更多信息”中提供的链接)提供了对这些特性的支持,包括英特尔® AVX、FMA、PCLMULQDQ 和 AES 指令。

为了能够在大多数设置中可靠地使用英特尔® AVX 扩展指令集,操作系统必须支持在线程环境切换中保存和加载新的寄存器(采用 XSAVE/XRSTOR),以预防数据损坏。为了避免出现此类错误,从支持英特尔® AVX 感知环境切换的操作系统明确地设置一个 CPU 位,以支持新的指令;否则,在使用英特尔® AVX 指令时,会生成一个未定义的操作码 (#UD) 异常。

带有 Service Pack 1 (SP1) 的 Microsoft Windows* 7 和带有 SP1 的 Microsoft Windows* Server 2008 R2(32 位和 64 位版本)以及更高版本的 Windows* 都支持英特尔® AVX 在线程和进程切换中进行保存和恢复。Linux* 内核 2.6.30(2009 年6 月)及更高版本也支持英特尔® AVX。

检测可用性与支持
对英特尔® AVX、FMA、AES 和 PCLMULQDQ 四个领域进行支持检测的步骤是类似的,都包括检查相应特性的硬件和操作系统支持(请参阅表 1)。包括以下步骤(计算位是从位 0 开始):

  1. 使用 CPUID.1:ECX.OSXSAVE bit 27 = 1 确认操作系统支持 XGETBV
  2. 同时,确认支持 CPUID.1:ECX bit 28=1 (支持英特尔® AVX)和/或 bit 25=1 (支持 AES)和/或 bit 12=1 (支持 FMA)和/或 bit 1=1 (PCLMULQDQ)。
  3. 发出 XGETBV ,并验证在位 1 和位 2 处特性支持的掩码是 11b(操作系统支持 XMM 状态和 YMM 状态)

表 1. 特性检测掩码

Feature Bits to check Constant
英特尔® AVX 28,、27 018000000H
VAES 28,、27, and 25 01A000000H
VPCLMULQDQ 28、 27, and 1 018000002H
FMA 28,、27, and 12 018001000H


条目 1 中提供了实施该进程的样例代码,其中 CONSTANT 是表 1 中的值。稍后将提供 Microsoft* Visual Studio* C++ 内联函数版本。

条目 1. 特性检测

INT Supports_Feature()
   { 
   ; result returned in eax
   mov eax, 1
   cpuid
   and ecx, CONSTANT
   cmp ecx, CONSTANT; check desired feature flags
   jne not_supported 
   ; processor supports features
   mov ecx, 0; specify 0 for XFEATURE_ENABLED_MASK register
   XGETBV; result in EDX:EAX
   and eax, 06H
   cmp eax, 06H; check OS has enabled both XMM and YMM state support
   jne not_supported
   mov eax, 1; mark as supported
   jmp done
   NOT_SUPPORTED:
   mov eax, 0 ; // mark as not supported
   done:
   }
 

用途

在最低的编程级别,大部分常用 x86 汇编器现在都支持英特尔® AVX、FMA、AES 和 VPCLMULQDQ 指令,包括 Microsoft MASM*(Microsoft Visual Studio* 2010 版本)、NASM*、FASM* 和 YASM*。请参阅各自的相关文档,获取详细信息。

对于语言编译器,英特尔® C++ 编译器 11.1 版及更高版本和英特尔® Fortran 编译器都可以通过编译器开关支持英特尔® AVX;而且这两种编译器还支持自动矢量化和浮点循环。英特尔® C++ 编译器支持英特尔® AVX 内联函数(使用 #include 访问内联函数)和内嵌汇编语言,甚至还可以使用 #include "avxintrin_emu.h" 支持英特尔® 内联函数模拟。

Microsoft Visual Studio* C++ 2010 (SP1) 及更高版本在编译 64 位置代码(使用 /arch:AVX 编译器开关)时支持英特尔® AVX(请参阅“更多信息”)。它使用 标头支持内联函数,但是不支持内嵌汇编语言。MASM*、代码的反汇编视图和寄存器的配置程序视图(完全支持 YMM)中都支持英特尔® AVX。

GNU Compiler Collection* (GCC*) 4.4 版通过同一标头 支持英特尔® AVX 内联函数。Binutils 2.20.51.0.1 及更高版本、gdb 6.8.50.20090915 及更高版本、最新版的 GNU 汇编器 (GAS) 以及 objdump 中还提供了其他 GNU 工具链支持。如果您的编译器不支持英特尔® AVX,您可以在许多情况下发出所需的字节,但是一流的支持能为您带来更多方便。

以上提及的三个 C++ 编译器都可以从 C 或 C++ 代码中使用英特尔® AVX 支持同一内联函数运算,从而简化运算。内联函数是编译器使用相应的汇编函数替换的函数。大部分英特尔® AVX 内联函数的命名都遵循以下格式:
 

_mm256_op_suffix(data_type param1, data_type param2, data_type param3)

其中 _mm256 是在新的 256 位寄存器上运行的前缀;_op 是运算,类似于加法 add 或者减法 sub;而 _suffix 则表示运算的数据类型,第一个字母表示紧缩 (p)、扩展紧缩 (ep) 或标量 (s)。表 2 中列出了其余字母所代表的类型。

表 2. 英特尔® AVX 后缀标记

Marking Meaning
[s/d] 单精度或双精度浮点
[i/u]nnn 位大小 nnn为 128、64、32、16 或 8
[ps/pd/sd] 紧缩单精度、紧缩双精度或标量双精度
epi32 扩展紧缩 32 位带符号整数
si256 标量 256 位整数


表 3 中列出了数据类型。前两个参数是源寄存器,第三个参数(显示时)是一个整数掩码、选择因子或偏移值。

表 3. 英特尔® AVX 内联函数数据类型

Type Meaning
__m256 256 位,作为 8 个单精度浮点值,表示一个 YMM 寄存器或内存位置
__m256d 256 位,作为 4 个双精度浮点值,表示一个 YMM 寄存器或内存位置
__m256i 256 位,作为整数、(字节、字等)
__m128 128 位单精度浮点(每个 32 位)
__m128d 128 位双精度浮点(每个 64 位)


某些内联函数位于其他标头中,如 中的 AES 和 PCLMULQDQ。查看您的编译器文档或者网站,了解各种内联函数的位置。

Microsoft Visual Studio* 2010
为了简单明了,本文中的以下部分将使用带有 SP1 的 Microsoft Visual Studio* 2010,英特尔® 编译器或 GCC* 上运行的代码与之类似。如果您依次点击 Project Properties > Configuration > Code Generation ,在Enable Enhanced Instruction Set 下选择Not Set ,然后将 /arch:AVX 手动添加至 Command Line 条目下的命令行,带有 SP1 的Microsoft Visual Studio* 2010 将自动生成英特尔® AVX 代码。作为使用内联函数的一个实例,条目 2 提供了基于内联函数的英特尔® AVX 特性检测例程。


条目 2. 基于内联函数的特性检测

// get AVX intrinsics
#include <immintrin.h>
// get CPUID capability
#include <intrin.h>

// written for clarity, not conciseness
#define OSXSAVEFlag (1UL<<27)
#define AVXFlag     ((1UL<<28)|OSXSAVEFlag)
#define VAESFlag    ((1UL<<25)|AVXFlag|OSXSAVEFlag)
#define FMAFlag     ((1UL<<12)|AVXFlag|OSXSAVEFlag)
#define CLMULFlag   ((1UL<< 1)|AVXFlag|OSXSAVEFlag)
 
bool DetectFeature(unsigned int feature)
	{
	int CPUInfo[4], InfoType=1, ECX = 1;
	__cpuidex(CPUInfo, 1, 1);       // read the desired CPUID format
	unsigned int ECX = CPUInfo[2];  // the output of CPUID in the ECX register. 
	if ((ECX & feature) != feature) // Missing feature 
		return false; 
	__int64 val = _xgetbv(0);       // read XFEATURE_ENABLED_MASK register
	if ((val&6) != 6)               // check OS has enabled both XMM and YMM support.
		return false; 
	return true;
	}
 

Mandelbrot 实例

为了使用新指令进行展示,使用了普通 C/C++ 代码(检查以确认编译器不会将代码转换为英特尔® AVX 指令!)和新的英特尔® AVX 指令(作为内联函数)来计算 Mandelbrot 集图像,对比它们的性能。Mandelbrot 集是在伪代码(如条目 3 所示)中定义的复杂数字上进行的计算密集型运算。


条目 3. Mandelbrot 伪代码

z,p are complex numbers
for each point p on the complex plane
	z = 0
	for count = 0 to max_iterations
		if abs(z) > 2.0
			break
		z = z*z+p
	set color at p based on count reached

常见的图像是位于长方形 (-2,-1) 至 (1,1) 中复杂平面部分之上的。可以通过多种方式着色(不在此做介绍)。提升最大迭代数,放大至其他部分并确定随着时间的推移,值是否会“消失”。

为了突出 CPU,将方框 (0.29768, 0.48364) 放大至 (0.29778, 0.48354),以多种大小计算计数网格并使用最大迭代 4096。充分着色时,最终的计数网格如图 4 中所示。
 

图 4. Mandelbrot 集 (0.29768, 0.48364) 至 (0.29778, 0.48354),采用最大迭代 4096
 


条目 4 中提供了计算迭代数的基本 C++ 实施。相比于 2 的复杂数字的绝对值替换为了相比于 4.0 的范数,通过删除平方根,速度几乎翻倍。对于所有版本,使用单精度浮点将尽可能多的元素紧缩至 YMM 寄存器,速度将更快,但是在以后进行放大后,与双精度相比,精度将降低。


条目 4. 简单的 Mandelbrot C++ 代码

 

// simple code to compute Mandelbrot in C++
#include <complex>
void MandelbrotCPU(float x1, float y1, float x2, float y2, 
                   int width, int height, int maxIters, unsigned short * image)
{
	float dx = (x2-x1)/width, dy = (y2-y1)/height;
	for (int j = 0; j < height; ++j)
		for (int i = 0; i < width; ++i)
		{
			complex<float> c (x1+dx*i, y1+dy*j), z(0,0);
			int count = -1;
			while ((++count < maxIters) && (norm(z) < 4.0))
				z = z*z+c;
			*image++ = count;
		}
}

针对性能测试多个版本:条目 4 中的基本版(一个类似 CPU 的版本,通过浮点扩展复杂类型);基于内联函数的 SSE 版本以及条目 5 中显示的基于内联函数的英特尔® AVX 版本。在 128×128、256×256、512×512、1024×1024、2048×2048 和 4096×4096 图像尺寸上对所有版本进行测试。每种实施在对更多工作保留了基本指令集限制的同时,性能可能会有所提升,但是它们应该能代表您可以实现的性能。

英特尔® AVX 版本经过精心设计,尽可能适合 16 个 YMM 寄存器。为了能够帮助追踪您希望如何分配它们,变量使用ymm0ymm15 进行命名。当然,编译器根据自身准则分配寄存器,但是为了考虑周到,您可以尝试使用这种方式将所有计算放入寄存器中。(实际上,从反汇编开始,编辑器就不能完美地分配它们,在汇编代码中重构它将是学习英特尔® AVX 的一次不错练习。)


条目 5. 英特尔® AVX 内联函数 Mandelbrot 实施

float dx = (x2-x1)/width;
float dy = (y2-y1)/height;
// round up width to next multiple of 8
int roundedWidth = (width+7) & ~7UL; 
 
float constants[] = {dx, dy, x1, y1, 1.0f, 4.0f};
__m256 ymm0 = _mm256_broadcast_ss(constants);   // all dx
__m256 ymm1 = _mm256_broadcast_ss(constants+1); // all dy
__m256 ymm2 = _mm256_broadcast_ss(constants+2); // all x1
__m256 ymm3 = _mm256_broadcast_ss(constants+3); // all y1
__m256 ymm4 = _mm256_broadcast_ss(constants+4); // all 1's (iter increments)
__m256 ymm5 = _mm256_broadcast_ss(constants+5); // all 4's (comparisons)
 
float incr[8]={0.0f,1.0f,2.0f,3.0f,4.0f,5.0f,6.0f,7.0f}; // used to reset the i position when j increases
__m256 ymm6 = _mm256_xor_ps(ymm0,ymm0); // zero out j counter (ymm0 is just a dummy)
 
for (int j = 0; j < height; j+=1)
{
	__m256 ymm7  = _mm256_load_ps(incr);  // i counter set to 0,1,2,..,7
	for (int i = 0; i < roundedWidth; i+=8)
	{
		__m256 ymm8 = _mm256_mul_ps(ymm7, ymm0);  // x0 = (i+k)*dx 
		ymm8 = _mm256_add_ps(ymm8, ymm2);         // x0 = x1+(i+k)*dx
		__m256 ymm9 = _mm256_mul_ps(ymm6, ymm1);  // y0 = j*dy
		ymm9 = _mm256_add_ps(ymm9, ymm3);         // y0 = y1+j*dy
		__m256 ymm10 = _mm256_xor_ps(ymm0,ymm0);  // zero out iteration counter
		__m256 ymm11 = ymm10, ymm12 = ymm10;        // set initial xi=0, yi=0
 
		unsigned int test = 0;
		int iter = 0;
		do
		{
			__m256 ymm13 = _mm256_mul_ps(ymm11,ymm11); // xi*xi
			__m256 ymm14 = _mm256_mul_ps(ymm12,ymm12); // yi*yi
			__m256 ymm15 = _mm256_add_ps(ymm13,ymm14); // xi*xi+yi*yi
 			
			// xi*xi+yi*yi < 4 in each slot
			ymm15 = _mm256_cmp_ps(ymm15,ymm5, _CMP_LT_OQ);        
			// now ymm15 has all 1s in the non overflowed locations
			test = _mm256_movemask_ps(ymm15)&255;      // lower 8 bits are comparisons
			ymm15 = _mm256_and_ps(ymm15,ymm4);
			// get 1.0f or 0.0f in each field as counters
			// counters for each pixel iteration
			ymm10 = _mm256_add_ps(ymm10,ymm15);        
 
			ymm15 = _mm256_mul_ps(ymm11,ymm12);        // xi*yi 
			ymm11 = _mm256_sub_ps(ymm13,ymm14);        // xi*xi-yi*yi
			ymm11 = _mm256_add_ps(ymm11,ymm8);         // xi <- xi*xi-yi*yi+x0 done!
			ymm12 = _mm256_add_ps(ymm15,ymm15);        // 2*xi*yi
			ymm12 = _mm256_add_ps(ymm12,ymm9);         // yi <- 2*xi*yi+y0	
 
			++iter;
		} while ((test != 0) && (iter < maxIters));
 
		// convert iterations to output values
		__m256i ymm10i = _mm256_cvtps_epi32(ymm10);
 
		// write only where needed
		int top = (i+7) < width? 8: width&7;
		for (int k = 0; k < top; ++k)
			image[i+k+j*width] = ymm10i.m256i_i16[2*k];
 
		// next i position - increment each slot by 8
		ymm7 = _mm256_add_ps(ymm7, ymm5);
		ymm7 = _mm256_add_ps(ymm7, ymm5);
	}
	ymm6 = _mm256_add_ps(ymm6,ymm4); // increment j counter
}

所有版本的完全代码和 Microsoft Visual Studio* 2010 (SP1) 项目(包括测试用具)的更多信息,请参阅“更多信息”部分提供的链接。

结果如图 5 和图 6 所示。为了避免太多数字受缚于特定的 CPU 速度,图 5 显示了每个版本相对应的 CPU 版本的性能,展示了算法的一个简单非 SIMD C/C++ 实施。按照预计,英特尔® SSE 版本总共执行 4 次,因为每通道执行 4 像素;而英特尔® AVX 版本和 CPU 版本一样,总共执行 8 次。由于循环、内存访问、非最佳指令顺序和其他因素都需要开销,提高 4 倍和 8 倍应该最佳,因此首次尝试时相当不错。


图 5. 不同大小的相对性能


图 6 中的第二个图表显示每毫秒计算的像素对于每单位大小来说是不变的;此外,算法显示从 CPU 到英特尔® SSE 版本的性能提升了四倍,而从英特尔® SSE 到英特尔® AVX 版本的性能提升了两倍。


图 6. 不同大小的绝对性能

 

结论

本文提供了新的英特尔® 高级矢量扩展指令集(英特尔® AVX)的中级概述。这些扩展指令集与之前的英特尔® SSE 指令类似,但是提供了更大的寄存器空间并添加了新的指令。Mandelbrot 实例显示了与之前的技术相比所提升的性能比率。有关详细信息,请查看“英特尔® 高级矢量扩展指令集编程指南”(请在“更多信息”部分查找该文件的链接)。

编程快乐!

更多信息

英特尔® 高级矢量扩展指令集编程指南: http://redfort-software.intel.com/en-us/avx/

联邦信息处理标准出版物 197,“发布高级加密标准”:http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf

The IEEE 754-2008 floating-point format standard at http://en.wikipedia.org/wiki/IEEE_754-2008

IEEE 754-2008 浮点格式标准:http://en.wikipedia.org/wiki/IEEE_754-2008

64 位驱动程序浮点支持:http://msdn.microsoft.com/en-us/library/ff545910.aspx

Mandelbrot 集的维基百科条目:http://en.wikipedia.org/wiki/Mandelbrot_set

英特尔® 软件开发仿真器:http://redfort-software.intel.com/en-us/articles/intel-software-development-emulator

下载完整的 Mandelbrot 英特尔® AVX 实施:http://www.lomont.org/

 

 

作者简介

Chris Lomont 是 Cybernet Systems 的一名研发工程师,曾参与过量子计算算法、NASA 图像处理、美国国土安全部门安全软件的开发和计算机取证分析等项目。他拥有普渡大学的博士学位,此外还拥有物理学、数学和计算机科学的学士学位。作为一名游戏程序员,他还短期从事过金融建模、机器人制造和各种咨询工作。他在业余时间喜欢与妻子一起徒步旅行,还爱好看电影、做专题座谈、娱乐性编程、数学研究、研究物理、听音乐和做各种实验。您可以访问他的个人网站:www.lomont.org或者他的电子设备网站:http://www.hypnocube.com/

 

 

附录 A:指令集参考

许多指令都是紧缩或标量形式,即它们在寄存器的多个并行元素或者一个单一元素上执行——标记为 [P/S]。条目长度分为双精度或单精度浮点(简称为双精度单精度),标记为 [D/S];整数形式分为字节、字、双字和四字,标记为 [B/W/D/Q]。整数形式有时还分为带符号形式和无符号形式,标记为 [S/U] 。有些指令在寄存器的高区或低区运行,标记为 [H/L] ;下文的表格中提供了其他可选元素。英特尔® SSE 形式和英特尔® AVX 形式中的指令以英特尔® AVX 形式的 (V) 为前缀,支持 3 操作数和 256 位寄存器。方括号 ( []) 中的条目为必选;圆括号 ( ()) 中的条目为可选。

范例:

 

  • (V)ADD[P/S][D/S] 是紧缩或标量、双精度或单精度相加,有 8 种可能的形式——VADDPD, VADDPS, VADDSD, VADDSS, 和不以 V开头的版本。
  • (V)[MIN/MAX][P/S][D/S]表示最大或最小双精度或单精度紧缩或标量的 16 种不同指令。

下一表格列出了多个比较类型。VEX 前缀的指令具有 32 个比较类型;非 VEX 前缀的比较仅支持圆括号中的 8 个类型。每个比较类型分为多种形式,其中O = 有序的、U = 无序的、S = 发信号、Q = 不发信号。有序/无序表示如果一个操作数是 NaN(浮点中的非数),比较是 false 还是 true,在计算过程中出现某些错误时会发生该情形,如被 0 除或者负数的平方根。发信号/不发信号表示至少一个操作数是 QnaN(用于错误捕获的静态非数)时是否会出现一个异常。

 

 

 

Type Flavors Meaning
EQ (OQ), UQ, OS, US 等于
LT (OS), OQ 小于
LE (OS), OQ 小于等于
UNORD (Q), S 无序测试 (NaN)
NEQ (UQ), US, OQ, OS 不等于
NLT (US), UQ 不小于
NLE (US), UQ 不小于等于
ORD (Q), S 有序测试 (非 NaN)
NGE US, UQ 不大于等于
NGT US, UQ 不大于
FALSE OQ, OS 比较一直是 false
GE OS, OQ 大于等于
GT OS, OQ 大于
TRUE UQ, US 比较一直是 true


最后,我们在此提供了所有英特尔® AVX 指令:

 

 

 

 

Arithmetic Description
(V)[ADD/SUB/MUL/DIV][P/S][D/S] 加/减/乘/除紧缩/标量双精度/单精度
(V)ADDSUBP[D/S] 紧缩双精度/单精度加减交互指数
(V)DPP[D/S] 点积,基于即时任务
(V)HADDP[D/S] 水平相加
(V)[MIN/MAX][P/S][D/S] 最小/最大紧缩/标量双精度/单精度
(V)MOVMSKP[D/S] 双精度/单精度符号掩码开方
(V)PMOVMSKB 生成包括大部分重要位的掩码
(V)MPSADBW 多个绝对差值求和
(V)PABS[B/W/D] 字节/字/双字上的紧缩绝对值
(V)P[ADD/SUB][B/W/D/Q] 加/减紧缩字节/字/双字/四字
(V)PADD[S/U]S[B/W] 紧缩带符号/无符号加饱和字节/字
(V)PAVG[B/W] 平均紧缩字节/字
(V)PCLMULQDQ Carry-less 乘法四字
(V)PH[ADD/SUB][W/D] 紧缩垂直加/减字/双字
(V)PH[ADD/SUB]SW 紧缩垂直加/减饱和
(V)PHMINPOSUW 最小垂直无符号字和位置
(V)PMADDWD 乘加紧缩整数
(V)PMADDUBSW 无符号字节和带符号字节乘以带符号字
(V)P[MIN/MAX][S/U][B/W/D] 最小/最大紧缩带符号/无符号整数
(V)PMUL[H/L][S/U]W 乘以紧缩带符号/无符号整数和存储高区/低区结果
(V)PMULHRSW 使用约数和移位乘以紧缩无符号
(V)PMULHW 乘以紧缩整数和存储高区结果
(V)PMULL[W/D] 乘以紧缩整数和存储低区结果
(V)PMUL(U)DQ 乘以紧缩带符号/无符号双字整数和存储四字
(V)PSADBW 计算无符号字节绝对差值总和
(V)PSIGN[B/W/D] 根据其他操作数上的符号更改一个操作数中每个元素的符号
(V)PS[L/R]LDQ 操作数中字节左/右移位量
(V)SL[L/AR/LR][W/D/Q] 位左移/算法右移/逻辑右移
(V)PSUB(U)S[B/W] 紧缩带符号/无符号减去带符号/无符号饱和
(V)RCP[P/S]S 计算紧缩/标量单精度的近似倒数
(V)RSQRT[P/S]S 计算紧缩/标量单精度平方根的近似倒数
(V)ROUND[P/S][D/S] 紧缩/标量双精度/单精度的约数
(V)SQRT[P/S][D/S] 紧缩/标量双精度/单精度的平方根
VZERO[ALL/UPPER] 将 YMM 寄存器的全部/上层归零

 

 

 

Comparison Description
(V)CMP[P/S][D/S] 比较紧缩/标量双精度/单精度
(V)COMIS[S/D] 比较标量双精度/单精度,设置 EFLAGS
(V)PCMP[EQ/GT][B/W/D/Q] 比较紧缩整数等于/大于
(V)PCMP[E/I]STR[I/M] 比较显式/隐式长度字符串,返回指数/掩码

 

 

Control Description
V[LD/ST]MXCSR 加载/存储 MXCSR 控制/状态寄存器
XSAVEOPT 保存优化的处理器延伸状态

 

 

Conversion Description
(V)CVTx2y 将类型 x 转换为类型 y,其中 xy 从以下选择:
DQ 和 P[D/S],
[P/S]S 和[P/S]D, or
S[D/S] 和 SI.

 

 

Load/store Description
VBROADCAST[SS/SD/F128] 通过传播进行加载(将单个数值加载到多个位置)
VEXTRACTF128 128 位浮点值开方
(V)EXTRACTPS 紧缩单精度开方
VINSERTF128 插入紧缩浮点值
(V)INSERTPS 插入紧缩单精度值
(V)PINSR[B/W/D/Q] 插入整数
(V)LDDQU 移动四倍未对齐整数
(V)MASKMOVDQU 使用非暂时提示NT Hint存储双四字中的指定字节
VMASKMOVP[D/S] 有条件的 SIMD 紧缩加载/存储
(V)MOV[A/U]P[D/S] 移动对齐/未对齐的紧缩双精度/单精度
(V)MOV[D/Q] 移动双字/四字
(V)MOVDQ[A/U] 移动对齐/未对齐的双字至四字
(V)MOV[HL/LH]P[D/S] 移动高区到低区/低区到高区的紧缩双精度/单精度
(V)MOV[H/L]P[D/S] 移动高区/低区的紧缩双精度/单精度
(V)MOVNT[DQ/PD/PS] 使用非暂时提示移动紧缩整数/双精度/单精度
(V)MOVNTDQA 使用对齐的非暂时提示移动紧缩整数
(V)MOVS[D/S] 移动或合并标量双精度/单精度
(V)MOVS[H/L]DUP 移动单个奇/偶指数的单精度
(V)PACK[U/S]SW[B/W] 在字节/字上对无符号/带符号饱和进行紧缩
(V)PALIGNR 字节对齐
(V)PEXTR[B/W/D/Q] 整数开方
(V)PMOV[S/Z]X[B/W/D][W/D/Q] 使用带符号/零扩展紧缩移动(仅根据长度,不支持 DD、DW 等)

 

 

Logical Description
(V)[AND/ANDN/OR]P[D/S] 紧缩双精度/单精度值的位逻辑 AND/AND NOT/OR
(V)PAND(N) 逻辑AND (NOT)
(V)P[OR/XOR] 位逻辑 logical OR/exclusive OR
(V)PTEST 紧缩位测试,如果位逻辑 AND为 all 0,则设置零标记
(V)UCOMIS[D/S] 无序比较标量双精度/单精度并设置EFLAGS
(V)XORP[D/S] 紧缩双精度/单精度的位逻辑 XOR

 

 

Shuffle Description
(V)BLENDP[D/S] 混合紧缩双精度/单精度,基于掩码选择元素
(V)BLENDVP[D/S] 混合值
(V)MOVDDUP 向所有值中复制偶数值
(V)PBLENDVB 变量混合紧缩字节
(V)PBLENDW 混合紧缩字
VPERMILP[D/S] 转换双精度/单精度值
VPERM2F128 转换浮点值
(V)PSHUF[B/D] 根据即时值移动紧缩字节/双字
(V)PSHUF[H/L]W 移动紧缩高区/低区字
(V)PUNPCK[H/L][BW/WD/DQ/QDQ] 解紧缩高区/低区数据
(V)SHUFP[D/S] 移动紧缩双精度/单精度
(V)UNPCK[H/L]P[D/S] 解紧缩和交错紧缩变量双精度/单精度值

 

 

AES Description
AESENC/AESENCLAST 执行一轮 AES 加密
AESDEC/AESDECLAST 执行一轮 AES 解密
AESIMC 执行 AES InvMixColumn 变换
AESKEYGENASSIST AES 次轮密钥生成协助

 

 

Future Instructions Description
[RD/WR][F/G]SBASE 读/写 FS/GS 寄存器
RDRAND 读取随机数(至 r16、r32、r64)
VCVTPH2PS 将 16 位浮点值转换为单精度浮点值
VCVTPS2PH 将单精度值转换为 16 位浮点值

 

 

FMA Each [z] is the string 132 or 213 or 231, giving the order the operands A,B,C are used in:
132 is A=AC+B
213 is A=AB+C
231 is A=BC+A
VFMADD[z][P/S][D/S] 面向双精度/单精度紧缩/标量的融合乘加 A = r1 * r2 + r3
VFMADDSUB[z]P[D/S] 融合乘交互加/减紧缩双精度/单精度 A = r1 * r2 + r3(奇指数),A = r1 * r2-r3(偶指数)
VFMSUBADD[z]P[D/S] 融合乘交互减/加紧缩双精度/单精度 A = r1 * r2 - r3(奇指数),A = r1 * r2+r3(偶指数)
VFMSUB[z][P/S][D/S] 面向双精度/单精度值紧缩/标量的融合乘减 A = r1 * r2 - r3
VFNMADD[z][P/S][D/S] 紧缩/标量双精度/单精度的融合负数乘加 A = -r1 * r2+r3
VFNMSUB[z][P/S][D/S] 紧缩/标量双精度/单精度的融合负数乘减 A = -r1 * r2-r3

 

如欲了解有关编译器优化的更多完整信息请参阅我们的优化声明

有关编译器优化的更完整信息,请参阅优化通知