0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

CPU優(yōu)化技術(shù)-NEON自動向量化

安芯教育科技 ? 來源:安謀科技學堂 ? 2023-01-11 14:53 ? 次閱讀

本文選自極術(shù)專欄《嵌入式AI》的文章,授權(quán)轉(zhuǎn)自知乎作者高性能計算學院的《移動端算法優(yōu)化。前面我們學習了如何快速上手開始NEON編程ArmNEON優(yōu)化技術(shù),Arm NEON 匯編與Intrinsics編程,CPU優(yōu)化技術(shù)之NEON介紹CPU 優(yōu)化技術(shù)-NEON 指令介紹,本篇將會詳細介紹NEON 自動向量化

一、概述

SIMD 作為一種重要的并行化技術(shù),在提升性能的同時也會增加開發(fā)的難度。目前大多數(shù)編譯器都具有自動向量化的功能,將 C/C++ 代碼自動替換為 SIMD 指令。

從編譯技術(shù)上來說,自動向量化一般包含兩部分:循環(huán)向量化(Loop vectorization)和超字并行向量化(SLP,Superword-Level Parallelism vectorization,又稱Basic block vectorization)

演示代碼:

void add(int *a, int *b, int n, int * restrict sum)
{
    // it is assumed that the input n is an integer multiple of 4
    for (int i = 0; i < (n & ~3); ++i)
    {
        sum[i] = a[i] + b[i];
    }
}
  • 循環(huán)向量化將循環(huán)進行展開,增加循環(huán)中的執(zhí)行代碼來減少循環(huán)次數(shù)。如以下代碼將循環(huán)次數(shù)精簡到之前的1/4。
for (int i = 0; i < (n & ~3); i += 4)
{
    sum[i]     = a[i    ] + b[i];
    sum[i + 1] = a[i + 1] + b[i + 1];
    sum[i + 2] = a[i + 2] + b[i + 2];
    sum[i + 3] = a[i + 3] + b[i + 3];
}
  • SLP 向量化:編譯器將多個標量運算綁定到一起,使其成為向量運算。下圖將四次標量運算替換為一次向量運算。

495aabde-9110-11ed-bfe3-dac502259ad0.png

SLP 自動向量化

接下來介紹如何通過編譯器實現(xiàn)自動向量化。

二、編譯器配置

目前支持自動向量化的編譯器有 Arm Compiler 6、Arm C/C++ Compiler、LLVM-clang 以及 GCC,這幾種編譯器間的相互關系如下表所示。

498f1838-9110-11ed-bfe3-dac502259ad0.png

自動向量化默認不會被啟用,編程人員需要向編譯器提供允許自動向量化的“許可證”來對自動向量化功能進行使能

A.Arm Compiler 中使能自動向量化

下文中 Arm Compiler 6 與 Arm C/C++ Compiler 使用 armclang 統(tǒng)稱,armclang 使能自動向量化配置信息如下表所示:

49985a60-9110-11ed-bfe3-dac502259ad0.png

armclang 實現(xiàn)自動向量化示例:

# AArch32
armclang --target=arm-none-eabi -mcpu=cortex-a53 -O1 -fvectorize main.c

# AArch64
armclang --target=aarch64-arm-none-eabi -O2 main.c

B. LLVM-clang中使能自動向量化

Android NDK 從 r13 開始以 clang 為默認編譯器,本節(jié)通過 cmake 調(diào)用Android NDK r19c 工具鏈展示 clang 的自動向量化方法。

  • 使用 Android NDK 工具鏈使能自動向量化配置參數(shù)如下表:

49a5ed38-9110-11ed-bfe3-dac502259ad0.png

  • 在 CMake 中配置自動向量化方式如下:
# method 1
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O1 -fvectorize")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1 -fvectorize")

# method 2
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")

C. GCC 中使能自動向量化

在 gcc 中使能自動向量化配置參數(shù)如下:

