2017年1月12日 星期四

SIMD Introduction 簡報

這份簡報是 2016 下半年的兩場大學 Seminar 邀約所準備的演講投影片
主要內容是各種處理器架構上對於 SIMD 需要的基本知識, 以及優化時需要的考量


Video Compression Standards - History & Introduction 簡報

這份簡報是由於臺北科技大學的黃士嘉教授的邀約
為了數位電視的實務課程中向學生們介紹 MPEG 相關標準的歷史而所準備


2017年1月7日 星期六

三個願望一次滿足的 libdispatch (Grand Central Dispatch) (revised ver)

這篇也是先前在臉書網誌上發表的, 今年再修正一下相關的 issue
====
不可否認, 儘管 POSIX thread 存在已久, 但是其 Programming Model 並不容易讓一般 coder 能夠輕鬆駕馭, 而中間衍生的 data structure 與 interface 也讓許多人頭疼, 然而享有多核平台的好處, 使用 thread 又是必要的.
而綜觀 thread 的用途大致有三:
  1. 利用 thread 來利用多核加速 (這也是多數其他 framework 專注的地方)
  2. timer background worker 實作
  3. event multiplexing (用以透過 handler 處理各式 event, 像是 blocked I/O )
Apple 於 2009 年以 Apache License 釋出了 Grand Central Dispatch (GCD) 的原始碼, 透過其 framework 搭配 blocks 的使用, 能一致性地處理上述三個面向的問題。時至今日, 儘管數年過去了, 個人認為 GCD/libdispatch 依然是設計上最出眾的 multi-thread framework.
在 Apple 釋出後不久, 在開放社群的努力下有了 FreeBSD port, 而 Linux 平台上也因Mark Heily的 OpenGCD 專案, 使得 Linux 平台也能夠使用 libdispatch, 在一般 Linux 上, 像是 ubuntu 已有現成包好的 package, 可以透過下列指令安裝
sudo apt-get install libdispatch-dev
然而對於 Linux 平台上的 package, 多數較為老舊 svn197, 若需要比較新的版本可以去官方的 github 抓取最新的 svn685 版本編譯使用

但對於Android 平台上, 若想要使用, 我們可以透過 PSPDFKit-labs 的移植搭配 NDK
以下列步驟來編譯 svn197 版

1. 請於確認與設定好環境具有 NDK, git 
2. 建立 libdispatch 工作目錄與 子目錄 jni
$ mkdir -p libdispatch/jni; cd libdispatch/jni
libdispatch

3. 於 jni 目錄下自 PSPDFKit-labs 抓取 blocksruntime, libkqueue 與 libdispatch
$ git clone https://github.com/PSPDFKit-labs/blocksruntime.git
$ git clone https://github.com/PSPDFKit-labs/libkqueue.git
$ git clone https://github.com/PSPDFKit-labs/libdispatch.git
 4. 於 jni 下建立 Android.mk 與 Application.mk 內容如下

Application.mk
#APP_ABI := armeabi-v7a
APP_ABI := arm64-v8a

Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := blocksruntime
LOCAL_C_INCLUDES := $(LOCAL_PATH)/blocksruntime
LOCAL_SRC_FILES     := \
    blocksruntime/BlocksRuntime/data.c \
    blocksruntime/BlocksRuntime/runtime.c
