深入探讨用位掩码代替分支(8):SSE指令集速度测试

  在上一篇测试了MMX指令集,这次我们来测试SSE指令集。说的更精确一点,是测试SSE2指令集。
  本篇致力于解决以下问题——
1.SSE/SSE2指令集是什么?
2.如何阅读Intel/AMD的手册?
3.如何运用SSE指令集?如何将MMX代码升级为SSE代码。
4.如何在VC++6.0这样的高级语言编译器中使用MMX指令集?

一、简介

  1999 年 Intel 推出了第 1 代的 SSE(Streaming SIMD Extensions)指令以回击 AMD 的 3DNow! 指令,使用在 Pentium III 处理器上。随后 AMD 在 2001 年 10 月 发布 的 Athlon XP 处理器上首次加入了 SSE 指令集。
  2001 年 Intel 推出第 2 个版本的 SSE 指令,使用在 Pentium 4 处理器上,AMD 在 2003 年推出的 Athlon 64 和 Opteron 处理器上加入对 SSE2 指令的支持。
  SSE有很多后续版本,详见——
http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html
[x86]SIMD指令集发展历程表(MMX、SSE、AVX等)

1.1 概述

  SSE技术对x86体系的编程环境的扩展是——
1.8个128位的SSE寄存器(xmm0~xmm7)。 // 64位环境下增加到16个寄存器(xmm0~xmm15)。
2.SSE数据类型(紧缩单精度浮点)。而在SSE2中,又增加了整数和双精度浮点类型。
3.SSE指令系统。

1.2 SSE寄存器

  SSE寄存器集是由8个128位寄存器组成,见下图。SSE指令使用寄存器名xmm0~xmm7直接访问SSE寄存器。


  还有一个新的控制/状态寄存器MXCSR,用于屏蔽/开放数值异常处理、设置舍入方式、设置清零方式和观察状态标志。
  与之前的MMX或3DNow!不同,这些寄存器并不是原来己有的寄存器(MMX和3DNow!均是使用x87浮点数寄存器),所以不需要像MMX或3DNow!一样,要使用x87指令之前,需要利用一个EMMS指令来清除寄存器的状态。因此,不像MMX或3DNow!指令,SSE运算指令,可以很自由地和x87浮点指令,或是MMX指令共用。

  2003年,AMD发表的x86-64 延伸架构,为SSE技术增加了8个寄存器,共16个寄存器(xmm0~xmm15)。

(Figure 4-1. SSE Registers。出自AMD手册 Vol.1 112)


1.3 SSE数据类型

  SSE技术定义了以下数据类型——
1.128位紧缩单精度浮点(128-Bit Packed Single-Precision Floating-Point):4个单精度浮点(single)紧缩成一个128位。

(Figure 10-4. 128-Bit Packed Single-Precision Floating-Point Data Type。出自Intel手册 Vol.1 10-8)

  而在SSE2中,又定义了以下5种数据类型——
1.128位紧缩双精度浮点(128-Bit Packed Double-Precision Floating-Point):2个双精度浮点(double)紧缩成一个128位。
2.128位紧缩字节(128-Bit Packed Byte Integers):16个字节(byte)紧缩成一个128位。
3.128位紧缩字(128-Bit Packed Word Integers):8个字(word)紧缩成一个128位。
4.128位紧缩双字(128-Bit Packed Doubleword Integers):4个双字(doubleword)紧缩成一个128位。
5.128位紧缩四字(128-Bit Packed Quadword Integers):2个四字(quadword)紧缩成一个128位。

(Figure 11-2. Data Types Introduced with the SSE2 Extensions。出自Intel手册 Vol.1 11-5)

  因为“将64位像素转为32位像素”这项工作需要字节(byte)和字(word),所以只有SSE2能满足需求。


二、指令解读

  对于PACKUSWB指令来说,操作数不仅可以是MMX寄存器(出自MMX指令集),也可以是SSE寄存器(出自SSE2指令集),甚至可以是AVX寄存器(出自AVX指令集。本文不讨AVX指令集)。虽然实际的机器码有所不同。
  对于这一点,Intel手册的编排方案是——以指令名为准,然后再在那一节内分别介绍不同寄存器的效果。
  而AMD的手册不一样,它按寄存器长度分成两个文档——