49b39eec-9110-11ed-bfe3-dac502259ad0.png

  • 在不明確配置 -mcpu 的情況下,編譯器將使用默認配置(取決于編譯工具鏈時的選項設置)進行編譯。

  • 通常情況下 -mfpu 和 -mcpu 的配置存在關聯(lián)性,對應關系如下。(如當選取-mcpu為cortex-a8時,-mfpu一般設置為vfpv3或neon)

49c1dd7c-9110-11ed-bfe3-dac502259ad0.png

gcc 中實現(xiàn)自動向量化的編譯配置如下

# AArch32
arm-none-linux-gnueabihf-gcc -mcpu=cortex-a53 -mfpu=neon -ftree-vectorize -O2 main.c

# AArch64
aarch64-none-linux-gnu-gcc -mcpu=cortex-a53 -ftree-vectorize -O2 main.c

此外,gcc 中可以通過 -fopt-info-vec 命令查看自動向量化的詳細信息,比如哪些代碼實現(xiàn)了向量化,哪些代碼沒有實現(xiàn)向量化及沒有進行向量化的原因。

D. 自動向量化實例

我們以上節(jié)的求和示例代碼,來對編譯器自動向量化的功能進行演示。編譯器以 32 位 arm-gcc 為例:

# automatic vectorization is not enabled
arm-none-linux-gnueabihf-gcc -O2 main.c -o avtest

# automatic vectorization is enabled
arm-none-linux-gnueabihf-gcc -mfpu=neon -ftree-vectorize -O2 main.c -o avtest
  • 使用 objdump 查看反匯編代碼,反匯編命令如下:
arm-none-linux-gnueabihf-objdump -d avtest > assemble.txt
  • 反匯編結(jié)果對比如下圖:

49cba10e-9110-11ed-bfe3-dac502259ad0.png

反匯編代碼

啟用自動向量化之后,編譯器通過矢量化加載 (ldr -> vld1)求和 (add -> vadd)以及保存 (str -> vst1)等指令,將每次循環(huán)中處理的數(shù)據(jù)變?yōu)?4 個,循環(huán)次數(shù)精簡為之前的 1/4。

三、自動向量化友好型代碼

基于一定的編程優(yōu)化準則,可以更好的協(xié)助編譯器完成自動向量化的工作,獲得理想的性能狀態(tài)。

A. 避免使用難以向量化的語句

  • 數(shù)據(jù)依賴

當循環(huán)中存在數(shù)據(jù)依賴時,編譯器無法進行向量化。

下述代碼中計算 a[i] 時依賴上一次循環(huán)的輸出,無法被向量化。

// the output of a[i] depends on its last result
for (int i = 1; i < n; ++i)
{
    a[i] = a[i - 1] + 1;
}
  • 多級指針

編譯器無法對間接尋址,多級索引、多級解引用等行為進行向量化,盡量避免使用多級指針。

下述代碼通過 idx 進行了多級索引,無法被向量化。

// idx is unpredictable, so this code cannot be vectorized
for (int i = 0; i < n; ++i)
{
    sum[idx[i]] = a[idx[i]] + b[idx[i]];
}
  • 條件及跳轉(zhuǎn)語句

當循環(huán)中存在條件語句或跳轉(zhuǎn)語句時,代碼很難被向量化。因此應盡量避免在循環(huán)中的使用if、break等語句。當循環(huán)中需要調(diào)用函數(shù)時,盡量使用內(nèi)聯(lián)函數(shù)進行替換。

下述代碼通過調(diào)用內(nèi)聯(lián)函數(shù) add_single2 避免發(fā)生函數(shù)跳轉(zhuǎn)。

__attribute__((noinline)) int add_single1(int a, int b);

__inline__ __attribute__((always_inline)) int add_single2(int a, int b);

