博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[C] 跨平台使用Intrinsic函数范例3——使用MMX、SSE2指令集 处理 32位整数数组求和...
阅读量:5278 次
发布时间:2019-06-14

本文共 6837 字,大约阅读时间需要 22 分钟。

作者:。

  本文面对对SSE等SIMD指令集有一定基础的读者,以32位整数数组求和为例演示了如何跨平台使用MMX、SSE2指令集。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。

 

一、关键讲解

  前文()演示了如何使用SSE、AVX指令集 处理 单精度浮点数组求和。现在对其进行改造,使用MMX、SSE2指令集 处理 32位整数数组求和。因程序基本上差不多,文本就不详细讲解了,只说关键变化。

1.1 指令集简介

  先来看看支持32位整数的SIMD的指令集——

MMX指令集支持多种整数类型的运算。MMX定义了64位紧缩整数类型,,对应Intrinsic中的__m64类型,它能一次能处理2个32位整数。
SSE指令集只支持单精度浮点运算,直到SSE2指令集才支持双精度浮点数运算。SSE2定义了128位紧缩整数类型,对应Intrinsic中的__m128i类型,它能一次能处理4个32位整数。
AVX指令集只支持单精度和双精度浮点运算。据说2013年Haswell架构中的AVX2指令集才支持整数运算。

1.2 改造为 SSE2的32位整数代码

  在使用Intrinsic函数时,将 SSE的单精度浮点代码 改造为 SSE2的32位整数代码是很方便的。对比前文与本文的数组求和代码,变更的地方有——

float

int32_t

备注

指令 Intrinsic Asm 指令 Intrinsic Asm
      MMX __m64 MMWORD 类型
    _mm_setzero_si64 PXOR 赋0
    * MOVQ 加载
    _mm_add_pi32 PADDD 加法
SSE __m128 XMMWORD SSE2 __m128i XMMWORD 类型
_mm_setzero_ps XORPS _mm_setzero_si128 PXOR 赋0
_mm_load_ps MOVAPS _mm_load_si128 MOVQ 加载
_mm_add_ps ADDPS _mm_add_epi32 PADDD 加法
AVX __m256 YMMWORD       类型
_mm256_setzero_ps VXORPS     赋0
_mm256_load_ps VMOVAPS     加载
_mm256_add_ps VADDPS     加法

  其次,还需要调整一下地址计算。因_mm_load_si128与_mm_load_ps不同,是直接采用__m128i指针一次性处理128位,而不是以元素宽度(如float、int32_t),所以循环与地址计算的代码有较大变化——

1. p指针的类型由“const float*”变为“const __m128i*”。为了适应_mm_load_si128。
2. q指针的含义发生了变化。现在作为单个数据处理时所用指针,即处理SIMD结果的合并,又处理剩下的数据。
3. p指针移动时直接“p++”。而四路循环版中移动指针是“p+=4”,加载时可以写成“_mm_load_si128(p+1)”,地址计算也很方便。

  例如sumfloat_sse与sumint_sse——

// 单精度浮点数组求和_SSE版.float sumfloat_sse(const float* pbuf, size_t cntbuf){    float s = 0;    // 求和变量.    size_t i;    size_t nBlockWidth = 4;    // 块宽. SSE寄存器能一次处理4个float.    size_t cntBlock = cntbuf / nBlockWidth;    // 块数.    size_t cntRem = cntbuf % nBlockWidth;    // 剩余数量.    __m128 xfsSum = _mm_setzero_ps();    // 求和变量。[SSE] 赋初值0    __m128 xfsLoad;    // 加载.    const float* p = pbuf;    // SSE批量处理时所用的指针.    const float* q;    // 将SSE变量上的多个数值合并时所用指针.    // SSE批量处理.    for(i=0; i

 

1.3 改造为 MMX版

  将SSE2版代码 改造为 MMX版代码也很方便,按照上一节的表格换用不同的数据类型和函数名,然后再调整一下地址计算就差不多了。

  只不过有两点要注意——

1. MMX运算结束后,要记得调用_mm_empty(EMMS)清理MMX状态,使后续的浮点运算(FPU)能正常运行。
2. MMX Intrinsic中没有提供_mm_load_si64这样的函数,要想从内存中加载数据到__m64变量,可以直接使用“*(指针)”运算符加载数据,但要保证地址是按8字节对齐的。

  例如sumint_mmx函数(可与上一节的sumint_sse函数进行比较)——

