2017年1月30日 星期一

從 "Three tips for maximising your SoC performance" 看軟體優化

先前 ARM 官方提供了 Free Webinar "Three tips to Maximize your SoC performance"
目前簡報的錄影與投影片都已經上線, 填入一些個人資訊即可閱覽

從報告的內容可以看出這個簡報是為了其 "System Guidance" 服務提高能見度
但是從設計原則中也有許多軟體人員可以作為借鏡並思考的地方
而以簡報中對於 SoC 設計上三個對於效能的考量為:
  • 最短化自 CPU 到記憶體路徑 - 每個 clocks cycle 都會歸屬到 CPU latency
  • 最大化系統頻寬 -  確認系統記憶體已經對於效能優化
  • 依流量種類管理流量 - 在系統符合即時性的情況下, 儘可能提供 CPU 優先權

而從軟體優化的角度來看, 這三點反映了對於效能上, 實作上的考量為:
  • 評估各種類型記憶體的 memory latency: 了解 memory latency 才能評估效能, 並且透過理解 cache 管理的機制來避免不當的行為所造成的過長存取時間. 此外理解 CPU 的硬體設計是透過何種方式來克服這問題, 以此來分析軟體上對於硬體差異在效能落差的影響.
  • 量測平台系統的頻寬: 透過 profiling 與 behavior analysis 能了解何時程式達到了 bandwidth bound, 才能採取對應的優化方式
  • 了解應用上可能同時競爭頻寬的硬體: 應用上所仰賴的 GPU 與 硬體加速器都會消耗頻寬
對於 latency 問題主要採取的策略有上述三項, 這三者的意義分別是紙上架構推估, 以軟體模擬
推測, 實際硬體上的量測, 三個不同階段

上圖為以 SGM-773 (System Guidance for Mobile) 的推估 memory latency 的方式, 若僅考量單次的存取, 請注意 85.1ns 這個推估時間約為 209 CPU cycles (也可以看出 CPU 內 cache 為 15 CPU cycles, 而出了 CPU 到了 CCI 則會超過 50 CPU cycles ). 這樣的計算通常能夠以此推敲 best case 與 worst case (考量各階段的 queue depth)的狀況, 也能得知架構上的物理限制, 與合理的數值範圍.

此外 LMBench 也是不錯的工具套件, 以 latency 來說是其中 lat_mem_rd 這個工具,下圖為使用 SGM-773 平台透過 LMBench 去量測後的結果

有興趣者可以先於自己的 ubuntu 平台上安裝 lmbench 套件, 接著執行下列指令:
/usr/lib/lmbench/bin/x86_64-linux-gnu/lat_mem_rd 16 128
其中 16 代表為 16MB, 也就是 bench 的大小上限為 16MB
而 128 所代表的是 stride 大小, 執行後可以看到類似下列的 log:
"stride=128
0.00049 2.361
0.00098 2.361
0.00195 2.362
0.00293 2.361
0.00391 2.361
0.00586 2.361
0.00781 2.361
0.01172 2.361
0.01562 2.362
0.02344 11.838
0.03125 11.819
0.04688 11.827
0.06250 11.816
0.09375 11.818
0.12500 11.825
0.18750 11.822
0.25000 11.817
0.37500 12.190
0.50000 12.182
0.75000 12.188
1.00000 12.188
1.50000 12.249
2.00000 19.838
3.00000 37.352
4.00000 38.298
6.00000 39.394
8.00000 39.920
12.00000 40.455
16.00000 40.674
這裡每行的兩個數字, 前者為該次測試的大小, 後者為測出的 latency 以 ns 計
上面測試的為個人使用 A8-5545M 平台所測出的 (L1: 16KB, L2: 2MB)
可以觀察到對應兩個 size 的 latency 轉折


