ARM SIMD NEON 简介 (翻译自 Introducing NEON Development Article)
目錄
- NEON簡介
- SIMD是什么?
- ARM SIMD 指令集
- NEON是什么?
- NEON架構概覽
- 支持的數據類型
- NEON寄存器
- NEON指令
- NEON開發
- 匯編器
- Intrinsics
- 自動向量化
- 使用 NEON 優化庫
NEON簡介
這篇文章介紹了ARM NEON技術,這項技術首次實現是在ARM Cortex-A8 處理器上。這篇文章將講述通用的SIMD概念以及NEON架構,并給出如何利用此技術的概要描述,包含以下章節:
- SIMD是什么?
- NEON是什么?
- NEON的架構概覽
- NEON開發技術
SIMD是什么?
一些現代軟件,尤其是多媒體編解碼軟件和圖形加速軟件,有大量的少于機器字長的數據參與運算。例如,在音頻應用中16位以內數據是頻繁的,在圖形與視頻領域8位以內數據是頻繁的。
當在32位微處理器上執行這些操作時,相當一部分計算單元沒有被利用,但是依然消耗著計算資源。為了更好的利用這部分閑置的資源,SIMD技術使用一個單指令來并行地在同樣類型和大小的多個數據元素上執行相同的操作。通過這種方法,硬件可以在同樣時間消耗內用并行的4個8位數值加法運算來替代通常的兩個32位數值加法運算。
ARM SIMD 指令集
ARMv6 架構引入了SIMD指令集的一小部分,對打包到標準 32 位通用寄存器中的多個 16 位或 8 位值進行操作。這允許某些操作以兩倍或四倍的速度執行,而無需實現額外的計算單元。這些指令的助記符通過將 8 或 16 附加到基本形式來識別,以指示操作的數據值的大小。
Figure1.1 展示了UADD8 R0,R1, R2指令操作。這個操作展示了以向量形式存儲在通用寄存器R1和R2中的4個8位數值的并行加法運算。最終結果也以向量形式存儲到寄存器R0。
Figure1.1 4-way 8-bit unsigned integer add operation
NEON是什么?
ARMv7 架構引入了高級 SIMD 擴展作為 ARMv7-A 和 ARMv7-R 配置文件的可選擴展。NEON通過定義存儲在 64 位雙字長的寄存器D 和128 位四字長的寄存器Q中的向量操作指令組來擴展 SIMD 概念。
用在ARM處理器上的高級SIMD擴展的實現稱為NEON,這是架構規范之外使用的通用術語。NEON技術在當前所有ARM Cortex-A系列處理器上得到了實現。
NEON 指令作為 ARM 或 Thumb 指令流的一部分執行。相比使用額外的加速器,這簡化了軟件的開發,調試和集成。傳統的ARM或Thumb指令管理所有程序流程和同步。NEON指令涉及以下管理:
- 內存訪問
- NEON與通用寄存器之間的數據復制
- 數據類型轉換
- 數據處理
Figure1.2 展示了VADD.I16 Q0, Q1, Q2 指令如何并行地執行存儲在Q1,Q2中的8通道16位數值的加法運算,最終結果存儲到了Q0。
Figure 1.2 8-way 16-bit integer add operation
NEON架構概覽
ARM架構定義高級SIMD擴展作為協處理器10和11的一部分,協處理器10和11同時也用于向量浮點擴展(VFP)。雖然架構層面并不要求VFP和NEON同時實現,但是鑒于這些擴展在編程模型層面的共同特征,一個支持VFP的操作系統僅需很少甚至無需修改即可支持NEON。
當考慮對一個特定處理器進行NEON代碼優化時,你可能不得不考慮處理器集成NEON技術的具體實現定義。這意味著即使 NEON 指令周期時序相同,針對特定處理器優化的指令序列在不同處理器上也可能具有不同的時序特征。
更多信息可以查看ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition 中關于高級SIMD擴展部分,其中包含了指令列表和編碼。你可以在http://infocenter.arm.com獲取有用信息。
支持的數據類型
NEON指令集支持8位,16位,32位和64位有符號和無符號整型。
NEON 還支持 32 位單精度浮點元素,以及 8 位和 16 位多項式。
VCVT指令用于單精度浮點元素與以下元素的類型轉換:
- 32位整型
- 定點數
- 半精度浮點,如果處理器實現了半精度擴展。
NEON寄存器
NEON 寄存器組由 32 個 64 位寄存器組成。 如果同時實現了 Advanced SIMD 和 VFPv3,那么它們將共享這個寄存器組。在這種情況下,VFPv3 以支持 32 個雙精度浮點寄存器的 VFPv3-D32 格式實現。這種集成簡化了上下文切換支持的實現,因為同一個例程既保存和恢復 VFP 上下文也保存和恢復 NEON 上下文。
NEON單元可以把同一個寄存器組看作:
- 16個128位四字長寄存器組,Q0-Q15
- 32個64位雙字長寄存器組,D0-D31
NEON D0-D31寄存器和VFPv3 D0-D31寄存器是一樣的,且每一個Q0-Q15寄存器都映射到一對D寄存器上。Figure1.3 展示了共享的NEON和VFP寄存器組的不同視圖。所有這些視圖都是隨時可訪問的。軟件層面不需要顯式地在他們之間切換,因為使用的指令決定了相應的視圖。
Figure1.3 NEON and VFP register set
NEON指令
NEON指令僅提供了數據處理以及加載/存儲操作,并集成到了ARM和Thumb指令集中。標準的ARM和Thumb指令管理了整個程序的控制流程。
NEON指令的編碼相當于協處理器操作,和VFP指令一樣,在協處理器10和11發揮作用。NEON和VFP指令按字母順序分組到一起,因為他們所有助記符的首字母都有V。
大多數指令都可以在指令編碼中指定的不同數據類型上運行。 軟件通過在指令助記符后加后綴來指定數據的字長。操作數據元素的數量由指定的寄存器長度決定。例如,VADD.I16 q0, q1, q2表示對存儲于128位Q寄存器中的16位整型數據的操作。這意味著這個操作可以達到8通道16位數據的并行計算。
有些指令的輸入和輸出寄存器可能有不同的大小。例如,VMULL.S16 Q0, D2, D3并行地乘以四個16位通道,并在128位目標向量中產生四個32位的結果,如下圖(譯者注)。
為了提高代碼密度和性能,NEON指令集包括結構化加載和存儲指令,可以從矢量寄存器的單個或多個通道加載數據,也可以將單個或多個數據存儲到矢量寄存器的通道。NEON還包括在多個向量寄存器和存儲器之間傳輸完整數據結構的指令,包括交叉(interleaving)和解交叉(de-interleaving)。
NEON開發
為了更好的使用新特性,你必須使用最新版本的編譯工具。GNU 工具和 RealView Compilation Tools(RVCT) 的最新版本都支持NEON指令集。
匯編器
最直接的使用NEON單元的方式就是寫匯編代碼。NEON 指令集的一致性設計使編寫匯編代碼比您預期的要簡單。
GNU和RVCT匯編器使用同樣的指令格式,但是語法略有不同。不同的地方包括:
- 匯編器指令
- 標簽的格式(label)
- 注釋的符號
例1.1 展示了一個使用GNU匯編器執行NEON指令的匯編函數。例1.2 展示了RVCT格式的相同的代碼。這兩個示例都使用硬件浮點鏈接,這意味著軟件在 NEON 寄存器中傳遞和返回參數。
Example 1.1
.text.arm.global double_elements double_elements:vadd.i32 q0,q0,q0bx lr.end要使用GNU匯編器匯編示例 1.1 中的代碼,請將 -mfpu=neon 添加到匯編器命令行。這一指定使得NEON指令被允許使用。例如:
arm-none-linux-gnueabi-as -mfpu=neon asm.s
Example 1.2
AREA RO, CODE, READONLYARMEXPORT double_elements double_elementsVADD.I32 Q0, Q0, Q0BX LREND要使用RVCT匯編示例1.2中的代碼,請一定要指定一個目標處理器以支持NEON指令集。例如:
armasm --cpu=Cortex-A8 asm.s
Intrinsics
(Intrinsics不太好翻譯,我理解就是內置函數,在C/C++高級語言規范里提供了一套內置函數來支持SIMD向量化)
Intrinsics(內置函數和數據類型)提供了類似與內聯匯編一樣的功能,并提供了像類型檢查和自動寄存器分配這樣的附加功能。一個Intrinsic函數在C或C++中以函數調用的形式出現,然后再編譯階段被替換為一系列低級別的指令。這意味著你可以在高級別語言中表達低級別架構行為。
除了提供給程序員直接訪問指令(和高級別語言沒有很好的映射起來)的能力之外,使用 Intrinsic 函數意味著編譯器可以優化操作以提高性能。 使用 Intrinsic 意味著開發人員不必考慮寄存器分配和互鎖問題,因為編譯器會處理這些問題。
GCC和RVCT支持相同的NEON Intrinsic 語法,使得C/C++代碼在不同工具鏈之間可移植。為了添加對NEON Intrinsic 的支持,需要引入頭文件arm_neon.h。例1.3 在C中使用 Intrinsic 替代匯編指令,實現了和匯編示例同樣的功能。
Example 1.3
#include <arm_neon.h>uint32x4_t double_elements(uint32x4_t input) {return (vaddq_u32(input,input)); }編譯示例
盡管GNU和RVCT開發工具支持同樣語法的NEON Intrinsics,但是這兩個工具鏈的命令行語法不盡相同。
使用GCC編譯NEON Intrinsics
為了在GCC中使用NEON Intrinsics,你必須指定-mfpu=non編譯選項。
arm-none-linux-gnueabi-gcc -mfpu=neon intrinsic.c
根據您的工具鏈,您可能還必須添加-mfloat-abi=softfp 以向編譯器指示必須在通用寄存器中傳遞 NEON 變量。
支持的Intrinsics完整列表可以在 http://gcc.gnu.org/onlinedocs/gcc/ARM-NEON-Intrinsics.html 中找到。
使用RVCT編譯NEON Intrinsics
當你在編譯選項中指定一個支持NEON指令的目標處理器,那么RVCT就可以接受NEON Intrinsics。例如:
armcc --cpu=Cortex-A9 intrinsic.c
你可以查閱 RVCT 編譯指南(通過 http://infocenter.arm.com)來獲取更多關于RVCT支持的Intrinsic函數和向量數據類型的信息。
自動向量化
編譯器也可以針對你的C/C++源代碼提供自動向量化的能力。這無需編寫匯編代碼或使用 Intrinsic 函數即可獲得較高的NEON性能。這允許您的源代碼在不同工具和目標平臺之間保持可移植性。
因為C語言并沒有指定并行行為,所以你需要給編譯器額外的提示,告訴編譯器那塊是安全且最佳的。您可以在不影響源代碼在不同平臺或工具鏈之間的可移植性的情況下做到這一點。
例1.4 展示了一個編譯器可以安全且最佳地向量化的小函數。編譯器能夠進行向量化的原因是程序員使用了 __restrict 關鍵字來保證指針 pa 和 pb 不會尋址重疊的內存區域。
void add_ints(int * __restrict pa, int * __restrict pb, unsigned int n, int x) {unsigned int i;for(i = 0; i < (n & ~3); i++)pa[i] = pb[i] + x; }編譯示例
盡管GNU和RVCT開發工具支持同樣的源代碼語法,但是命令行語法還是不盡相同。
使用GCC自動向量化
為了開啟自動向量化,你必須添加-mfpu=neon和-ftree-vectorize選項。例如:
arm-none-linux-gnueabi-gcc -mfpu=neon -ftree-vectorize -c vectorized.c
根據您的工具鏈,您可能還必須添加-mfloat-abi=softfp 以向編譯器指示必須在通用寄存器中傳遞 NEON 變量。
您可以通過將 -ftree-vectorizer-verbose=1 添加到命令行來請求更詳細的編譯器輸出。 這使得編譯器輸出以下信息:
- 編譯器向量化的代碼
- 編譯器不能向量化的代碼,并給出不能向量化的原因提示
你可以利用這些信息將代碼修改為編譯器可向量化的形式。某些版本的 GCC 支持大于 1 的 verbose 參數值,從而提供有關向量化的更多詳細信息。
使用RVCT自動向量化
為了開啟自動向量化,你必須指定一個實現了NEON技術的目標處理器,開啟編譯優化選項-O2或更高級別優化選項,以及添加編譯選項-Otime和--vectorize。例如:
armcc --cpu=Cortex-A9 -O3 -Otime --vectorize -c vectorized.c
注意
僅指定 --vectorize 是不行的,您還要指定 -Otime 和 -O2 或 -O3 優化級別才會啟用自動矢量化。
因為浮點值的并行累積會降低通過對輸入數據進行排序獲得的精度,所以除非您在命令行上指定 --fpmode=fast,否則這些會被禁用。
您可以通過在命令行中添加 --remarks 來請求更詳細的編譯器輸出。 這提供了有關編譯的許多方面的附加信息。 對于 NEON 矢量化,這包括:
- 編譯器向量化的代碼
- 編譯器不能向量化的代碼,并給出不能向量化的原因提示
這些信息可用于將代碼修改為編譯器能夠向量化的格式。
使用 NEON 優化庫
在你的系統使用NEON技術最簡單的方式是通過使用已經經過NEON優化過的庫。
OpenMAX
OpenMAX 是由 Khronos Group 創建和分發的免版稅跨平臺 API 標準。ARM 創建了 OpenMAX 開發層 (DL) 的 ARMv7 NEON 優化實現。你可以在http://www.arm.com下載這個庫。
例 1.5 通過調用 OpenMAX 函數 omxSP_DotProd_S16() 計算兩個有符號 16 位整數向量中的值的點積。當使用 ARMv7 優化的 OpenMAX DL 庫時,此函數是使用 NEON 矢量運算實現的。
#include <omxSP.h>OMX_S16 source1[] = {42, 23, 983, 7456, 124, 11111, 4554, 10002}; OMX_S16 source2[] = {242, 423, 9832, 746, 1124, 1411, 2254, 1298};OMX_S32 source_dotproduct(void) {OMX_INT len = sizeof(source1)/sizeof(OMX_S16);return omxSP_DotProd_S16(source1, source2, len);}總結
以上是生活随笔為你收集整理的ARM SIMD NEON 简介 (翻译自 Introducing NEON Development Article)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【genius_platform软件平台
- 下一篇: 互联网项目开发流程大全