// 32位整数数组求和_MMX版.int32_t sumint_mmx(const int32_t* pbuf, size_t cntbuf){    int32_t s = 0;    // 求和变量.    size_t i;    size_t nBlockWidth = 2;    // 块宽. MMX寄存器能一次处理2个int32_t.    size_t cntBlock = cntbuf / nBlockWidth;    // 块数.    size_t cntRem = cntbuf % nBlockWidth;    // 剩余数量.    __m64 midSum = _mm_setzero_si64();    // 求和变量。[MMX] PXOR, 赋初值0.    __m64 midLoad;    // 加载.    const __m64* p = (const __m64*)pbuf;    // MMX批量处理时所用的指针.    const int32_t* q;    // 单个数据处理时所用指针.    // MMX批量处理.    for(i=0; i

 

1.4 环境检查

  最后,别忘了检查环境——

INTRIN_MMX、INTRIN_SSE2 宏是 zintrin.h 提供的,可用来在编译时检测编译器是否支持MMX、SSE2指令集。
simd_mmx、simd_sse_level函数是 ccpuid.h 提供的,可用来在运行时检测当前系统环境是否支持MMX、SSE2指令集。

二、全部代码

2.1 simdsumint.c

  全部代码——

#define __STDC_LIMIT_MACROS    1    // C99整数范围常量. [纯C程序可以不用, 而C++程序必须定义该宏.]#include 
#include
#include
#include "zintrin.h"#include "ccpuid.h"// Compiler name#define MACTOSTR(x) #x#define MACROVALUESTR(x) MACTOSTR(x)#if defined(__ICL) // Intel C++# if defined(__VERSION__)# define COMPILER_NAME "Intel C++ " __VERSION__# elif defined(__INTEL_COMPILER_BUILD_DATE)# define COMPILER_NAME "Intel C++ (" MACROVALUESTR(__INTEL_COMPILER_BUILD_DATE) ")"# else# define COMPILER_NAME "Intel C++"# endif // # if defined(__VERSION__)#elif defined(_MSC_VER) // Microsoft VC++# if defined(_MSC_FULL_VER)# define COMPILER_NAME "Microsoft VC++ (" MACROVALUESTR(_MSC_FULL_VER) ")"# elif defined(_MSC_VER)# define COMPILER_NAME "Microsoft VC++ (" MACROVALUESTR(_MSC_VER) ")"# else# define COMPILER_NAME "Microsoft VC++"# endif // # if defined(_MSC_FULL_VER)#elif defined(__GNUC__) // GCC# if defined(__CYGWIN__)# define COMPILER_NAME "GCC(Cygmin) " __VERSION__# elif defined(__MINGW32__)# define COMPILER_NAME "GCC(MinGW) " __VERSION__# else# define COMPILER_NAME "GCC " __VERSION__# endif // # if defined(_MSC_FULL_VER)#else# define COMPILER_NAME "Unknown Compiler"#endif // #if defined(__ICL) // Intel C++//// sumint: 32位整数数组求和的函数//// 32位整数数组求和_基本版.//// result: 返回数组求和结果.// pbuf: 数组的首地址.// cntbuf: 数组长度.int32_t sumint_base(const int32_t* pbuf, size_t cntbuf){ int32_t s = 0; // 求和变量. size_t i; for(i=0; i
= SIMD_SSE_2) { runTest("sumint_sse", sumint_sse); // 32位整数数组求和_SSE版. runTest("sumint_sse_4loop", sumint_sse_4loop); // 32位整数数组求和_SSE四路循环展开版. }#endif // #ifdef INTRIN_SSE2 return 0;}

 

2.2 makefile

  全部代码——

# flagsCC = g++CFS = -Wall -msse2# argsRELEASE =0BITS =CFLAGS =# [args] 生成模式. 0代表debug模式, 1代表release模式. make RELEASE=1.ifeq ($(RELEASE),0)    # debug    CFS += -gelse    # release    CFS += -O3 -DNDEBUG    //CFS += -O3 -g -DNDEBUGendif# [args] 程序位数. 32代表32位程序, 64代表64位程序, 其他默认. make BITS=32.ifeq ($(BITS),32)    CFS += -m32else    ifeq ($(BITS),64)        CFS += -m64    else    endifendif# [args] 使用 CFLAGS 添加新的参数. make CFLAGS="-mavx".CFS += $(CFLAGS).PHONY : all clean# filesTARGETS = simdsumintOBJS = simdsumint.oall : $(TARGETS)simdsumint : $(OBJS)    $(CC) $(CFS) -o $@ $^simdsumint.o : simdsumint.c zintrin.h ccpuid.h    $(CC) $(CFS) -c $

 

三、编译测试

3.1 编译

  在以下编译器中成功编译——

VC6:x86版。
VC2003:x86版。
VC2005:x86版。
VC2010:x86版、x64版。
GCC 4.7.0(Fedora 17 x64):x86版、x64版。
GCC 4.6.2(MinGW(20120426)):x86版。
GCC 4.7.1(TDM-GCC(MinGW-w64)):x86版、x64版。
llvm-gcc-4.2(Mac OS X Lion 10.7.4, Xcode 4.4.1):x86版、x64版。

3.2 测试

  因虚拟机上的有效率损失,于是仅在真实系统上进行测试。

  系统环境——

CPU:Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz
操作系统:Windows 7 SP1 x64版

  然后分别运行VC与GCC编译的Release版可执行文件,即以下4个程序——

exe\simdsumint_vc32.exe:VC2010 SP1 编译的32位程序,/O2 /arch:SSE2。
exe\simdsumint_vc64.exe:VC2010 SP1 编译的64位程序,/O2 /arch:SSE2。
exe\simdsumint_gcc32.exe:GCC 4.7.1(TDM-GCC(MinGW-w64)) 编译的32位程序,-O3 -mss2。
exe\simdsumint_gcc64.exe:GCC 4.7.1(TDM-GCC(MinGW-w64)) 编译的64位程序,-O3 -mss2。

  测试结果(使用cmdarg_ui)——

 

参考文献——

《Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes:1, 2A, 2B, 2C, 3A, 3B, and 3C》044US. August 2012.
《Intel® Architecture Instruction Set Extensions Programming Reference》014. AUGUST 2012.
《AMD64 Architecture Programmer’s Manual Volume 4: 128-Bit and 256-Bit Media Instructions》. December 2011.
《[C] 让VC、BCB支持C99的整数类型(stdint.h、inttypes.h)(兼容GCC)》.
《[C] zintrin.h: 智能引入intrinsic函数 V1.01版。改进对Mac OS X的支持,增加INTRIN_WORDSIZE宏》.
《[C/C++] ccpuid:CPUID信息模块 V1.03版,改进mmx/sse指令可用性检查(使用signal、setjmp,支持纯C)、修正AVX检查Bug》.
《[x86]SIMD指令集发展历程表(MMX、SSE、AVX等)》.
《SIMD(MMX/SSE/AVX)变量命名规范心得》.
《GCC 64位程序的makefile条件编译心得——32位版与64位版、debug版与release版(兼容MinGW、TDM-GCC)》.
《[C#] cmdarg_ui:“简单参数命令行程序”的通用图形界面》. 
《[C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)》.

源码下载——

 

转载于:https://www.cnblogs.com/zyl910/archive/2012/10/26/simdsumint.html

你可能感兴趣的文章
awk 统计
查看>>
模板设计模式的应用
查看>>
实训第五天
查看>>
平台维护流程
查看>>
2012暑期川西旅游之总结
查看>>
12010 解密QQ号(队列)
查看>>
2014年辛星完全解读Javascript第一节
查看>>
装配SpringBean(一)--依赖注入
查看>>
java选择文件时提供图像缩略图[转]
查看>>
方维分享系统二次开发, 给评论、主题、回复、活动 加审核的功能
查看>>
Matlab parfor-loop并行运算
查看>>
string与stringbuilder的区别
查看>>
2012-01-12 16:01 hibernate注解以及简单实例
查看>>
iOS8统一的系统提示控件——UIAlertController
查看>>
PAT甲级——1101 Quick Sort (快速排序)
查看>>
python创建进程的两种方式
查看>>
1.2 基础知识——关于猪皮(GP,Generic Practice)
查看>>
迭代器Iterator
查看>>
java易错题----静态方法的调用
查看>>
php建立MySQL数据表
查看>>