1.SSE、AVS指令在第4卷中(AMD64 Architecture Programmer's Manual Volume 4: 128-bit and 256 bit media instructions)。
2.MMX、3DNow!、浮点指令在第5卷中(AMD64 Architecture Programmer's Manual Volume 5: 64-Bit Media and x87 Floating-Point Instructions)。

2.1 Intel手册原文

  略。与前一篇(http://www.cnblogs.com/zyl910/archive/2012/04/09/noifopex7.html)的“2.1 Intel手册对PACKUSWB指令的说明”相同。

  PS:SSE编程指南见“Volume 1: Basic Architecture”的“Chapter 10 Programming with Streaming SIMD Extensions (SSE)”和“Chapter 11 Programming with Streaming SIMD Extensions 2 (SSE2)”。不属于本文范畴,读者可自行翻阅。

2.2 AMD手册原文

  找到AMD手册的第4卷(AMD64 Architecture Programmer's Manual Volume 4: 128-bit and 256 bit media instructions)。如果没有的话,请在官网下载——
http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals

  打开第4卷(26568_APM_v4.pdf)。在左侧的书签树中依次展开“2 Instruction Reference”,然后拖动滚动条找到“PACKUSWB”——

图4

  AMD手册对PACKUSWB指令的说明有两页,上图(图4)是第一页的内容。第二页内容不属于本文范畴,故不贴图,读者可自行翻阅。

  PS:SSE编程指南见“Volume 1: Application Programming”的“4 Streaming SIMD Extensions Media and Scientific Programming”。不属于本文范畴,读者可自行翻阅。

2.3 手册解读

  首先看Intel手册,图1最上面的那个方框内,列出了PACKUSWB指令在不同环境下(MMX、SSE、AVX)的效果。

  本篇只关心SSE指令集。此时该指令的格式为“PACKUSWB xmm1, xmm2/m128”,描述信息为“Converts 8 signed word integers from xmm1 and 8 signed word integers from xmm2/m128 into 16 unsigned byte integers in xmm1 using unsigned saturation.”。

  而在AMD手册(图4)中,对该指令的描述是“Converts 16-bit signed integers in xmm1 and xmm2 or mem128 into 8-bit signed integers with saturation. Writes packed results to xmm1.”。

  在Intel手册PACKUSWB指令的第二页(图2)中,有解释该指令功能的伪代码——

PACKUSWB (with 128-bit operands)
DEST[7:0]← SaturateSignedWordToUnsignedByte (DEST[15:0]);
DEST[15:8] ← SaturateSignedWordToUnsignedByte (DEST[31:16]);
DEST[23:16] ← SaturateSignedWordToUnsignedByte (DEST[47:32]);
DEST[31:24] ← SaturateSignedWordToUnsignedByte (DEST[63:48]);
DEST[39:32] ← SaturateSignedWordToUnsignedByte (DEST[79:64]);
DEST[47:40] ← SaturateSignedWordToUnsignedByte (DEST[95:80]);
DEST[55:48] ← SaturateSignedWordToUnsignedByte (DEST[111:96]);
DEST[63:56] ← SaturateSignedWordToUnsignedByte (DEST[127:112]);
DEST[71:64] ← SaturateSignedWordToUnsignedByte (SRC[15:0]);
DEST[79:72] ← SaturateSignedWordToUnsignedByte (SRC[31:16]);
DEST[87:80] ← SaturateSignedWordToUnsignedByte (SRC[47:32]);
DEST[95:88] ← SaturateSignedWordToUnsignedByte (SRC[63:48]);
DEST[103:96] ← SaturateSignedWordToUnsignedByte (SRC[79:64]);
DEST[111:104] ← SaturateSignedWordToUnsignedByte (SRC[95:80]);
DEST[119:112] ← SaturateSignedWordToUnsignedByte (SRC[111:96]);
DEST[127:120] ← SaturateSignedWordToUnsignedByte (SRC[127:112]);


  该伪代码的大致含义为——将DEST中的 每16位的带符号整数 饱和转换为 8位的无符号整数,放到返回值(DEST)的低64位;将SRC中的每16位的整数 饱和转换为 8位的整数,放到返回值的高64位。
注:x86指令的2操作数指令一般是——第1个参数是DEST,第2个参数是SRC。即参数格式为“PACKUSWB DEST, SRC”。


2.4 画图解释

  用文字或伪代码来解释MMX指令都不太直观,用图片就直观多了。

  这一次AMD的手册没有配图。

  因此,我画了一张图片,更能清晰表示SSE2模式下PACKUSWB指令的功能——

  该图的风格与第7篇(http://www.cnblogs.com/zyl910/archive/2012/04/09/noifopex7.html)的图片类似,左侧是内存中的源数据,右侧是运算结果,中间是SSE寄存器,箭头代表运算过程。该图 绘有三种操作——
1.加载(红色箭头)。将内存中的源数据(源缓冲区)加载到SSE寄存器。因为SSE寄存器是128位(16字节)的,所以该环节共加载了32字节数据,分别加载到2个SSE寄存器中(PACKUSWB需要两个操作数)。
2.运算(绿色将头)。这里就是PACKUSWB指令的功能,将 每个16位的带符号整数 饱和转换为 8位的无符号整数。
3.存储(蓝色箭头)。将SSE寄存器中的运算结果 存储到内存(目标缓冲区)。


三、如何在VC中使用MMX指令集?

  对于Visual C++ 6.0来说,依次打上SP5、PP5补丁后,就能支持MMX、SSE、SSE2这三套指令集。它们的下载地址是——
SP5(Visual Studio 6.0 Service Pack 5):http://www.microsoft.com/download/en/details.aspx?id=2618
PP5(Visual C++ 6.0 Processor Pack):http://msdn.microsoft.com/en-us/library/aa718349.aspx

  对于更高版本Visual Studio,它们内置了对MMX指令集的支持,不需要安装补丁。详见——
http://www.cnblogs.com/zyl910/archive/2012/02/28/vs_intrin_table.html
Intrinsics头文件与SIMD指令集、Visual Studio版本对应表

3.1 使用内嵌汇编

  在VC中,最直接的办法就是使用内嵌汇编,即利用“_asm”关键字直接写汇编语句。
  例如下面那段代码,先尝试执行cpuid指令检查CPU特性,再尝试执行xorps指令测试系统中是否能运行SSE指令——

// 检测SSE系列指令集的支持级别
int    simd_sse_level()
{const DWORD    BIT_D_SSE = 0x02000000;    // bit 25const DWORD    BIT_D_SSE2 = 0x04000000;    // bit 26const DWORD    BIT_C_SSE3 = 0x00000001;    // bit 0const DWORD    BIT_C_SSSE3 = 0x00000100;    // bit 9const DWORD    BIT_C_SSE41 = 0x00080000;    // bit 19const DWORD    BIT_C_SSE42 = 0x00100000;    // bit 20BYTE    rt = SIMD_SSE_NONE;    // result
    DWORD    v_edx;DWORD    v_ecx;// check processor support
    __try {_asm {mov eax, 1cpuidmov v_edx, edxmov v_ecx, ecx}}__except (EXCEPTION_EXECUTE_HANDLER){return SIMD_SSE_NONE;}if ( v_edx & BIT_D_SSE ){rt = SIMD_SSE_1;if ( v_edx & BIT_D_SSE2 ){rt = SIMD_SSE_2;if ( v_ecx & BIT_C_SSE3 ){rt = SIMD_SSE_3;if ( v_ecx & BIT_C_SSSE3 ){rt = SIMD_SSE_3S;if ( v_ecx & BIT_C_SSE41 ){rt = SIMD_SSE_41;if ( v_ecx & BIT_C_SSE42 ){rt = SIMD_SSE_42;}}}}}}// check OS support
    __try {_asm{xorps xmm0, xmm0    // executing any SSE instruction
        }}__except (EXCEPTION_EXECUTE_HANDLER){return SIMD_SSE_NONE;}return rt;
}

http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html
[VC6] 检查MMX和SSE系列指令集的支持级别(最高SSE4.2)


3.2 使用Intrinsics函数

  SSE指令也有对应的Intrinsics函数。
  在MSDN中可以找到SSE/SSE2的Intrinsics函数帮助:http://msdn.microsoft.com/en-us/library/y0dh78ez(v=vs.110).aspx。具体的目录层次是——
MSDN Library
Development Tools and Languages
Visual Studio 11 Beta
Visual C++  Reference
C/C++ Languages
Compiler Intrinsics
MMX, SSE, and SSE2 Intrinsics

  SSE/SSE2的Intrinsics函数的应用方法,推荐Alex Farber的《Introduction to SSE Programming》——
http://www.codeproject.com/Articles/4522/Introduction-to-SSE-Programming
Introduction to SSE Programming

  中文翻译版见——
http://dev.gameres.com/Program/Other/sseintro.htm
基于SSE指令集的程序设计简介


四、实际应用

  现在我们想使用PACKUSWB指令,怎么知道该指令对应的Intrinsics函数呢?
  最简单的办法就是查阅Intel手册。在Intel手册PACKUSWB指令的第三页(图3),列出Intrinsic函数的名称——

Intel C/C++ Compiler Intrinsic Equivalent
PACKUSWB: __m64 _mm_packs_pu16(__m64 m1, __m64 m2)
PACKUSWB: __m128i _mm_packus_epi16(__m128i m1, __m128i m2)


  因现在是探讨SSE指令集,所以应该选用_mm_packus_epi16函数。

  现在万事具备,可以完成SSE版的“将64位像素转为32位像素”函数了——

// 饱和处理SSE版
void f5_sse(BYTE* pbufD, const signed short* pbufS, int cnt)
{//const signed short* pS = pbufS;//BYTE* pD = pbufD;const __m128i* pS = (const __m128i*)pbufS;__m128i* pD = (__m128i*)pbufD;int i;for(i=0; i<cnt; i+=4){// 同时对四个像素做饱和处理。即 将四个64位像素(4通道,每分量为带符号16位) 转为 四个32位像素(每分量为无符号8位)。pD[0] = _mm_packus_epi16(pS[0], pS[1]);    // 饱和方式数据打包(带符号16位->无符号8位)。等价于 for(i=0;i<8;++i){ pD[0].uB[i]=SU(pS[0].iW[i]); r.uB[8+i]=SU(pS[1].iW[i]); }// nextpS += 2;pD += 1;}
}

 


  因SSE2版PACKUSWB指令(_mm_packus_epi16)的功能,现在的内循环变得十分简单,一次就能处理4个像素。所以每次循环时“i+=4”。

  注意这里将pbufS、pbufD这两个指针均设定为__m128i指针类型。所以“pD += 1”实际上将指针地址前移了16个字节,而“pS += 2”将指针地址前移了32个字节。


五、全部代码

  全部代码——

// MMX, SSE, SSE2
#include <emmintrin.h>// 用位掩码做饱和处理.用求负生成掩码
#define LIMITSU_FAST(n, bits) ( (n) & -((n) >= 0) | -((n) >= (1<<(bits))) )
#define LIMITSU_SAFE(n, bits) ( (LIMITSU_FAST(n, bits)) & ((1<<(bits)) - 1) )
#define LIMITSU_BYTE(n) ((BYTE)(LIMITSU_FAST(n, 8)))// 用位掩码做饱和处理.用带符号右移生成掩码
#define LIMITSW_FAST(n, bits) ( ( (n) | ((signed short)((1<<(bits)) - 1 - (n)) >> 15) ) & ~((signed short)(n) >> 15) )
#define LIMITSW_SAFE(n, bits) ( (LIMITSW_FAST(n, bits)) & ((1<<(bits)) - 1) )
#define LIMITSW_BYTE(n) ((BYTE)(LIMITSW_FAST(n, 8)))// 数据规模
#define DATASIZE    16384    // 128KB / (sizeof(signed short) * 4)// 缓冲区。SSE需要按128位对齐
__declspec(align(16)) signed short    bufS[DATASIZE*4];    // 源缓冲区。64位的颜色(4通道,每通道16位)
__declspec(align(16)) BYTE    bufD[DATASIZE*4];    // 目标缓冲区。32位的颜色(4通道,每通道8位)// 测试时的函数类型
typedef void (*TESTPROC)(BYTE* pbufD, const signed short* pbufS, int cnt);// http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html
// SSE系列指令集的支持级别. simd_sse_level 函数的返回值。
#define SIMD_SSE_NONE    0    // 不支持
#define SIMD_SSE_1    1    // SSE
#define SIMD_SSE_2    2    // SSE2
#define SIMD_SSE_3    3    // SSE3
#define SIMD_SSE_3S    4    // SSSE3
#define SIMD_SSE_41    5    // SSE4.1
#define SIMD_SSE_42    6    // SSE4.2const char*    simd_sse_names[] = {"None","SSE","SSE2","SSE3","SSSE3","SSE4.1","SSE4.2",
};// 是否支持MMX指令集
BOOL    simd_mmx()
{const DWORD    BIT_DX_MMX = 0x00800000;    // bit 23
    DWORD    v_edx;// check processor support
    __try {_asm {mov eax, 1cpuidmov v_edx, edx}}__except (EXCEPTION_EXECUTE_HANDLER){return FALSE;}if ( v_edx & BIT_DX_MMX ){// check OS support
        __try {_asm{pxor mm0, mm0    // executing any MMX instruction
                emms}return TRUE;}__except (EXCEPTION_EXECUTE_HANDLER){}}return FALSE;
}// 检测SSE系列指令集的支持级别
int    simd_sse_level()
{const DWORD    BIT_D_SSE = 0x02000000;    // bit 25const DWORD    BIT_D_SSE2 = 0x04000000;    // bit 26const DWORD    BIT_C_SSE3 = 0x00000001;    // bit 0const DWORD    BIT_C_SSSE3 = 0x00000100;    // bit 9const DWORD    BIT_C_SSE41 = 0x00080000;    // bit 19const DWORD    BIT_C_SSE42 = 0x00100000;    // bit 20BYTE    rt = SIMD_SSE_NONE;    // result
    DWORD    v_edx;DWORD    v_ecx;// check processor support
    __try {_asm {mov eax, 1cpuidmov v_edx, edxmov v_ecx, ecx}}__except (EXCEPTION_EXECUTE_HANDLER){return SIMD_SSE_NONE;}if ( v_edx & BIT_D_SSE ){rt = SIMD_SSE_1;if ( v_edx & BIT_D_SSE2 ){rt = SIMD_SSE_2;if ( v_ecx & BIT_C_SSE3 ){rt = SIMD_SSE_3;if ( v_ecx & BIT_C_SSSE3 ){rt = SIMD_SSE_3S;if ( v_ecx & BIT_C_SSE41 ){rt = SIMD_SSE_41;if ( v_ecx & BIT_C_SSE42 ){rt = SIMD_SSE_42;}}}}}}// check OS support
    __try {_asm{xorps xmm0, xmm0    // executing any SSE instruction
        }}__except (EXCEPTION_EXECUTE_HANDLER){return SIMD_SSE_NONE;}return rt;
}// 用if分支做饱和处理
void f0_if(BYTE* pbufD, const signed short* pbufS, int cnt)
{const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分别对4个通道做饱和处理pD[0] = (pS[0]<0) ? 0 : ( (pS[0]>255) ? 255 : (BYTE)pS[0] );pD[1] = (pS[1]<0) ? 0 : ( (pS[1]>255) ? 255 : (BYTE)pS[1] );pD[2] = (pS[2]<0) ? 0 : ( (pS[2]>255) ? 255 : (BYTE)pS[2] );pD[3] = (pS[3]<0) ? 0 : ( (pS[3]>255) ? 255 : (BYTE)pS[3] );// nextpS += 4;pD += 4;}
}// 用min、max饱和处理
void f1_min(BYTE* pbufD, const signed short* pbufS, int cnt)
{const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分别对4个通道做饱和处理pD[0] = min(max(0, pS[0]), 255);pD[1] = min(max(0, pS[1]), 255);pD[2] = min(max(0, pS[2]), 255);pD[3] = min(max(0, pS[3]), 255);// nextpS += 4;pD += 4;}
}// 用位掩码做饱和处理.用求负生成掩码
void f2_neg(BYTE* pbufD, const signed short* pbufS, int cnt)
{const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分别对4个通道做饱和处理pD[0] = LIMITSU_BYTE(pS[0]);pD[1] = LIMITSU_BYTE(pS[1]);pD[2] = LIMITSU_BYTE(pS[2]);pD[3] = LIMITSU_BYTE(pS[3]);// nextpS += 4;pD += 4;}
}// 用位掩码做饱和处理.用带符号右移生成掩码
void f3_sar(BYTE* pbufD, const signed short* pbufS, int cnt)
{const signed short* pS = pbufS;BYTE* pD = pbufD;int i;for(i=0; i<cnt; ++i){// 分别对4个通道做饱和处理pD[0] = LIMITSW_BYTE(pS[0]);pD[1] = LIMITSW_BYTE(pS[1]);pD[2] = LIMITSW_BYTE(pS[2]);pD[3] = LIMITSW_BYTE(pS[3]);// nextpS += 4;pD += 4;}
}// 饱和处理MMX版
void f4_mmx(BYTE* pbufD, const signed short* pbufS, int cnt)
{//const signed short* pS = pbufS;//BYTE* pD = pbufD;const __m64* pS = (const __m64*)pbufS;__m64* pD = (__m64*)pbufD;int i;for(i=0; i<cnt; i+=2){// 同时对两个像素做饱和处理。即 将两个64位像素(4通道,每分量为带符号16位) 转为 两个32位像素(每分量为无符号8位)。pD[0] = _mm_packs_pu16(pS[0], pS[1]);    // 饱和方式数据打包(带符号16位->无符号8位)。等价于 for(i=0;i<4;++i){ pD[0].uB[i]=SU(pS[0].iW[i]); pD[0].uB[4+i]=SU(pS[1].iW[i]); }// nextpS += 2;pD += 1;}// MMX状态置空
    _mm_empty();
}// 饱和处理SSE版
void f5_sse(BYTE* pbufD, const signed short* pbufS, int cnt)
{//const signed short* pS = pbufS;//BYTE* pD = pbufD;const __m128i* pS = (const __m128i*)pbufS;__m128i* pD = (__m128i*)pbufD;int i;for(i=0; i<cnt; i+=4){// 同时对四个像素做饱和处理。即 将四个64位像素(4通道,每分量为带符号16位) 转为 四个32位像素(每分量为无符号8位)。pD[0] = _mm_packus_epi16(pS[0], pS[1]);    // 饱和方式数据打包(带符号16位->无符号8位)。等价于 for(i=0;i<8;++i){ pD[0].uB[i]=SU(pS[0].iW[i]); r.uB[8+i]=SU(pS[1].iW[i]); }// nextpS += 2;pD += 1;}
}// 进行测试
void runTest(char* szname, TESTPROC proc)
{const int nLoop = 16;    // 使用MMX/SSE指令时速度太快了,只好再多循环几次int i,j,k;DWORD    tm0, tm1;    // 存储时间for(i=1; i<=3; ++i)    // 多次测试
    {//tm0 = GetTickCount();tm0 = timeGetTime();// mainfor(k=1; k<=nLoop; ++k){for(j=1; j<=4000; ++j)    // 重复运算几次延长时间,避免计时精度问题
            {proc(bufD, bufS, DATASIZE);}}// show//tm1 = GetTickCount() - tm0;tm1 = timeGetTime() - tm0;printf("%s[%d]:\t%.1f\n", szname, i, (double)tm1/nLoop);// check//if (1==i)//{//    // 检查结果//    for(j=0; j<=16; ++j)//    printf("[%d]:\t%d\t%u\n", j, bufS[j], bufD[j]);//}
}
}int main(int argc, char* argv[])
{int i;    // 循环变量//printf("Hello World!\n");printf("== noif:VC6 SIMD ==");// 初始化
    srand( (unsigned)time( NULL ) );for(i=0; i<DATASIZE*4; ++i){bufS[i] = (signed short)((rand()&0x1FF) - 128);    // 使数值在 [-128, 383] 区间
    }// 准备开始。可以将将进程优先级设为实时if (argc<=1){printf("<Press any key to continue>");getch();printf("\n");}// 进行测试//runTest("f0_if", f0_if);//runTest("f1_min", f1_min);//runTest("f2_neg", f2_neg);//runTest("f3_sar", f3_sar);if (simd_mmx())    runTest("f4_mmx", f4_mmx);if (simd_sse_level()>=SIMD_SSE_2)    runTest("f5_sse", f5_sse);// 结束前提示if (argc<=1){printf("<Press any key to exit>");getch();printf("\n");}return 0;
}


  注意——
1.SSE需要按128位对齐。于是bufS、bufD的声明中增加了“__declspec(align(16))”。
2.在调用测试函数时,需要先检查是否支持相应的指令集——

    if (simd_mmx())    runTest("f4_mmx", f4_mmx);if (simd_sse_level()>=SIMD_SSE_2)    runTest("f5_sse", f5_sse);



六、测试结果

  在32位winXP上的测试结果——

== noif:VC6 SIMD ==<Press any key to continue>
f4_mmx[1]:      37.5
f4_mmx[2]:      37.3
f4_mmx[3]:      38.9
f5_sse[1]:      25.6
f5_sse[2]:      26.1
f5_sse[3]:      25.7

 

  在64位win7上的测试结果——

== noif:VC6 SIMD ==<Press any key to continue>
f4_mmx[1]:      37.1
f4_mmx[2]:      37.1
f4_mmx[3]:      36.1
f5_sse[1]:      25.4
f5_sse[2]:      24.4
f5_sse[3]:      25.3

 

  硬件环境——
CPU:Intel Core i3-2310M, 2100 MHz
内存:DDR3-1066

 

参考文献——
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. December 2011. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《AMD64 Architecture Programmer's Manual Volume 4: 128-bit and 256 bit media instructions》. December 2011. http://support.amd.com/us/Processor_TechDocs/26568_APM_v4.pdf
《Pentium Ⅱ/Ⅲ体系结构及扩展技术》。刘清森、马鸣锦、吴灏等著。国防工业出版社,2000年7月。
《Introduction to SSE Programming》。Alex Farber 著。http://www.codeproject.com/Articles/4522/Introduction-to-SSE-Programming
《基于SSE指令集的程序设计简介》(Introduction to SSE Programming)。?译。http://dev.gameres.com/Program/Other/sseintro.htm
《Pentium III处理器的SSE入门》。Bipin Patwardhan著,foenix译。http://www.vckbase.com/document/viewdoc/?id=322
《在C/C++代码中使用SSE等指令集的指令(1)介绍》。gengshenghong著。http://blog.csdn.net/gengshenghong/article/details/7007100
《x86 指令集发展历程》。mik著。http://www.mouseos.com/x64/SIMD/x86_ISA.html

 

源码下载——
http://files.cnblogs.com/zyl910/noifVC6s.rar
(建议阅读编译器生成的汇编代码,位于Release\noifVC6s.asm)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295783.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql字段简索引_MySQL优化看这一篇就够了

本文概要概述为什么要优化系统的吞吐量瓶颈往往出现在数据库的访问速度上随着应用程序的运行&#xff0c;数据库的中的数据会越来越多&#xff0c;处理时间会相应变慢数据是存放在磁盘上的&#xff0c;读写速度无法和内存相比如何优化设计数据库时&#xff1a;数据库表、字段的…

.NET 6 Talk Party 2|.NET Core 与行业

关注我们微软 Reactor 为帮助广开发者&#xff0c;技术爱好者&#xff0c;更好的学习 .NET Core, C#, Python&#xff0c;数据科学&#xff0c;机器学习&#xff0c;AI&#xff0c;区块链, IoT 等技术&#xff0c;将每周三到周六&#xff0c;组织 3~5 场线上分享活动。欢迎跟着…

施一公:中国还缺乏真正的世界顶尖大学,研究生该听听这些建议

全世界只有3.14 % 的人关注了爆炸吧知识“中国的科技发展很快&#xff0c;变得很大&#xff0c;但还不够强&#xff1b;中国的人才众多&#xff0c;变得很大&#xff0c;但还不够强。中国是一个高等教育大国&#xff0c;但从权威的世界大学排名来看&#xff0c;中国缺乏真正的世…

Android之Fragment 真正的完全解析(上)

转载出处&#xff1a;http://blog.csdn.net/lmj623565791/article/details/37970961 自从Fragment出现&#xff0c;曾经有段时间&#xff0c;感觉大家谈什么都能跟Fragment谈上关系&#xff0c;做什么都要问下Fragment能实现不~~~哈哈&#xff0c;是不是有点过~~~ 本篇博客力求…

第零讲.1 tapestry项目创建与运行

2019独角兽企业重金招聘Python工程师标准>>> 1、在eclipse工程目录下创建项目&#xff1a; 第一次运行eclipse的时候会弹出选择工程项目存放地点&#xff0c;如我的存放路径D:\workspace。我们就把tapestry创建的项目放到这个目录方便统一管理。 打开系统的命令提示…

阿里云-数据盘挂载

2019独角兽企业重金招聘Python工程师标准>>> 硬盘分区及挂载操作步骤&#xff1a; 1. 查看未挂载的硬盘&#xff08;名称为/dev/xvdb&#xff09; # fdisk -l Disk /dev/xvdb doesnt contain a valid partition table 2. 创建分区 # fdisk /dev/xvdb ... 输入n Comm…

composer 查看php 版本_最常用的PHP版本:PHP 7.3取代7.2

php中文网最新课程每日17点准时技术干货分享自2014年以来&#xff0c;Private Packagist的联合创始人Jordi Boggiano一直在撰写半年度报告&#xff0c;介绍各种PHP版本的使用情况。他从packagist.io上的Composer安装中获取数据库。从2019年5月开始&#xff0c;PHP 7.3已在2019年…

钱少也就算了,为啥我们还越来越忙?

全世界只有3.14 % 的人关注了爆炸吧知识你是否时常感觉自己的生活总是不富裕&#xff0c;但工作却越来越忙&#xff1f;那是因为我们想得到的东西太多&#xff0c;但能够燃烧的生命却太少。那些对未来的焦虑、恐惧&#xff0c;说白了&#xff0c;就是想的太多。随着经历和阅历的…

Hello Blazor:(14)CSS隔离

前言上次我们说到&#xff0c;FindRazorSourceFile使用有一定限制.查看它的源码&#xff0c;发现它仅查找以b-开头属性名的HTML元素&#xff1a;function getScope(element: Element): string | null {return element.getAttributeNames().filter(name > name.startsWith(b-…

CSS- 横向和纵向时间轴

时间轴在展示公司发展信息&#xff0c;服务流程中用的比较多&#xff0c;常见的注册登录有的是通过引导&#xff0c;一步一步的来完成&#xff0c;上面会通过时间轴告诉用户当前在哪一步&#xff0c;公司在关于我们或者发展流程的时候也特别喜欢用时间轴来展示&#xff0c;简单…

互联网巨头基于全球产业链打造ARM CPU

日前&#xff0c;“四十大盗”发布服务器CPU屠龙710。就“四十大盗”公司公布的数据来看&#xff0c;屠龙710是一款非常优秀的ARM芯片&#xff0c;在SPECInt2017基础测试中屠龙710跑分达到440分&#xff0c;超过行业标杆20%。不过&#xff0c;和以前FT、HW的ARM服务器CPU类似&a…

modbus 台达a2_驱控智造未来 台达重磅发布多款工业自动化新品

呼应智能制造发展需求&#xff0c;8月22日&#xff0c;“驱控智造未来-——2019台达工业自动化新品发布会”在北京举行&#xff0c;台达推出PC-Based运动控制器AX864E系列、伺服驱动系统ASDA-B3系列、精巧迷你型矢量控制变频器ME300系列、高功能通用型矢量控制变频器C2000 Plus…

当充气娃娃过于逼真......

1 朋友一生一起走....▼2 总裁爹是被吓到了吗&#xff1f;▼3 我宣布&#xff1a;本届舞林大会&#xff0c;冠军已出&#xff01;▼4 哥哥&#xff0c;我来了&#xff01;▼5 为了防疫&#xff0c;泰国的小朋友们很不容易▼6 这位爸爸真的是非常巧妙了&#xff01;▼7 生…

(转)Android基础类之BaseAdapter

BaseAdapter就Android应用程序中经常用到的基础数据适配器&#xff0c;它的主要用途是将一组数据传到像ListView、Spinner、Gallery及GridView等UI显示组件&#xff0c;它是继承自接口类Adapter&#xff0c;1、Adapter类简介1)、Adapter相关类结构如下图所示&#xff1a;自定义…

手把手教你学Dapr - 4. 服务调用

介绍通过使用服务调用&#xff0c;您的应用程序可以使用标准的gRPC或HTTP协议与其他应用程序可靠、安全地通信。为什么不直接用HttpClientFactory呢先问几个问题&#xff1a;如何发现和调用不同服务的方法如何安全地调用其他服务&#xff0c;并对方法应用访问控制如何处理重试和…

高速的二舍八入三七作五_有没有发现,高速收费都是5的倍数,这是为什么?怎么判断的?...

要想富&#xff0c;先修路。这是劳动人民致富的第一要素。从最早的泥巴路到现在的高速公路&#xff0c;人民生活有了翻天覆地的变化。随后路上汽车的数量也就越来越多了&#xff0c;堵车便成为很常见的现象&#xff0c;所以就有了高架桥、立交桥的诞生&#xff0c;它们的出现很…

清华大学再出神人,汽车被盗,用“贪心算法”瞬间找到偷车贼

全世界只有3.14 % 的人关注了爆炸吧知识惹谁都不要惹会算法的人今天要讲的故事的主人公&#xff0c;是来自圣母大学计算机系的副教授史戈宇。就在之前&#xff0c;他还经历了一场惊心动魄的劫车事件。度假旅游遇劫匪在某一个周末&#xff0c;史教授计划着开车带一家人去百慕大度…

Android之RecyclerView 实现真正的Gallery效果

简介&#xff1a; RecyclerView是support-v7包中的新组件&#xff0c;是一个强大的滑动组件&#xff0c;与经典的ListView相比&#xff0c;同样拥有item回收复用的功能&#xff0c;但是直接把viewholder的实现封装起来&#xff0c;用户只要实现自己的viewholder就可以了&#x…

手把手教你学Dapr - 2. 必须知道的概念

Sidecar 边车Dapr API提供Http和gRPC两种通讯方式。运行方式则可以是容器也可以是进程&#xff08;Windows开发推荐使用Self Hosted&#xff0c;后续会解释&#xff09;。这样的好处是与运行环境无关&#xff0c;且独立运行不需要应用包含Dapr运行时的代码。只需要通过SDK集成即…

office打开服务器文件提示内存不足,打开Excel2016提示内存或磁盘空间不足的解决方法...

摘要在Excel2016或者2013中打开新建的空白文档提示&#xff1a;内存或磁盘空间不足&#xff0c;Microsoft Excel无法再次打开或保存任何文档。问题描述在Excel2016或者2013中打开新建的空白文档提示&#xff1a;内存或磁盘空间不足&#xff0c;Microsoft Excel无法再次打开或保…