事實上, LMBench 也提供了 bandwidth 的估測工具 - bw_mem
簡易執行指令如下:
/usr/lib/lmbench/bin/x86_64-linux-gnu/bw_mem  16K rdwr
也就是測試以 16MB 為大小的 Read/Write 頻寬, 有興趣亦可以調整前面的 16K 的數字
輸出有兩個數字:
0.016000 9801.85
前者為所要求測試的大小 (16KB 為 0.016M), 後者為 MB/s (也就是測出為 9891.85MB/s)
了解系統的能力, 對於針對平台的軟體優化有相當的幫助 (像是 tiling 與批次演算作法, 其分割大小能夠以 L2 大小做考量)

2017年1月25日 星期三

解析 Qualcomm Hexagon 680 架構 III - Thread Model and Execution Model

Thread Model

最後讓我們講述 Hexagon 680 的 Thread Model
在 Qualcomm 開發 DSP 的過程中一直都有 Hardware Threads 的支援
因此在談 Hexagon 680 的 Thread Model 前, 讓我們先來看看 V5x 的 HW Threads

上圖為 Qualcomm 在 2013 年 Hotchips 會議上投影片的 Page 11
從這圖可以看到 Hexagon 680 的 V6 基本上與 V5 架構差異不大
也是 4-slot VLIW, 兩個是 Data Unit, 兩個是 eXecution Unit
同個 DSP 核心具有3個 Thread Context, 在硬體設計上已考慮簡化多工上的需求

在 2015 年度 Hotchips 中 Qualcomm 簡報的 Page 8:
由此可見, V6 Scalar DSP 以 2Ghz 頻率運作
俱備 4 HW Threads 支援, 每個 thread 以 500Mhz 運作
而 HVX coprocessor 以 1Ghz 頻率運作, 具有兩個 context, 一樣每個 thread 以 500 Mhz 運作
因此同一時間可以有兩個 Thread 控制著 HVX, 另外兩個處理 Scalar 工作
但光以上看投影片, 不知道是否有人懷疑過, 或許 V6 DSP 為 quad core, 而 HVX 為 dual core?

這裡我們回到 HVX 官方文件當中:
從這張圖可以看出, V6 DSP 具有的是 1C4T, 而 HVX 為 1C2T
但是, 官方在投影片與到此中所說明的, 並非 Hexagon 680 所有的模式
如果還有印象, 在系列文II當中留了一些不確定:
1. HVX 對於 L2 的總寬度為 512b x2, 而非 1024b x 1
2. 對於 VPF/VRF 的描述, 我加了 "基本上" 這個有所保留的詞

再談這之前, 先來介紹 HVX 的 Vector Modes:

也就是 HVX 中支援了兩種不同長度的 Vector
  • 64B 模式, Vector 寬度為 512 bits
  • 128B 模式, Vector 寬度為 1024 BITS
而這樣的模式並不是像 NEON 中分為長短 vector 並且可以混用, 而是如上圖, 直接將所有的單元(VX, VPF, VRF), 以 512b 寬度的方式等分為二

所以除了 1C2T(128B) 的方式外, 事實上 HVX 還存在另一種 HW thread 的型態 - 1C4T (64B)
在這個模式下, 4 個 V6 HW Threads 各自都可以控制一個 64B Vector Mode 的 VX Thread Context. 所以 Hexagon 680 提供多元且彈性的運作模式. 而這也說明了 系列文II 當中沒交代清楚的部分.

Execution Mode


Qualcomm 曾經官方撰文介紹過 Hexagon DSP 的軟體開發
在 Qualcomm 裝置上, 在 DSP 上執行工作需要幾個步驟:
  1. 下載與安裝 Qualcomm Hexagon SDK
  2. 將你於 DSP 上實作的功能 API 以其 IDL (Interface Description Language) 方式描述.
  3. SDK 會自動產生 Header files 與 stub (下圖中應用處理器端, 也就是 ARM 處理器端提供給 client 的呼叫介面, client 通常是其他的 library 或是 executable)及 skel (DSP 端去呼叫你實作功能的進入點) 等相關函式庫.
  4. 在 DSP 端實作你的 API, 並與第3點中自動產生的 skel 函式庫 link 成為一個 shared object 並放置於該平台裝置中.
  5. 將你的 native APP 與 link 到第3點自動產生的 stub 來呼叫你的 API.
  6. (選擇性) 建立你的 API 專屬的 Java bindings 好讓 APK 能夠使用.