void add(const int *a, const int *b, int n, int * restrict sum)
{
    for (int i = 0; i < (n & ~3); ++i)
    {
        // replace normal functions with inline functions
        // sum[i] = add_single1(a[i], b[i]);
        sum[i] = add_single2(a[i], b[i]);
    }
}
  • 長數(shù)據(jù)類型

neon 對 64 位長數(shù)據(jù)類型的支持有限,且較小的數(shù)據(jù)位寬有更高的并行度,應盡量選用較小的數(shù)據(jù)類型。當程序中存在浮點數(shù)據(jù)時,指明其數(shù)據(jù)類型。

下述代碼指明1.0是浮點數(shù)據(jù),否則編譯器會優(yōu)先將其理解為double。

// assume that array sum and a are floating-point arrays
for (int i = 0; i < (n & ~3); ++i)
{
    // replace 1.0 with 1.f
    // sum[i] = a[i] + 1.0;
    sum[i] = a[i] + 1.f;
}

B. 增加自動向量化信息

  • 地址交疊

指針操縱同一片數(shù)據(jù)區(qū)的情況被稱為地址交疊。地址交疊會阻止自動向量化操作。

當程序不會發(fā)生地址交疊時,用 restrict 限定符(C99 引入)在代碼中聲明指針所指區(qū)域是獨立的。

下述代碼通過restrict限定 sum 與 a、b 間沒有地址交疊的情況。

// add restrict before the output parameter sum
void add(const int *a, const int *b, int n, int * restrict sum)
  • 數(shù)組尺寸

明確數(shù)組尺寸,使其達到向量化處理長度的整數(shù)倍。但應注意處理不足向量化部分的剩余數(shù)據(jù)。

下述代碼通過掩碼操作表明處理循環(huán)次數(shù)是 4 的整數(shù)倍。

// make number of cycles is an integer multiple of 4, 
for (int i = 0; i < (n & ~3); ++i)
// don't forget to process the remaining data
  • 循環(huán)展開

在一些編譯器中可以通過在 for 循環(huán)之前增加預處理語句告知編譯器循環(huán)展開級數(shù)。

下述代碼告知 armclang 編譯器希望將循環(huán)展開 4 次。

// #pragma unroll (4) // armcc
#pragma clang loop interleave_count(4) //armclang
for (int i = 0; i < n; ++i)
{
    // ...
}
  • 結(jié)構(gòu)體加載

編譯器僅會對每一成員都有操作的結(jié)構(gòu)體加載操作進行自動向量化,可以結(jié)合實際需求考慮去除用于結(jié)構(gòu)體對齊的填充數(shù)據(jù)。

下述代碼中刪除用于填充結(jié)構(gòu)體的變量 padding 以避免無法向量化。

struct st_align  
{    
    char r;    
    char g;
    char b;
    // delete the data used to populate the structure
    // char padding;
};
  • neon 加載指令要求結(jié)構(gòu)體中的所有項有相同的大小。

下述代碼中結(jié)構(gòu)體由于 short 類型與 char 類型不一致而不會被執(zhí)行自動向量化。

struct st_align  
{    
    short r; // change short to char to get auto-vectoration
    char g;
    char b;  
};
  • 循環(huán)構(gòu)造

盡量通過

下述代碼通過調(diào)整i的范圍實現(xiàn)

// use '<' to construct a loop instead of '<='
// for(int i = 1; i <= n; ++i)
for (int i = 1; i < n + 1; ++i)
{
    // ...
}

當對數(shù)組進行操作時,使用數(shù)組索引替代指針索引。

下述代碼通過 sum[i]進行索引,而不是*(sum + i)。

// replace arrary with pointer
// *(sum + i) = *(a + i) + *(b + i);
sum[i] = a[i] + b[i];

C. 重排數(shù)據(jù)實現(xiàn)緩存友好

  • 循環(huán)合并

