2020年9月28日 星期一

關於 clang/gcc vector extension 的 unaligned data load / store

從 ARM 平台開始學習著手寫 SIMD intrinsics 有個相當大的好處 - 在 ARM 平台上 vector load/store 在任何 memory address 都可以使用 vld1q_u32 or vst1q_u32 這類的 intrinsics (另外像是 vld3q_u8 / vst3q_u8 對於 interleaving RGB 非常好用, 在 x86 上就很頭大, 但這是題外話了), 也就是說 ARM 的 vector load / store 可以不用考慮 vector width alignment. 

然而 ARM NEON 的 vector load / store 指令設計是少數, 在多數的 SIMD ISA 平台都不是如此, 像是 Intel AVX / SSE 就必須顧慮到 alignment ( 請參考 Intel Intrinsics Guide 網站 ):
以 SSE 而言:

  • _mm_load_si128 - 這是用來針對 aligned address
  • _mm_loadu_si128 - 這是使用來處理 unaligned address

對於 AVX :

  • _mm256_load_si256 - 這是用來針對 aligned address
  • _mm256_loadu_si256 - 這是使用來處理 unaligned address

在 unaligned address 使用 aligned instruction 就會發生 crash / error, 因此不可不慎, 而這在硬體上會反映在內部 pipeline 狀態到 memory request 的頻率/數量, 對於效能有一定程度的影響. 在多數 DSP 上由於使用 banked SRAM, 因此有可能有更嚴格的 alignment 限制. 

在 clang / gcc 中使用 vector extension 是可以較為簡單撰寫 SIMD-friendly code 的方法之一, 然而無論在 clang 的 vector extension 說明 或是 gcc 的 vector extension 說明 都採用 aligned vector 的形式, 所以通常在撰寫 data loading 時就必須注意 pointer alignment:

// clang vector type
typedef
float float4 __attribute__((ext_vector_type(4)));
...
float4 *vdata = *((float4*)(data_ptr + offset));

對於像是 CV / Image Processing 通常必須注意 padding, boundary 與 data_ptr 的 offset, 最簡單的方式當然是透過 memalign 之類的方式配置 buffer, 然後將 layout 設計好符合 SIMD width. 然而並非所有演算都可以如此輕鬆, 一些像是 connected conponents 之類的演算, 能夠有一定程度的 SIMD, 但是又並不保證起點由 aligned address 處理, 這時通常處理方式有二:

  1. 使用 unaligned read + indications/select
  2. 轉為 aligned-based loops

兩者都有人使用, 這裡要談的不是 algorithm 層級的事情, 且 1. 的部份會簡單一些, 那麼問題就在於該如何讓 compiler 產生 unaligned load / store. 對於 gcc / clang vector extension 而言這在於 type alignment 屬性 (詳情請參考 StackOverflow 對此問題的回覆) , 將上述的 float4 型別宣告改為:

typedef float float4 __attribute__((ext_vector_type(4), aligned(1)));

如此 float4 型別的 alignment 會是 1-byte, 如此 compiler 會轉為產生 unaligned load / store 指令, 便能在 c code 當中於任意位置做資料的存取與處理.

沒有留言:

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

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