而這些背後所仰賴的機制的核心即是上圖所描述的 FastRPC

一旦 Native APP 呼叫所需的 function, 便會執行自動產生的 Stub lib, 它會 透過 ARM 與 DSP 間橋接的 Driver-Framework, 載入先前所產生的 DSP 端的 shared object, 而 DSP 端上的 OS 會動態地載入該 object, 並且以 Skel 介面處理來自 ARM 處理器端的請求.

2017年1月22日 星期日

解析 Qualcomm Hexagon 680 架構 II - Hexagon Vector eXtension (HVX)

在講解 680 中的 SIMD 單元 - HVX 之前, 還是先以系列文 I 的 blocks diagram開頭, 並且今日重點會是文中提到第3點的官方文件

從 blocks diagram 中可以看到 HVX 由三個主要部分所組成
  • VX : Vector Extension Unit, 也就是 HVX 的 SIMD 指令執行單元
  • VPF : Vector Predicate register File, 這部分是用來處理 SIMD 的中較不易處理的 branch, if-else 等 divergence. 以 8bit 運算來說, 若需要提供 SIMD 中每個 lane 所需的 true/false boolean 資訊, 需要 1024/8 = 128 bit, 而 Predication Register 寬度, 基本上就是 128b.
  • VRF : Vector Register File, 這即是 VX unit 中用來存放的暫存器, HVX 為 1024b vector, 因此基本上這部分為 1024b
而對外的介面有兩個:
  • Co-Processor Instruction Port: 用來與 V6x 處理器溝通的, V6x DSP 是透過這個介面將指令送給 HVX 單元執行
  • 兩組 512b-wide L2-cache/L2-TCM port: 相較 V6x 處理器對於 L2 的頻寬為 256b port, HVX 對於總寬度為 512b x2 , 這樣的設計很重要, 這讓 HVX 能夠更快的取得所需要的 vector 資料, 由於 HVX 為寬度 1024b vector 的 SIMD Unit, 因此這樣的寬度是有必要的. 至於為何是兩組 512b 而非一組 1024b, 這部分與其 thread model 有關, 在下一篇會介紹這個部分.

首先來談 VPF/VRF 的 Register File 部分, 在 HVX 中分別有著 32組 vector register 以 V0 ~ V31 表示, 而 predicate register 共有 4組, 以 Q0 ~ Q3 表示, 這些表示法用在 SDK toolchain 所產生的 .s assembly 中, 對於 V0 ~ V31, 並沒有區分資料型別, 因此為了表示使用的精確度, 在指令中對於 register 使用的方式必須有標記所
從語法中可以看出兩點, 首先是並不支援 float 運算(當然以這樣的notation 要新增也不是難事就是了), 再者是 SIMD 的 operation 與 data type 是脫勾的, 重點是搭配的 register data type, 以上面的例子來說
V2.h = VADD(V3.h, V4.h)
這個 asm 指令是指套用的是 add 指令, 並且資料型別是 h 所代表的 signed halfword (也就是通常 C 中的 short), 因此這行所代表的意義為: 將 V3 與 V4 的資料以 signed 16b 的方式相加後存入 V2. 這樣的 asm 設計也直接影響到了 HVX 在 C intrinsics 上的 coding style, 而這樣的設計其實有好有壞. 好處顯而易見的是 asm 很簡單, 而且能有效的降低寶貴的 register 浪費, 但這樣的設計當 export 到 C 的層次的時候, 就缺乏了對於各資料型別的 vector type, 因此沒有對應型別的 operator overloading, 但這點若採用 C++ 去自己實作也並不難就是了.

接著進入 HVX 的指令類別, HVX 有著六類的指令類型, 在系列文 I 當中也討論過 V6x 各個 slot 的支援, 而 HVX 也是使用相同 VLIW4 的設計, 針對這六類支援的指令, 於官方文件中有著下列表格:
從表格中可以出六類分別為:
  • Aligned Memory Load
  • Aligned Memory Store
  • Unaligned Memory Load/Store
  • Multiplies and special ALU
  • Vextract
  • Simple ALU, Permute, Shift