當數(shù)據(jù)連續(xù)存儲在結(jié)構(gòu)體中時,可以進行循環(huán)合并操作,即在一個循環(huán)內(nèi)處理臨近的數(shù)據(jù),提高緩存命中率。

下述代碼將 r、g、b 三個通道的處理合并到一個循環(huán)中。

// combine the rgb operation
/*
for (...)
{
    pixels[i].r = ....;
}  
for (...)
{
    pixels[i].g = ....;
}  
for (...)
{
    pixels[i].b = ....;
}
*/

// cache friendly code
for (...) 
{    
    pixels[i].r = ....;    
    pixels[i].g = ....;    
    pixels[i].b = ....;  
}

四、總結(jié)

本章節(jié)主要介紹了自動向量化的相關內(nèi)容,其優(yōu)缺點對比如下:

49e3cf68-9110-11ed-bfe3-dac502259ad0.png

總之,雖然通過自動向量化技術(shù)我們可以在一定程度上降低向量化編程難度,增強代碼的可移植性,但是不能完全依賴于編譯器,而且有時為了獲得更高性能的代碼,還是需要通過intrinsic甚至neon匯編進行編程。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • cpu
    cpu
    +關注

    關注

    68

    文章

    10702

    瀏覽量

    209373
  • C++
    C++
    +關注

    關注

    21

    文章

    2085

    瀏覽量

    73302
  • 編譯器
    +關注

    關注

    1

    文章

    1602

    瀏覽量

    48896

原文標題:Arm NEON學習(七)CPU 優(yōu)化技術(shù)-NEON 自動向量化

