天天看點

NEON優化

ARM平台NEON指令的編譯和優化

本文介紹了ARM平台基于ARM v7-A架構的ARM Cortex-A系列處理器(Cortex-A5, Cortex-A7,Cortex-A8, Cortex-A9, Cortex-A15)上的NEON多媒體處理硬體加速器針對C/C++語言、彙編語言和NEON intrinsics如何編譯和優化,包含如何向量化、向量化的ARMCC和GCC編譯器選項、NEON的彙編和EABI程式調用規範、如何在bare-metal和Linux作業系統上檢測NEON硬體、如何指導編譯器進行向量化NEON指令的優化等内容。

NEON向量化

基于ARM v7-A架構的ARM Cortex-A系列處理器(Cortex-A5, Cortex-A7, Cortex-A8, Cortex-A9, Cortex-A15)都可以選用NEON多媒體處理器加速程式運作,NEON是一種SIMD(Single Instruction Multiple Data)架構的協處理器,ARM的NEON處理器還可選配置成向量浮點VFPv3(Vector Floating-Point)指令集處理器。

常用的編譯器選項配置

自動向量化選項

armcc編譯器使用–vectorize選項來使能向量化編譯,一般選擇更高的優化等級如-O2或者-O3就能使能–vectorize選項。

gcc編譯器的向量化選項-ftree-vectorize來使能向量化選項,使用-O3會自動使能-ftree-vectorize選項。

選擇處理器類型

armcc編譯器使–cpu 7-A或者–cpu Cortex-A8來指定指令集架構和CPU類型。

gcc編譯器的處理器選項-mfpu=neon和-mcpu來指定cpu類型。如-mcpu=cortex-a5

選擇NEON和VFP類型

gcc選擇用-mfpu=vfpv3-fp16來指定為vfp協處理,而-mfpu=neon-vfpv4等就能指定為NEON+VFP結構。

選擇浮點處理器和ABI接口類型

  -mfloat-abi=soft使用軟體浮點庫,不是用VFP或者NEON指令;-mfloat-abi=softfp使用軟體浮點的調用規則,而可以使用VFP和NEON指令,編譯的目标代碼和軟體浮點庫連結使用;

-mfloat-abi=hard使用VFP和NEON指令,并且改變ABI調用規則來産生更有效率的代碼,如用vfp寄存器來進行浮點資料的參數傳遞,進而減少NEON寄存器和ARM寄存器的拷貝。

常用的CPU類型編譯器選項

CPU類型 CPU類型選項 FP選項 FP + SIMD選項 備注

Cortex-A5 -mcpu=cortex-a5 -mfpu=vfpv3-fp16

-mfpu=vfpv3-d16-fp16 -mfpu=neon-fp16 -d16表明隻有前16個浮點寄存器可用

Cortex-A7 -mcpu=cortex-a7 -mfpu=vfpv4

-mfpu=vfpv4-d16 -mfpu=neon-vfpv4 -fp16表明支援16bit半精度浮點操作

Cortex-A8 -mcpu=cortex-a8 -mfpu=vfpv3 -mfpu=neon

Cortex-A9 -mcpu=cortex-a9 -mfpu=vfpv3-fp16

-mfpu=vfpv3-d16-fp16 -mfpu=neon-fp16

Cortex-A15 -mcpu=cortex-a15 -mfpu=vfpv4 -mfpu=neon-vfpv4

常用的gcc組合編譯器選項

Cortex-A15 with a NEON unit

arm-gcc -O3 -mcpu=cortex-a15 -mfpu=neon-vfpv4 -mfloat-abi=hard -ffast-math -omyprog.exe myprog.c

1

Cortex-A9 with a NEON unit

arm-gcc -O3 -mcpu=cortex-a9 -mfpu=neon-vfpv3-fp16 -mfloat-abi=hard -ffast-math -omyprog.exe myprog.c

Cortex-A7 without a NEON unit

arm-gcc -O3 -mcpu=cortex-a7 -mfpu=vfpv4-d16 -mfloat-abi=softfp -ffast-math -omyprog2.exe myprog2.c

Cortex-A8 without a NEON unit

arm-gcc -O3 -mcpu=cortex-a8 -mfloat-abi=soft -c -o myfile.omyfile.c

NEON彙編和EABI程式調用規範

GNU assembler (gas) and ARM Compiler toolchain assembler(armasm)都支援NEON指令的彙編。但必須遵循ARMEmbedded Application Binary Interface (EABI)EABI的規範,即NEON寄存器的S0-S15 (D0-D7, Q0-Q3)用于傳遞參數和傳回值,被調用函數内可以直接使用,不用儲存;D16-D31 (Q8-Q15)則有調用函數來儲存,被調用函數内可以不儲存的随意使用;而S16-S31(D8-D15, Q4-Q7)則必須由被調用函數内部儲存。對于調用傳參規範則有,對于軟體浮點,參數有R0~R3和堆棧stack傳遞,而硬體浮點,可以通過NEON寄存器來傳遞參數。