對於表格的呈現方式, 各位或許會覺得很奇怪, 為何不是像 V6x 處理器中那樣, 是以 slot 為主要分類, 而是以 instruction type 為主要分類. 這主因在於對於同一 cycle 中4個指令的使用上搭配要考慮的不是 slot 本身而已, 因為對於 HVX 來說支援每個 instruction type 所動用到內部的 "resource" 不同. 那什麼是 resource  呢?

在官方文件中有說明, HVX 本身由六種 resource 所組成, 這些 resource 讓 SIMD 指令得以運作
因此弄懂每個指令背後所動用到的 resource 也是重要的:
等等!!! 不是6類嗎, 怎麼變成11類了? 其實細看一下, 你會發現它只是將一些指令細分, 特別是第4類與第6類再去細分, 但是基本上還是那六類. 像是 Multiply 區分了 8b 與 16b 的部分, 這裡請注意, 8b 與 16b 這兩者分別使用的 Multiply Resource 數目不同, 所以這裡讓我們以官方在 Hotchips 中的 slides 對於效能作進一步解釋的說明:
上面這張 Qualcomm 官方投影片很常出現在一些淺談 Hexagon 680 與 HVX 的文章中, 但請注意其中的第2大點的 8b 與 16b 的乘法數目, 相信很多人很難理解(或者根本沒弄懂)為何 8b 的乘法數目為 16b 的 4倍, 因為 1024/8 = 128, 而 1024/16 = 64, 單以 SIMD 的寬度是無法解釋這樣的落差, 但這時若將指令背後 resource 的消耗數目納入考量就會很清楚了, 因為 HVX 處理器中提供了兩組 Multiply Resource, 而 16b 的乘法指令按照上面提供的表格一次即會消耗完兩組,  而 8b 的乘法指令只會消耗一組, 因此同一個 cycle 中, 至多只能塞入一個 16b SIMD 乘法 也就是 1024/16 = 64 個, 而 8b 可以一次執行兩個 8b SIMD 乘法, 也就是 (1024/8)*2 = 256 個.

所以若不考慮 data dependency 因素, 一個指令能否再塞進一個 VLIW 指令中以同一cycle執行, 這背後必須要同時考量 slot 是否 conflict, 以及 resource 是否有餘裕, 這兩者同時都滿足, 才能夠塞進同一個. 對於這樣的考量, 官方文件提供了一份整理好的總表
在這份總表中, 查找所需滿足的條件, 即反映了是否需要額外的 cycle 去執行所想加入的指令. 另外需要注意一件事, 請注意 resource 使用的平衡性, 像是 shift unit 只有一個, 千萬不要不明就裡, 將乘法全以 shift 替換就以為是優化, 在這樣的情況下, 有可能增加的 cycle 數目是以倍數來計, 對於這樣的處理器, 優化的策略在於儘量在乘法與Shift所需要的運算數目中取得平衡. 其他比較常考慮的就是 load/store unit.

對於 HVX 指令中是否有讓我覺得印象深刻的? 我個人認為是 vdelta/vrdelta 以及 permute 所屬指令, 這個類別與一般 DSP 常提供的 PACK/UNPACK 太不同了, 它提供了一個 general permute network 來做兩個 vector register data 的次序調移.
其實當下我覺得這東西一時間我只想到 sorting network, 而且還搞不清楚狀況, 直到看到了一個圖解, 發現這 1 cycle 運算真是潛力無窮, 所有 1-1 mapping 的方式的次序改變, 只要能算得出 Vv, 就可以快速地作 Vu 至 Vd 的轉換. 而對於像我腦子這麼簡單的人, 它也提供了入門版的 shuffle 與 pack 指令.
最後來談 HVX 的資料取得方式來源 - L2-cache/L2-TCM, 對於一般 DSP 在資料傳輸與核心運算的平行部分, 儘管 HVX 文件沒有特別強調 DMA, 但實際上 HVX 提供了對應功能的 L2FETCH, 以一個多媒體為訴求的 DSP  來說, 其也支援了 1D/2D 資料的傳輸.

