[C] 跨平台使用Intrinsic函數范例3——使用MMX、SSE2指令集 處理 32位整數數組求和


作者:zyl910

  本文面對對SSE等SIMD指令集有一定基礎的讀者,以32位整數數組求和為例演示了如何跨平台使用MMX、SSE2指令集。支持vc、gcc編譯器,在Windows、Linux、Mac這三大平台上成功運行。

 

一、關鍵講解

  前文(http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html)演示了如何使用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<cntBlock; ++i)
    {
        xfsLoad = _mm_load_ps(p);    // [SSE] 加載
        xfsSum = _mm_add_ps(xfsSum, xfsLoad);    // [SSE] 單精浮點緊縮加法
        p += nBlockWidth;
    }
    // 合並.
    q = (const float*)&xfsSum;
    s = q[0] + q[1] + q[2] + q[3];

    // 處理剩下的.
    for(i=0; i<cntRem; ++i)
    {
        s += p[i];
    }

    return s;
}

// 32位整數數組求和_SSE版.
int32_t sumint_sse(const int32_t* pbuf, size_t cntbuf)
{
    int32_t s = 0;    // 求和變量.
    size_t i;
    size_t nBlockWidth = 4;    // 塊寬. SSE寄存器能一次處理4個int32_t.
    size_t cntBlock = cntbuf / nBlockWidth;    // 塊數.
    size_t cntRem = cntbuf % nBlockWidth;    // 剩余數量.
    __m128i xidSum = _mm_setzero_si128();    // 求和變量。[SSE2] PXOR. 賦初值0.
    __m128i xidLoad;    // 加載.
    const __m128i* p = (const __m128i*)pbuf;    // SSE批量處理時所用的指針.
    const int32_t* q;    // 單個數據處理時所用指針.

    // SSE批量處理.
    for(i=0; i<cntBlock; ++i)
    {
        xidLoad = _mm_load_si128(p);    // [SSE2] MOVDQA. 加載.
        xidSum = _mm_add_epi32(xidSum, xidLoad);    // [SSE2] PADDD. 32位整數緊縮環繞加法.
        p ++;
    }
    // 合並.
    q = (const int32_t*)&xidSum;
    s = q[0] + q[1] + q[2] + q[3];

    // 處理剩下的.
    q = (const int32_t*)p;
    for(i=0; i<cntRem; ++i)
    {
        s += q[i];
    }

    return s;
}

 

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<cntBlock; ++i)
    {
        midLoad = *p;    // [MMX] MOVQ. 加載.
        midSum = _mm_add_pi32(midSum, midLoad);    // [MMX] PADDD. 32位整數緊縮環繞加法.
        p ++;
    }
    // 合並.
    q = (const int32_t*)&midSum;
    s = q[0] + q[1];

    // 處理剩下的.
    q = (const int32_t*)p;
    for(i=0; i<cntRem; ++i)
    {
        s += q[i];
    }

    // 清理MMX狀態.
    _mm_empty();    // [MMX] EMMS.

    return s;
}

 


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 <stdlib.h>
#include <stdio.h>
#include <time.h>

#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<cntbuf; ++i)
    {
        s += pbuf[i];
    }
    return s;
}

#ifdef INTRIN_MMX
// 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<cntBlock; ++i)
    {
        midLoad = *p;    // [MMX] MOVQ. 加載.
        midSum = _mm_add_pi32(midSum, midLoad);    // [MMX] PADDD. 32位整數緊縮環繞加法.
        p ++;
    }
    // 合並.
    q = (const int32_t*)&midSum;
    s = q[0] + q[1];

    // 處理剩下的.
    q = (const int32_t*)p;
    for(i=0; i<cntRem; ++i)
    {
        s += q[i];
    }

    // 清理MMX狀態.
    _mm_empty();    // [MMX] EMMS.

    return s;
}