include $(BUILD_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libkqueue
LOCAL_C_INCLUDES := \
    $(LOCAL_PATH)/libkqueue/src/common \
    $(LOCAL_PATH)/libkqueue/android \
    $(LOCAL_PATH)/libkqueue/include
LOCAL_CFLAGS := -std=c99 -D_XOPEN_SOURCE=600 -fvisibility=hidden -DCLANG_TLS_WORKAROUND
LOCAL_SRC_FILES    := \
    libkqueue/src/common/filter.c \
    libkqueue/src/common/knote.c \
    libkqueue/src/common/map.c \
    libkqueue/src/common/kevent.c \
    libkqueue/src/common/kqueue.c \
    libkqueue/src/posix/platform.c \
    libkqueue/src/linux/platform.c \
    libkqueue/src/linux/read.c \
    libkqueue/src/linux/write.c \
    libkqueue/src/linux/user.c \
    libkqueue/src/linux/vnode.c \
    libkqueue/src/linux/timer.c \
    libkqueue/src/posix/signal.c
include $(BUILD_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libdispatch
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libdispatch/android \
LOCAL_C_INCLUDES += $(LOCAL_PATH)/libdispatch/android/config \
    $(LOCAL_PATH)/libdispatch/ \
    $(LOCAL_PATH)/libdispatch/os \
    $(LOCAL_PATH)/libdispatch/private \
    $(LOCAL_PATH)/libkqueue/include \
    $(LOCAL_PATH)/libpwq/include \
    $(LOCAL_PATH)/blocksruntime/BlocksRuntime
LOCAL_CFLAGS := -fvisibility=hidden -momit-leaf-frame-pointer -fblocks -DHAVE_CONFIG_H
LOCAL_SRC_FILES    := \
    libdispatch/src/apply.c \
    libdispatch/src/benchmark.c \
    libdispatch/src/data.c \
    libdispatch/src/init.c \
    libdispatch/src/io.c \
    libdispatch/src/object.c \
    libdispatch/src/once.c \
    libdispatch/src/queue.c \
    libdispatch/src/semaphore.c \
    libdispatch/src/source.c \
    libdispatch/src/time.c \
    libdispatch/src/transform.c

ifeq ($(TARGET_ARCH_ABI),x86_64)
LOCAL_CFLAGS += -DHAVE_GETPROGNAME
endif
ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
LOCAL_CFLAGS += -DHAVE_GETPROGNAME
endif
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_CFLAGS += -DHAVE_VALLOC
endif
LOCAL_STATIC_LIBRARIES  := blocksruntime libkqueue
include $(BUILD_SHARED_LIBRARY)
最後於 libdispatch 目錄下透過 ndk-build 指令即可編譯出 libdispatch.so

最後可以測試一下, 這裡我們小修改了, "你也可以寫 SIMD 比寫網頁快"的範例
以 8x8 為轉置單位
void transpose8x8(int *src, int *dst, int w, int h)
{
   
    int8 row0 = *((int8*)(src)); src+=w;
    int8 row1 = *((int8*)(src)); src+=w;
    int8 row2 = *((int8*)(src)); src+=w;
    int8 row3 = *((int8*)(src)); src+=w;
    int8 row4 = *((int8*)(src)); src+=w;
    int8 row5 = *((int8*)(src)); src+=w;
    int8 row6 = *((int8*)(src)); src+=w;
    int8 row7 = *((int8*)(src));
   
    int8 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
    tmp0 = (int8){row0.s0, row1.s0, row2.s0, row3.s0, row4.s0, row5.s0, row6.s0, row7.s0};
    tmp1 = (int8){row0.s1, row1.s1, row2.s1, row3.s1, row4.s1, row5.s1, row6.s1, row7.s1};
    tmp2 = (int8){row0.s2, row1.s2, row2.s2, row3.s2, row4.s2, row5.s2, row6.s2, row7.s2};
    tmp3 = (int8){row0.s3, row1.s3, row2.s3, row3.s3, row4.s3, row5.s3, row6.s3, row7.s3};
    tmp4 = (int8){row0.s4, row1.s4, row2.s4, row3.s4, row4.s4, row5.s4, row6.s4, row7.s4};
    tmp5 = (int8){row0.s5, row1.s5, row2.s5, row3.s5, row4.s5, row5.s5, row6.s5, row7.s5};
    tmp6 = (int8){row0.s6, row1.s6, row2.s6, row3.s6, row4.s6, row5.s6, row6.s6, row7.s6};
    tmp7 = (int8){row0.s7, row1.s7, row2.s7, row3.s7, row4.s7, row5.s7, row6.s7, row7.s7};
   
    *((int8*)(dst)) = tmp0; dst+=h;
    *((int8*)(dst)) = tmp1; dst+=h;
    *((int8*)(dst)) = tmp2; dst+=h;
    *((int8*)(dst)) = tmp3; dst+=h;
    *((int8*)(dst)) = tmp4; dst+=h;
    *((int8*)(dst)) = tmp5; dst+=h;
    *((int8*)(dst)) = tmp6; dst+=h;
    *((int8*)(dst)) = tmp7;
}

void gcd_transpose(dispatch_queue_t queue, dispatch_group_t group, int *src, int *dst, int w, int h)
{
    for(int x = 0; x < w; x+=8){
        dispatch_group_async(
            group,
            queue,
            ^{

                for(int y = 0; y < h; y+=8){
                    transpose8x8(src+y*w+x, dst+x*h+y, w, h);
                }
            }
        );

    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
而 gcd_transpose 的使用方式如下
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,NULL);
        gcd_transpose(queue, group, src, out1, TEST_W, TEST_H);
        dispatch_release(group);
        dispatch_release(queue);
編譯的方式
* Ubuntu
clang -O3 -fblocks -mavx -mfma -o test test.c -ldispatch -lBlockRuntime
* Android 請使用以上產生的 libdispatch.so 置於 test 的 jni 下, 並於 Android.mk 加入
LOCAL_LDLIBS := -L$(LOCAL_PATH) -ldispatch
個人在 i7-3612QM 上, 速度為單核 1.5X.
Android 上曾以此於 Nexus 9 (四核平台)上做過 memory copy 的測試,  為單核 2.7X.

儘管對 multi-threading programming 熟稔的人使用 libdispatch 與直接使用 pthread 差異落差會感覺其實還好, 但可以觀察到藉由使用 Blocks 的方式, 能夠省去建構用以傳遞各 thread 執行所需資訊的資料結構之必要, 而由於 loop 中的變數值更可以直接取值使用, Blocks 這樣的特性使得執行流程簡潔又同時具有效能的延伸性. 另外在 Android N 之後預設使用 clang 編譯, 即能同夠使用 libdispatch 與 Blocks 來寫出更簡潔好讀兼顧效率的程式碼。
延伸閱讀:
 1. Grand Central Dispatch Reference (PDF, Apple Website) 
2. libdispatch tutorial (timer usage) 
3. Grand Central Dispatch In-Depth PartI, PartII 
4. Directory Monitoring and GCD (event handle) 

2017年1月6日 星期五

你也可以寫 SIMD 比寫網頁還快 - III

如果沒有特別的後續想法
這篇就是系列文的最後一篇
"你也可以寫 SIMD 比寫網頁快" 的精神
不外乎就是 簡單 快速 有效

延續上兩篇在最後想要談的是搭配 clang + OpenCL vector 的進階使用方式
這裡只探討一個問題 : 使用那些基本的運算符號是否就能用上所有的 SIMD 指令?
很明顯的, 答案是不行(否則就不會有這篇啦 XDDDD)

但是如果你知道特定好用的 SIMD 指令該如何套用呢?
舉例來說 ARM NEON 有個特別的指令 VQDMULH
它的用途是將兩個 16/32bit 數值相乘後所產生的 32/64bit 數值, 取其高位的 16/32bit
以 code 表示的話:
int a, b, c;
...
c = sqdmulh(a, b);
// equal-to
int64 tmp;
tmp = a*b;
c = (int)(tmp>>32) ;
這指令對於 NEON 上實作常數的除法, 或是 fixed-point 的實作上很有幫助
這時我們可以這麼做:
int4 va = {0, 1, 2, 3};
int4 vb = {1, 2, 3, 4};
int4 vrab = vqdmulhq_s32(va, vb);
上面的範例中 int32x4_t 為 NEON intrinsics 的 data type
而基本的精神並不是什麼難以理解的事
僅是透過 pointer 作 NEON vector 與 OpenCL vector 的型別替換使用
而在之後要換回使用 OpenCL vector 可以透過一樣的方式轉回
接著我們可以透過 vec.s 觀察 clang code generation 的結果:
        adr     r0, .LCPI2_0
...
        vld1.64 {d16, d17}, [r0:128]

...
        adr     r0, .LCPI2_1
        vld1.64 {d18, d19}, [r0:128]
        vqdmulh.s32     q8, q9, q8
在此為了確認這些暫存器間的關係, 可以參考 (ARMv7) NEON registers
64b 的 (d16,d17) 即為 128b 的 q8
64b 的 (d18,d19) 即為 128b 的 q9
所以可以看到這樣的作法並沒有因此產生多餘的指令
是不是很簡單呢?

在此 "你也可以寫 SIMD 比寫網頁還快" 系列文告一段落

The Power of Prefetching

這篇其實比"你也可以寫 SIMD 比網頁還快"早很多, 其中 I 的部分就是與這篇的  function 比較速度
====

先前在臉書上有分享過 “When Prefetching Works, When It Doesn’t, and Why”這篇 paper, 現在要用實例顯示 prefetching 帶來的效益, 今日使用的範例是 Matrix Transpose
一個簡單的 C 版本實作如下
void naive_transpose(int *src, int *dst, int w, int h){
    for(int x = 0; x < w; x++){
        for(int y = 0; y < h; y++){
             *(dst + x*h + y) = *(src + y*w + x);
        } 
    }
} 
SSE2 實作版本如下, 由於先進處理器架構有著 automatic/speculative prefetcher 所以這裡我們故意寫得比較 CPU-unfriendly 一些
void sse_transpose(int *src, int *dst, int w, int h){ 
    for(int x = 0; x < w; x+=4){
        for(int y = 0; y < h; y+=4){
            __m128i I0 = _mm_loadu_si128 ((__m128i*)(src+y*w+x)); 
            __m128i I1 = _mm_loadu_si128 ((__m128i*)(src+(y+1)*w+x));
            __m128i I2 = _mm_loadu_si128 ((__m128i*)(src+(y+2)*w+x)); 
            __m128i I3 = _mm_loadu_si128 ((__m128i*)(src+(y+3)*w+x));
            __m128i T0 = _mm_unpacklo_epi32(I0, I1); 
 
            __m128i T1 = _mm_unpacklo_epi32(I2, I3); 
            __m128i T2 = _mm_unpackhi_epi32(I0, I1);
            __m128i T3 = _mm_unpackhi_epi32(I2, I3); 
            I0 = _mm_unpacklo_epi64(T0, T1); 
            I1 = _mm_unpackhi_epi64(T0, T1); 
            I2 = _mm_unpacklo_epi64(T2, T3); 
            I3 = _mm_unpackhi_epi64(T2, T3); 
            _mm_storeu_si128((__m128i*)(dst+(x*h)+y), I0); 
            _mm_storeu_si128((__m128i*)(dst+((x+1)*h)+y), I1); 
            _mm_storeu_si128((__m128i*)(dst+((x+2)*h)+y), I2); 
            _mm_storeu_si128((__m128i*)(dst+((x+3)*h)+y), I3);
        } 
    }
}
接著我們來撰寫一個使用 SSE prefetch 指令的加速版本
 
void sse_prefetch_transpose(int *src, int *dst, int w, int h){ 
    for(int x = 0; x < w; x+=4){
        for(int y = 0; y < h; y+=4){
            #define PFDIST  8
            _mm_prefetch(src+(y+PFDIST)*w+x, _MM_HINT_T1); 
            _mm_prefetch(src+(y+PFDIST+1)*w+x, _MM_HINT_T1);
            _mm_prefetch(src+(y+PFDIST+2)*w+x, _MM_HINT_T1);
            _mm_prefetch(src+(y+PFDIST+3)*w+x, _MM_HINT_T1);
 
            __m128i I0 = _mm_loadu_si128 ((__m128i*)(src+y*w+x)); 
            __m128i I1 = _mm_loadu_si128 ((__m128i*)(src+(y+1)*w+x)); 
            __m128i I2 = _mm_loadu_si128 ((__m128i*)(src+(y+2)*w+x)); 
            __m128i I3 = _mm_loadu_si128 ((__m128i*)(src+(y+3)*w+x)); 
            __m128i T0 = _mm_unpacklo_epi32(I0, I1);
            __m128i T1 = _mm_unpacklo_epi32(I2, I3);
            __m128i T2 = _mm_unpackhi_epi32(I0, I1);
            __m128i T3 = _mm_unpackhi_epi32(I2, I3); 
            I0 = _mm_unpacklo_epi64(T0, T1); 
            I1 = _mm_unpackhi_epi64(T0, T1); 
            I2 = _mm_unpacklo_epi64(T2, T3); 
            I3 = _mm_unpackhi_epi64(T2, T3); 
            _mm_storeu_si128((__m128i*)(dst+(x*h)+y), I0); 
            _mm_storeu_si128((__m128i*)(dst+((x+1)*h)+y), I1); 
            _mm_storeu_si128((__m128i*)(dst+((x+2)*h)+y), I2); 
            _mm_storeu_si128((__m128i*)(dst+((x+3)*h)+y), I3); 
        }
    }
}
接著是測試程式(請將上述程式碼置於指定位置)
#include  
#include 
#include  
#include 
//for using x86-SSE2 intrinsics you have to include this
#include 
 
// PUT naive_transpose, sse_transpose, sse_prefetch_transpose HERE 
// ...
 
#define TEST_W 4096
#define TEST_H 4096
int main(void){
    //verify the result of 4x4 matrix 
    { 
        int testin[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; 
        int testout[16];
        for(int y = 0; y < 4; y++){
            for(int x = 0; x < 4; x++)
                printf(" %2d", testin[y*4+x]);
            printf("\n");
        } 
        printf("\n");
        sse_transpose(testin, testout, 4, 4); 
        for(int y = 0; y < 4; y++){ 
            for(int x = 0; x < 4; x++) 
                printf(" %2d", testout[y*4+x]); 
            printf("\n");
        }
    }
 
    {
        struct timeval stime, etime; 
        int *src = (int*)malloc(sizeof(int)*TEST_W*TEST_H);
        int *out0 = (int*)malloc(sizeof(int)*TEST_W*TEST_H); 
        int *out1 = (int*)malloc(sizeof(int)*TEST_W*TEST_H); 
        int *out2 = (int*)malloc(sizeof(int)*TEST_W*TEST_H);

        srand(time(NULL));
        for(int y = 0; y < TEST_H; y++){ 
            for(int x = 0; x < TEST_W; x++)
                *(src + y*TEST_W + x) = rand();
        }
        gettimeofday(&stime, NULL); 
        sse_prefetch_transpose(src, out0, TEST_W, TEST_H); 
        gettimeofday(&etime, NULL);
        printf("sse prefetch: %ld us\n", (etime.tv_sec - stime.tv_sec)*1000000 + (etime.tv_usec - stime.tv_usec));
 
        gettimeofday(&stime, NULL); 
        sse_transpose(src, out1, TEST_W, TEST_H);
        gettimeofday(&etime, NULL); 
        printf("sse: %ld us\n", (etime.tv_sec - stime.tv_sec)*1000000 + (etime.tv_usec - stime.tv_usec));
 
        gettimeofday(&stime, NULL); 
        naive_transpose(src, out2, TEST_W, TEST_H);
        gettimeofday(&etime, NULL); 
        printf("naive: %ld us\n", (etime.tv_sec - stime.tv_sec)*1000000 + (etime.tv_usec - stime.tv_usec));

        free(src);
        free(out0); 
        free(out1); 
        free(out2); 
     }
     return 0; 
}
存成 test.c, 並且按照下列方式編譯
gcc -msse2 -o test test.c
於個人的 Core i7 3612QM 執行得到下列結果
  0  1  2  3 
  4  5  6  7
  8  9 10 11 
 12 13 14 15
 
  0  4  8 12 
  1  5  9 13 
  2  6 10 14
  3  7 11 15
 
sse prefetch:  57782 us
sse: 117184 us 
naive: 228870 us
儘管現今 CPU 有著強大的 auto-prefetcher, 但是並不是所有的演算都有著規律或是線型的記憶體存取模式, 如此透過優化 prefetching 依然是能夠有相當的效能增進.

你也可以寫 SIMD 比寫網頁還快 - II

第一篇並沒有介紹 clang 中的 OpenCL kernel 一些基礎 型別宣告部分, 已經在上一篇出現過了, 所以這裡就從使用開始. 首先是 vector 的初始化

typedef int int4 __attribute__((ext_vector_type(4)));
typedef int int8 __attribute__((ext_vector_type(8)));
int a = 3;
int4 va = {1, 2, 3, 4}; 
int4 vb = 3;

接著是個別的 access, 下列的方式很適合做資料格式上的操作 在等號的兩邊可以用不同的表示法, 沒有需要一致:

va.x = a; 
// the below two lines are equivalent. 
vb.xyw = va.xzw; 
vb.s013 = va.s023; 
// repeat is ok 
va = vb.xyyz; 
// high and low part 
va.hi = va.lo;
va.lo = vb.hi;
// even and odd 
va.odd = vb.even;

Vector 簡單的計算如下, 可以參照 clang - vectors and extended vectors 的列表:

// scalar multiply 
va = a * vb;
// element-wise multiply
va = va * vb;
va = va | vb;
va = -va;

不同型別的 vector 轉換:

typedef float float4 __attribute__((ext_vector_type(4))); 
// format convertion 
float4 vfa = __builtin_convertvector(va, float4); 
// data reinterpret
float4 vfa = (float4)va;

接著來實戰一番吧, 下列為以 bayer format 平均 GRBG 4個像素, 轉為1/4大小灰階圖的範例

void bayer_convert(char *src, char *gray, int w, int h) 
{
    for(int y = 0; y < h; y+=2){ 
        for(int x = 0; x < w; x+=16){
            //bayer raw data fetch
            ushort8 pix00 = __builtin_convertvector(*((uchar8*)(src+x)), ushort8);
            ushort8 pix01 = __builtin_convertvector(*((uchar8*)(src+x+8)), ushort8); 
            ushort8 pix10 = __builtin_convertvector(*((uchar8*)(src+x+w)), ushort8);
            ushort8 pix11 = __builtin_convertvector(*((uchar8*)(src+x+w+8)), ushort8);

            ushort8 pix_g0;
            pix_g0.lo = pix00.even;
            pix_g0.hi = pix01.even; 
            ushort8 pix_r;
            pix_r.lo = pix00.odd;
            pix_r.hi = pix01.odd;
            ushort8 pix_b;
            pix_b.lo = pix10.even;
            pix_b.hi = pix11.even;
            ushort8 pix_g1;
            pix_g1.lo = pix10.odd;
            pix_g1.hi = pix11.odd;

            // average! so simple
            ushort8 pix_out = (pix_g0 + pix_r + pix_b + pix_g1) ;
            // write out 
            *((uchar8*)(gray+(x/2))) = __builtin_convertvector(pix_out, uchar8); 
        }
        src += w*2; 
        gray += w;
    }
}
如何? 比起看 intrinsics function 來得清楚與簡單吧
如果還想了解 OpenCL vector 的操作可以參考這篇文章
有興趣記得編譯時加入 --save-temps 來看使用的指令!!! 下一篇會介紹進階的使用方式


2017年1月5日 星期四

你也可以寫 SIMD 比寫網頁還快 - I

看到 Jim Huang 要開”從無到有自幹 boot loader 比寫網頁還快” 除了幫他打廣告外, 也趕快搭這個熱潮, 證明人人可以寫 SIMD 比網頁還快 去年寫了系列 MMX/SSE 去處理 Matrix Transpose 但是看到 SSE/AVX intrinsics 很多人大概就眼睛花了 若加上實際應用還要從 Intel 網頁查找適當的 intrinsics 會覺得難以下手 搭配適當的工具, 你也可以是 SIMD 高手! 這裡最重要的工具是 - clang clang 如同 gcc 一般提供了許多 extension 其中很重要的一點是 Vectors and Extended Vectors 這個 extension 請特別注意 link 中的 OpenCL vector, 底下的表格表示此方式提供相當全面的 operator 而 clang 中使用 OpenCL vector 這即是這裡所推荐的方式 (還可以學會 OpenCL kernel 的部份撰寫!) 首先來以 clang 方式定義 OpenCL vector 的型別
typedef int int8 __attribute__((ext_vector_type(8)));
UPDATED: 對於 GCC 支援相近語法, 其宣告為 (其中 vector_size 以 byte 計)
typedef int int8 __attribute__ ((vector_size (32)));

這裡定義了長度為 8 的 int vector type 接著我們來實作先前時做過的 matrix transpose 功能
void transpose8x8(int *src, int *dst, int w, int h){
    for(int x = 0; x < w; x+=8){
        for(int y = 0; y < h; y+=8){
            int8 row0 = *((int8*)(src+y*w+x));
            int8 row1 = *((int8*)(src+(y+1)*w+x));
            int8 row2 = *((int8*)(src+(y+2)*w+x));
            int8 row3 = *((int8*)(src+(y+3)*w+x));
            int8 row4 = *((int8*)(src+(y+4)*w+x));
            int8 row5 = *((int8*)(src+(y+5)*w+x));
            int8 row6 = *((int8*)(src+(y+6)*w+x));
            int8 row7 = *((int8*)(src+(y+7)*w+x));
            int8 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
            tmp0 = (int8){row0.s0, row1.s0, row2.s0, row3.s0, row4.s0, row5.s0, row6.s0, row7.s0};
            tmp1 = (int8){row0.s1, row1.s1, row2.s1, row3.s1, row4.s1, row5.s1, row6.s1, row7.s1};
            tmp2 = (int8){row0.s2, row1.s2, row2.s2, row3.s2, row4.s2, row5.s2, row6.s2, row7.s2};
            tmp3 = (int8){row0.s3, row1.s3, row2.s3, row3.s3, row4.s3, row5.s3, row6.s3, row7.s3};
            tmp4 = (int8){row0.s4, row1.s4, row2.s4, row3.s4, row4.s4, row5.s4, row6.s4, row7.s4};
            tmp5 = (int8){row0.s5, row1.s5, row2.s5, row3.s5, row4.s5, row5.s5, row6.s5, row7.s5};
            tmp6 = (int8){row0.s6, row1.s6, row2.s6, row3.s6, row4.s6, row5.s6, row6.s6, row7.s6};
            tmp7 = (int8){row0.s7, row1.s7, row2.s7, row3.s7, row4.s7, row5.s7, row6.s7, row7.s7};
            *((int8*)(dst+(x*h)+y)) = tmp0;
            *((int8*)(dst+((x+1)*h)+y)) = tmp1;

            *((int8*)(dst+((x+2)*h)+y)) = tmp2;
            *((int8*)(dst+((x+3)*h)+y)) = tmp3;
            *((int8*)(dst+((x+4)*h)+y)) = tmp4;
            *((int8*)(dst+((x+5)*h)+y)) = tmp5;
            *((int8*)(dst+((x+6)*h)+y)) = tmp6;
            *((int8*)(dst+((x+7)*h)+y)) = tmp7;
        }
    }
}
將上列內容與先前的範例結合, 最後就是編譯了
clang -O3 -mavx -mfma -o test test.c
 以下為在 Intel(R) Core i7-3612QM 的執行結果
clang vector: 35759 us
sse: 41246 us
naive: 146310 us
這樣是不是很快呢? 甚至比之前直接用 SSE intrinsics 寫的還快!!!(灑花) 更棒的是這樣的 SIMD 撰寫, 透過 clang 是可以在各種硬體平台的 可以自己試試看用 NDK 然後跑在自己的手機看看喔!!

在 ARM 平台上使用 Function Multi-Versioning (FMV) - 以使用 Android NDK 為例

Function Multi-Versioning (FMV) 過往的 CPU 發展歷程中, x86 平台由於因應各種應用需求的提出, 而陸陸續續加入了不同的指令集, 此外也可能因為針對市場做等級區隔, 支援的數量與種類也不等. 在 Linux 平台上這些 CPU 資訊可以透過...