而到此並沒有特別的地方. 值得一提的是, 這裡的 Level-2 是只對於 V6x 而言, 但對於 HVX 的角度來說, 它就是如同 L1 的存在, 最特別地方在於 V6x 架構中 L2 可以扮演兩個角色, 在 block diagram 中已經標明了
  • L2 data cache
  • L2-TCM
這設計是很有趣的, 因為這兩種記憶體各自有著不同且互補的特性:
  • cache本身有著 transparent 特性, 對於 programmer 本身並不需要特別改變, 只需要注意沒有不利於 cache 管理機制的存取方式,  cache 能夠帶來效能的 scalability (隨著 cache 的加大而效能改善), 功能上的 portability (無論 cache 大小, 功能本身的正確性不會影響), 這樣的特性對於一些高度 locality 的程式很有幫助, 也能降低程式的實作複雜度, 但是 cache 本身有著難以精確控制的特性, 對於一些資料的存取並不能有時間上的保證.
  • Tightly-Coupled Memory (TCM), TCM 有著指定的位置, 距離 processor 很近, 有著存取時間上的保證, 但是由於使用上 Programmer 必須意識到它的存在, 並且自己管理, 所以 TCM 若容量增大也在不修改程式的情況下無法直接增加效能; TCM 若容量變小在不修改程式的情況下程式可能無法正常運作. 但是它卻能有時間上存取的保證, 因此對於一些像是 Lookup-Table (LUT) 的需求, TCM 顯得相當重要
在 V6x 中能將 L2-cache 做分割, 雖然相關機制未知, 但是運作上能將部分 L2 作為 Cache 而部分作為 TCM 是標注在 V6x 的 features list 中:
下篇文, 沒意外的話也是系列文最後一篇, 要談論的是 Hexagon 680 的 Thread Model 以及 Execution Model

2017年1月21日 星期六

解析 Qualcomm Hexagon 680 架構 I - V6x 架構

Qualcomm S835 於不久前發佈
其中內建了強大的 Hexagon 682 DSP
雖然在 S835 的發佈宣傳文中僅說明了新增了對於 TensorFlow 與 Halide Language 的支援
但是還是有發佈專文說明 Hexagon 682 DSP 即便與 Adreno 540 GPU 相較下
在 TensorFlow 與 Machine Learing 方面的效能與功耗效率的其表現出色
儘管 DSP 處理器這字眼對於程式工作人員並不陌生
但除了特定領域面向的工作者外, 有機會接觸者並不多
這與應用平台的可見度與普及性
以及各家 DSP 相關的軟硬體在架構與軟體資訊較為封閉有關

Qualcomm 自 S820 加入的 Hexagon 680 這顆運算性能強大的 DSP
(事實上先前即有 V5 的 DSP,  但是 HVX 讓 V6 增色不少)
相關文件與 SDK 皆有公開釋出, 所以是個很好的說明範例
而 Hexagon 682 應該與 680 相去不遠
因此這裡以系列短文介紹探討這顆 DSP 的架構

探討的內容為 Qualcomm 官網內的一份簡報與兩分文件 (需註冊帳號, 免費)
1. Architecture of the Hexagon 680 DSP for Mobile Image and Computer Vision
2. Hexagon V60/V61 Programmer's Reference Manual
3. Hexagon V60 HVX Programmer's Reference Manual

Hexagon 68x 為 Hexagon V6x 架構的處理器
而 HVX 為 Hexagon Vector Extension 的縮寫, 以 Coprocessor 的方式與 V6x DSP 整合運作
下圖取自官方文件 3. 的系統 blocks diagram
首先讓我們先以上圖來討論 Hexagon V6x DSP
Hexagon DSP 主要由 5 個部分所組成, 其中各個 block 意義如下:
  • D$ - Data Cache
  • RF - Register File
  • I$ - Instruction Cache
  • XU - eXecution Unit
  • MMU - Memory Management Unit