NEON硬體檢測和使能

編譯時指定NEON單元是否存在

ARM編譯器(armcc)從4.0之後就支援在某些處理器和FPU的選項中預定義宏ARM_NEON, armasm的宏TARGET_FEATURE_NEON.

運作時指定檢測NEON單元

OS内可以檢測NEON單元是否存在,如Linux下cat /proc/cpuinfo看是否包含NEON或者VFP,

  如Tegra2 (雙核 Cortex-A9 帶 FPU),

cat /proc/cpuinfo

Features : swp half thumb fastmult vfp edsp thumbee vfpv3vfpv3d16

2

  四核 Cortex-A9 帶NEON單元

Features : swp half thumb fastmult vfp edsp thumbee neonvfpv3

  另外,可以檢視/proc/self/auxv,這裡會包含二進制格式的hwcap,可以通過AT_HWCAP來搜尋到。HWCAP_NEON bit (4096).另外如Ubuntu的釋出在路徑/lib/neon/vfp下包含lib的NEON優化版本。

Bare-metal模式下使能NEON

#include <stdio.h>
// Bare-minimum start-up code to run NEON code
__asm void EnableNEON(void)
{
MRC p15,0,r0,c1,c0,2    // Read CPAccess register
ORR r0,r0,#0x00f00000   // Enablefull access to NEON/VFP by enabling access to
                        // Coprocessors 10 and 11
MCR p15,0,r0,c1,c0,2    // Write CPAccess registerISB
MOV r0,#0x40000000      // Switch onthe VFP and NEON hardware
MSR FPEXC,r0            // Set EN bit inFPEXC
}
      

下面的EnableNEON函數使能NEON協處理器;使用下面的編譯選擇就能在bare-metal下使能NEON

armcc -c --cpu=Cortex-A8 --debug hello.c -o hello.o
armlink --entry=EnableNEON hello.o -o hello.axf
      

系統運作時使能NEON

核心在遇到第一個NEON指令時會産生一個UndefinedInstruction的異常,這會讓核心自動重新開機NEON協處理器,核心還可以在上下文切換時關閉NEON來省電。

Linux核心的NEON配置

NEON優化

          圖1. NEON的Linux核心配置

使能NEON,需要選擇以下選項

Floating point emulation → VFP-format floating point maths

  Floating pointemulation → Advanced SIMD (NEON) Extension

檢查Linux的配置檔案來确認核心是否使能NEON

zcat /proc/config.gz | grep NEON

  看是否存在

  CONFIG_NEON=y

确認處理器是否支援NEON

cat /proc/cpuinfo | grep neon

  看是否有如下内容

  Features : swp half thumb fastmult vfp edsp neon vfpv3 tlsvfpv4 idiva idivt

向量化NEON優化指南

避免指針混疊alias

  C90不要求指針位置,不同指針可以指向相同的記憶體區域,C99中引入了__restrict關鍵字來表明隻有這個指針能指向它工作的區域。

告訴編譯器循環資訊

  如循環是否某個整數的整數倍,以友善向量化;如下表明循環次數是4的整數倍:

for(i=0 ; i < (len & ~3) ; i++)
{
...
}
for (i=0; i<(items*4); i+=1)
{
...
}


#pragma unroll (n)
      

采用NEON Intrinsics

armcc, GCC/g++和llvm等編譯器都支援 NEON C/C++ intrinsics,并且采用相同的文法規範。因而代碼可以在各個編譯器間共享。NEON Intrinsics的代碼容易維護而且效率高。NEON Intrinsics采用新的資料類型,這些類型對應于D和Q寄存器。NEONIntrinsics寫起來像是函數調用但對應于每一條NEON指令。程式設計NEON Intrinsics時不用考慮具體的寄存器配置設定和代碼的schedule,pipeline流水安排等。但NEON Intrinsics往往不能産生想象的代碼,性能上相比純彙編要稍差一些。

減少循環内的相關性

如果目前疊代時使用的資料是上次疊代計算的結果,就産生了疊代間的相關性,可以拆分循環來減少相關。

向量化其他準則

短小的循環更容易讓編譯器實作自動向量化; 避免在循環内使用break退出循環 避免在循環内使用過多的條件語句,減少可能産生的條件跳轉; 讓循環次數盡可能是2的幂次 讓編譯器知曉循環次數,減少對循環次數為0等的判斷; 循環内調用的函數盡量inline内聯 使用數組+索引的方式通路比指針形式更容易向量化; 間接尋址(多重索引)不會向量化; 使用restrict關鍵字來告訴編譯器沒有重疊的記憶體區域;

總結

繼續閱讀