// 32位整數數組求和_MMX四路循環展開版.
int32_t sumint_mmx_4loop(const int32_t* pbuf, size_t cntbuf)
{
    int32_t s = 0;    // 返回值.
    size_t i;
    size_t nBlockWidth = 2*4;    // 塊寬. MMX寄存器能一次處理2個int32_t,然后循環展開4次.
    size_t cntBlock = cntbuf / nBlockWidth;    // 塊數.
    size_t cntRem = cntbuf % nBlockWidth;    // 剩余數量.
    __m64 midSum = _mm_setzero_si64();    // 求和變量。[MMX] PXOR, 賦初值0.
    __m64 midSum1 = _mm_setzero_si64();
    __m64 midSum2 = _mm_setzero_si64();
    __m64 midSum3 = _mm_setzero_si64();
    __m64 midLoad;    // 加載.
    __m64 midLoad1;
    __m64 midLoad2;
    __m64 midLoad3;
    const __m64* p = (const __m64*)pbuf;    // MMX批量處理時所用的指針.
    const int32_t* q;    // 單個數據處理時所用指針.

    // SSE批量處理.
    for(i=0; i<cntBlock; ++i)
    {
        midLoad = *p;    // [MMX] MOVQ. 加載.
        midLoad1 = *(p+1);
        midLoad2 = *(p+2);
        midLoad3 = *(p+3);
        midSum = _mm_add_pi32(midSum, midLoad);    // [MMX] PADDD. 32位整數緊縮環繞加法.
        midSum1 = _mm_add_pi32(midSum1, midLoad1);
        midSum2 = _mm_add_pi32(midSum2, midLoad2);
        midSum3 = _mm_add_pi32(midSum3, midLoad3);
        p += 4;    // 四路循環展開.
    }
    // 合並.
    midSum = _mm_add_pi32(midSum, midSum1);    // 兩兩合並(0~1).
    midSum2 = _mm_add_pi32(midSum2, midSum3);    // 兩兩合並(2~3).
    midSum = _mm_add_pi32(midSum, midSum2);    // 兩兩合並(0~3).
    q = (const int32_t*)&midSum;
    s = q[0] + q[1];

    // 處理剩下的.
    q = (const int32_t*)p;
    for(i=0; i<cntRem; ++i)
    {
        s += q[i];
    }

    // 清理MMX狀態.
    _mm_empty();    // [MMX] EMMS.

    return s;
}
#endif    // #ifdef INTRIN_MMX


#ifdef INTRIN_SSE2
// 32位整數數組求和_SSE版.
int32_t sumint_sse(const int32_t* pbuf, size_t cntbuf)
{
    int32_t s = 0;    // 求和變量.
    size_t i;
    size_t nBlockWidth = 4;    // 塊寬. SSE寄存器能一次處理4個int32_t.
    size_t cntBlock = cntbuf / nBlockWidth;    // 塊數.
    size_t cntRem = cntbuf % nBlockWidth;    // 剩余數量.
    __m128i xidSum = _mm_setzero_si128();    // 求和變量。[SSE2] PXOR. 賦初值0.
    __m128i xidLoad;    // 加載.
    const __m128i* p = (const __m128i*)pbuf;    // SSE批量處理時所用的指針.
    const int32_t* q;    // 單個數據處理時所用指針.

    // SSE批量處理.
    for(i=0; i<cntBlock; ++i)
    {
        xidLoad = _mm_load_si128(p);    // [SSE2] MOVDQA. 加載.
        xidSum = _mm_add_epi32(xidSum, xidLoad);    // [SSE2] PADDD. 32位整數緊縮環繞加法.
        p ++;
    }
    // 合並.
    q = (const int32_t*)&xidSum;
    s = q[0] + q[1] + q[2] + q[3];

    // 處理剩下的.
    q = (const int32_t*)p;
    for(i=0; i<cntRem; ++i)
    {
        s += q[i];
    }

    return s;
}