另外有3個對外的控制介面, 分別是:
  • AXI, AHB 對外的 Bus 與 Host 端系統整合
  • 256b-wide L2-cache port
  • Co-Processor Instruction Port 控制 Hexagon VX Co-processor
其中值得注意的地方是 AXI/AHB 以及 L2-cache port 中, V6x 內部中以 L2 Control 與 cache state/tag table 的 function block 連接著, 整個 Hexagon DSP 的資料, 都仰賴著 V6x processor 統一對外透過 AXI/AHB 而取得, 若了存入 L2-cache, 相關資訊則會以 L2 state/tag 紀錄, V6x processor 本身即能夠完全掌握系統中 L1 與 L2 一致性控制的管理. 而 HVX co-processor 即是在 V6x processor 控制流程與 data control 的方式下, 以兩個 512b-wide L2-cache port 存取資料來運作. 即便 HVX 所需資料發生 cache miss, 負責處理這問題的是 V6x proccessor 本身.

首先是 Cache Size, MMU 的相關資訊:
這樣的設計方式與其他 DSP 不同的地方在於它的 instruction 處理部分, 預設是採用 I-Cache 的方式, 以架構來說,  缺乏 I-TCM 的方式對於一些 hard realtime 的應用稍有不利的地方, 但是其支援了 hardware instruction prefetch 的能力彌補來這部分. 而 MMU 部分提供了 4GB 的定址空間, 作為 DSP 的 Virtual Address 與 Physical Address 間著轉換. 由於對於 ARMv8 系統都提供了 > 4GB 定址空間, 因此對於 V6x DSP 使用的記憶體區間應有軟體上或硬體上的限制.

接著來探討 V6x 的剩下的 XU 與 RF, 讓我們用官方的 architecture 圖來說明
透過官方架構圖, 可以看到需要探究的東西增加了, Control Registers 基本上是控制流程(Loop Regs, Predicate Regs)與提供流程相關(Status Regs, Program Counter)資訊的用途, 這裡就捨去不談, 這裡主要談 XU 衍生的 Sequencer + S0 ~ S3 以及 RF 所代表的 General Registers R0 ~ R31

對於 S0 ~ S3 來說, S 代表的是 Slot 的意思, 看到這個字眼稍有敏銳的人就知道接著要說的是 Very Large Instruction Word (VLIW), V6x processor 本身即為 VLIW 架構, 許多的 DSP 都採用這樣的方式, 其優點是:
  • 簡化硬體設計: 對於 DSP 來說若採用 SuperScalar 架構會增加面積在 hardware instruction scheduler 的實作上, 除了增加成本外也增加 power/thermal 的風險
  • 可精確分析與預期效能: 由於 VLIW 的指令排程是透過 compiler 來完成, 而硬體上來說的處理相對簡單, 因此執行上的時間, 若以 single loop iteration 而言 compiler 即能提供資訊, runtime 所需時間, 透過 simulator/emulator 就能得到很精確的時間估測
對於 VLIW 而言, 弄清楚每個 slot 能夠處理的指令是一個重點, 以下為 V6x 的 Slot-Instruction 配置說明:
可以看出, 除了共通的 ALU32 指令外, 每個 Slot  都有著其所被賦予的任務:
  • Slot 0, 1: 主要負責記憶體的 load & store, 其中 Slot 0 更負責了 cache 維護, maintenance 與匯流排的操作.
  • Slot 2, 3: 負責主要的負責數理運算, 以及 Vector 相關指令, 程式內部的流程控制 (J/JR/CR)
到這邊可能會有人想喊 "等等!!! Vector 運算 !?"
是的, V6x 本身即支援了初步的 64b-wide Vector 運算, 下圖為官方的指令範例:
但 V6x 有一點是特別的, 就是它的 Register File
主要是它將 General Register 與 Vector Register 整合在一起:
在操作上亦提供了便利的 32/64b Register Notation
對於 V6x 就以此簡短的介紹, 有興趣者可以再透過官方文件深入
下一篇會介紹讓 Hexagon V6x 大幅提升計算能力的 HVX

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

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