文章出處:【微信號:Ithingedu,微信公眾號:安芯教育科技】歡迎添加關注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關推薦

    CPU優(yōu)化技術(shù)自動向量化實例

    SIMD 作為一種重要的并行化技術(shù),在提升性能的同時也會增加開發(fā)的難度。目前大多數(shù)編譯器都具有自動向量化的功能,將 C/C++ 代碼自動替換為 SIMD 指令。
    發(fā)表于 10-08 09:31 ?1925次閱讀

    小白快速上手Arm NEON編程手冊指南

    開始加速我們的應用了。使用NEON 技術(shù)通常有下列四種方式:調(diào)用NEON優(yōu)化過的庫函數(shù)使用編譯器自動
    發(fā)表于 07-15 15:38

    簡述ARM SVE的發(fā)展以及和NEON的區(qū)別來探討Vector在AI中的應用

    vector length是在每次循環(huán)中通過指令獲得的,這樣同一份代碼是可以不經(jīng)改動跑在不同長度的sve機器上,很好的解決了代碼移植的問題。第三,隨著編譯技術(shù)的發(fā)展,對于自動向量化的推進也獲得了很大
    發(fā)表于 09-19 15:27

    使用SVE對HACCmk進行矢量化的案例研究

    介紹隨著 Arm 引入 Scalable Vector Extensions (SVE) 作為 ARMv8-2 中的可選擴展,編譯器自動量化器可以在優(yōu)化 SVE 或 Neon 之間進
    發(fā)表于 11-08 11:50

    如何使用Arm Compiler 6自動量化功能為Neon編譯

    作為一名程序員,你可以通過多種方式使用Neon技術(shù): ?霓虹燈支持的開源庫,如Arm計算庫提供了一個最簡單的利用Neon的方法。 ?編譯器中的自動向量化功能可以
    發(fā)表于 08-02 19:31

    RealView編譯工具NEON量化編譯器指南

    RVCT提供了armcc--Vectorize,這是ARM編譯器的一個矢量化版本,它以帶有neon單元的ARM處理器為目標,比如Cortex-A8。 向量化意味著編譯器直接從C或C++代碼生成霓虹燈
    發(fā)表于 08-12 06:22

    NEON音頻編解碼器優(yōu)化技術(shù)

    本文旨在探討在采用NEON技術(shù)的ARM Cortex-A8處理器解決方案中部署音頻編解碼器時使用的各種優(yōu)化技術(shù)。
    發(fā)表于 09-02 22:59 ?26次下載

    發(fā)掘函數(shù)級單指令多數(shù)據(jù)向量化的方法

    首先分析程序的變量屬性,然后利用一組包括向量函數(shù)予句、一致子句、線性子句等編譯指示予句指導編譯器實現(xiàn)函數(shù)級向量化,最后利用變量屬性結(jié)果對向量化代碼進行了優(yōu)化。從多媒體和圖像處理領域選擇
    發(fā)表于 11-29 16:08 ?0次下載
    發(fā)掘函數(shù)級單指令多數(shù)據(jù)<b class='flag-5'>向量化</b>的方法

    基于Matrix的Givens旋轉(zhuǎn)的QR分解向量化方法

    提出一種基于Matrix的Givens旋轉(zhuǎn)的QR分解向量化方法。針對Matrix的體系結(jié)構(gòu)特點,對向量數(shù)據(jù)訪存和計算進行優(yōu)化,使計算均衡分布到各個向量處理單元;設計雙緩沖DMA的數(shù)據(jù)傳
    發(fā)表于 12-05 14:54 ?1次下載
    基于Matrix的Givens旋轉(zhuǎn)的QR分解<b class='flag-5'>向量化</b>方法

    控制流SIMD向量化方法

    SIMD擴展部件是近年來集成到通用處理器中的加速部件,旨在發(fā)掘多媒體和科學計算等程序的數(shù)據(jù)級并行.控制依賴給發(fā)掘程序中的數(shù)據(jù)級并行帶來了阻礙,當前,無論基于loop-based還是SLP的控制流向量化
    發(fā)表于 12-26 14:55 ?0次下載
    控制流SIMD<b class='flag-5'>向量化</b>方法

    DSP的并行指令分析和冗余優(yōu)化算法

    如今單指令多數(shù)據(jù)流( SIMD)技術(shù)在數(shù)字信號處理器(DSP)上得到了廣泛的應用,現(xiàn)有的向量化編譯器大多都實現(xiàn)了自動向量化的功能,但是編譯器并不適合支持DSP為特征的SIMD自動向量化
    發(fā)表于 02-24 15:17 ?0次下載
    DSP的并行指令分析和冗余<b class='flag-5'>優(yōu)化</b>算法

    MATLAB的循環(huán)向量化編程方法的詳細資料研究

    在簡要介紹MATLAB軟件基礎上,探討了MABLAB傳統(tǒng)循環(huán)結(jié)構(gòu)編程思想及循環(huán)向量化編程思想。通過實例對循環(huán)結(jié)構(gòu)編程與循環(huán)向量化編程進行比較。說明了循環(huán)向量化編程的優(yōu)點。循環(huán)向量化方法
    發(fā)表于 08-28 17:46 ?4次下載

    NEON編程中的一些常見優(yōu)化技巧

      讀過上一篇文章“ARM NEON快速上手指南”之后,相信你已經(jīng)對ARM NEON編程有了基本的認識。但在真正利用ARM NEON優(yōu)化程序性能時,還有很多編程技巧和注意事項。本文將結(jié)
    的頭像 發(fā)表于 12-12 09:11 ?1800次閱讀

    CPU優(yōu)化技術(shù)NEON 的基本原理、指令

    Arm NEON 是適用于 Arm Cortex-A 和 Cortex-R 系列處理器的一種 SIMD(Single Instruction Multiple Data)擴展架構(gòu)。
    發(fā)表于 12-19 09:54 ?4203次閱讀

    CPU優(yōu)化技術(shù)層面講解Arm NEON

    NEON intrinsic 函數(shù)提供了一種編寫 NEON 代碼的方法,該方法比匯編代碼更易于維護,同時仍然可以控制生成的 NEON 指令。
    發(fā)表于 12-26 10:18 ?1930次閱讀