// 32位整數數組求和_SSE四路循環展開版.
int32_t sumint_sse_4loop(const int32_t* pbuf, size_t cntbuf)
{
    int32_t s = 0;    // 返回值.
    size_t i;
    size_t nBlockWidth = 4*4;    // 塊寬. SSE寄存器能一次處理4個int32_t,然后循環展開4次.
    size_t cntBlock = cntbuf / nBlockWidth;    // 塊數.
    size_t cntRem = cntbuf % nBlockWidth;    // 剩余數量.
    __m128i xidSum = _mm_setzero_si128();    // 求和變量。[SSE2] PXOR. 賦初值0.
    __m128i xidSum1 = _mm_setzero_si128();
    __m128i xidSum2 = _mm_setzero_si128();
    __m128i xidSum3 = _mm_setzero_si128();
    __m128i xidLoad;    // 加載.
    __m128i xidLoad1;
    __m128i xidLoad2;
    __m128i xidLoad3;
    const __m128i* p = (const __m128i*)pbuf;    // SSE批量處理時所用的指針.
    const int32_t* q;    // 單個數據處理時所用指針.

    // SSE批量處理.
    for(i=0; i<cntBlock; ++i)
    {
        xidLoad = _mm_load_si128(p);    // [SSE2] MOVDQA. 加載.
        xidLoad1 = _mm_load_si128(p+1);
        xidLoad2 = _mm_load_si128(p+2);
        xidLoad3 = _mm_load_si128(p+3);
        xidSum = _mm_add_epi32(xidSum, xidLoad);    // [SSE2] PADDD. 32位整數緊縮環繞加法.
        xidSum1 = _mm_add_epi32(xidSum1, xidLoad1);
        xidSum2 = _mm_add_epi32(xidSum2, xidLoad2);
        xidSum3 = _mm_add_epi32(xidSum3, xidLoad3);
        p += 4;    // 四路循環展開.
    }
    // 合並.
    xidSum = _mm_add_epi32(xidSum, xidSum1);    // 兩兩合並(0~1).
    xidSum2 = _mm_add_epi32(xidSum2, xidSum3);    // 兩兩合並(2~3).
    xidSum = _mm_add_epi32(xidSum, xidSum2);    // 兩兩合並(0~3).
    q = (const int32_t*)&xidSum;
    s = q[0] + q[1] + q[2] + q[3];

    // 處理剩下的.
    q = (const int32_t*)p;
    for(i=0; i<cntRem; ++i)
    {
        s += q[i];
    }

    return s;
}
#endif    // #ifdef INTRIN_SSE2





//////////////////////////////////////////////////
// main
//////////////////////////////////////////////////

// 變量對齊.
#ifndef ATTR_ALIGN
#  if defined(__GNUC__)    // GCC
#    define ATTR_ALIGN(n)    __attribute__((aligned(n)))
#  else    // 否則使用VC格式.
#    define ATTR_ALIGN(n)    __declspec(align(n))
#  endif
#endif    // #ifndef ATTR_ALIGN


#define BUFSIZE    4096    // = 32KB{L1 Cache} / (2 * sizeof(int32_t))
ATTR_ALIGN(32) int32_t buf[BUFSIZE];

// 測試時的函數類型
typedef int32_t (*TESTPROC)(const int32_t* pbuf, size_t cntbuf);

// 進行測試
void runTest(const char* szname, TESTPROC proc)
{
    const int testloop = 4000;    // 重復運算幾次延長時間,避免計時精度問題.
    const clock_t TIMEOUT = CLOCKS_PER_SEC/2;    // 最短測試時間.
    int i,j,k;
    clock_t    tm0, dt;    // 存儲時間.
    double mps;    // M/s.
    double mps_good = 0;    // 最佳M/s. 因線程切換會導致的數值波動, 於是選取最佳值.
    volatile int32_t n=0;    // 避免內循環被優化.
    for(i=1; i<=3; ++i)    // 多次測試.
    {
        tm0 = clock();
        // main
        k=0;
        do
        {
            for(j=1; j<=testloop; ++j)    // 重復運算幾次延長時間,避免計時開銷帶來的影響.
            {
                n = proc(buf, BUFSIZE);    // 避免內循環被編譯優化消掉.
            }
            ++k;
            dt = clock() - tm0;
        }while(dt<TIMEOUT);
        // show
        mps = (double)k*testloop*BUFSIZE*CLOCKS_PER_SEC/(1024.0*1024.0*dt);    // k*testloop*BUFSIZE/(1024.0*1024.0) 將數據規模換算為M,然后再乘以 CLOCKS_PER_SEC/dt 換算為M/s .
        if (mps_good<mps)    mps_good=mps;    // 選取最佳值.
        //printf("%s:\t%.0f M/s\t//%f\n", szname, mps, n);
    }
    printf("%s:\t%.0f M/s\t//%d\n", szname, mps_good, n);
}

int main(int argc, char* argv[])
{
    char szBuf[64];
    int i;

    printf("simdsumint v1.00 (%dbit)\n", INTRIN_WORDSIZE);
    printf("Compiler: %s\n", COMPILER_NAME);
    cpu_getbrand(szBuf);
    printf("CPU:\t%s\n", szBuf);
    printf("\n");

    // init buf
    srand( (unsigned)time( NULL ) );
    for (i = 0; i < BUFSIZE; i++) buf[i] = (int32_t)(rand() & 0x7fff);    // 使用&0x7fff是為了使數值在一定范圍內,便於觀察結果是否正確.

    // test
    runTest("sumint_base", sumint_base);    // 32位整數數組求和_基本版.
#ifdef INTRIN_MMX
    if (simd_mmx(NULL))
    {
        runTest("sumint_mmx", sumint_mmx);    // 32位整數數組求和_MMX版.
        runTest("sumint_mmx_4loop", sumint_mmx_4loop);    // 32位整數數組求和_MMX四路循環展開版.
    }
#endif    // #ifdef INTRIN_MMX
#ifdef INTRIN_SSE2
    if (simd_sse_level(NULL) >= 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

  全部代碼——

# flags
CC = g++
CFS = -Wall -msse2

# args
RELEASE =0
BITS =
CFLAGS =

# [args] 生成模式. 0代表debug模式, 1代表release模式. make RELEASE=1.
ifeq ($(RELEASE),0)
    # debug
    CFS += -g
else
    # release
    CFS += -O3 -DNDEBUG
    //CFS += -O3 -g -DNDEBUG
endif

# [args] 程序位數. 32代表32位程序, 64代表64位程序, 其他默認. make BITS=32.
ifeq ($(BITS),32)
    CFS += -m32
else
    ifeq ($(BITS),64)
        CFS += -m64
    else
    endif
endif

# [args] 使用 CFLAGS 添加新的參數. make CFLAGS="-mavx".
CFS += $(CFLAGS)


.PHONY : all clean

# files
TARGETS = simdsumint
OBJS = simdsumint.o

all : $(TARGETS)

simdsumint : $(OBJS)
    $(CC) $(CFS) -o $@ $^


simdsumint.o : simdsumint.c zintrin.h ccpuid.h
    $(CC) $(CFS) -c $<


clean :
    rm -f $(OBJS) $(TARGETS) $(addsuffix .exe,$(TARGETS))

 


三、編譯測試

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. http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
《Intel® Architecture Instruction Set Extensions Programming Reference》014. AUGUST 2012. http://software.intel.com/en-us/avx/
《AMD64 Architecture Programmer’s Manual Volume 4: 128-Bit and 256-Bit Media Instructions》. December 2011. http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals
《[C] 讓VC、BCB支持C99的整數類型(stdint.h、inttypes.h)(兼容GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/08/c99int.html
《[C] zintrin.h: 智能引入intrinsic函數 V1.01版。改進對Mac OS X的支持,增加INTRIN_WORDSIZE宏》. http://www.cnblogs.com/zyl910/archive/2012/10/01/zintrin_v101.html
《[C/C++] ccpuid:CPUID信息模塊 V1.03版,改進mmx/sse指令可用性檢查(使用signal、setjmp,支持純C)、修正AVX檢查Bug》. http://www.cnblogs.com/zyl910/archive/2012/10/13/ccpuid_v103.html
《[x86]SIMD指令集發展歷程表(MMX、SSE、AVX等)》. http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html
《SIMD(MMX/SSE/AVX)變量命名規范心得》. http://www.cnblogs.com/zyl910/archive/2012/04/23/simd_var_name.html
《GCC 64位程序的makefile條件編譯心得——32位版與64位版、debug版與release版(兼容MinGW、TDM-GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/14/gcc64_make.html
《[C#] cmdarg_ui:“簡單參數命令行程序”的通用圖形界面》.  http://www.cnblogs.com/zyl910/archive/2012/06/19/cmdarg_ui.html
《[C] 跨平台使用Intrinsic函數范例1——使用SSE、AVX指令集 處理 單精度浮點數組求和(支持vc、gcc,兼容Windows、Linux、Mac)》. http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html


源碼下載——
http://files.cnblogs.com/zyl910/simdsumint.rar


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM