Search This Blog

7/23/16

x86 架構 Android 應用程式的優化技巧與訣竅

x86 架構 Android 應用程式的優化技巧與訣竅

Intel 公司非常重視如何為開發人員提供可在 Intel 架構上運作正常 (甚至是效能最佳) 的 Android 應用程式。雖然 Intel 致力於社群層級,包括最佳化 Dalvik Java、V8 引擎及 Bionic C;為程式碼基底做出貢獻;並為 IA 提供 32 與 64 位元核心的版本;但 Intel 也同樣創造多種新工具來協助 Android 開發人員。上述工具主要用於提升效能,以超越 x86 預設的 ARM 轉譯層提供的效能:libhoudini
但是首先應選擇正確的工具。建立 Android 應用程式有三種常見的方法。
  1. 使用 Android SDK API 編譯 Java,以便在 Dalvik VM 中執行。附註:針對 ART 的說明文章近期之內即可在 Android L 取得。
    使用最新的 SDK 以顧及最大的差異,雖然您可能比較注重針對高解析度螢幕所配置的記憶體。最值得注意的是,如果藉由 Intel® HAXM (需要將 Intel® 虛擬技術與 XD 設為開啟) 提升 Android 模擬軟體的速度,將可加快測試的速度。
  2. 聚焦於網頁的 HTML5 與 JavaScript。有關開放原始碼 Android 資訊,請參閱 Android-IA 網站
  3. 建立或移植的 NDK (以 C++ 撰寫)。如果有密集使用處理器的功能或已有 C++ 程式碼,那麼這是比較理想的方法。原生 C++ 程式碼通常 (不一定) 會因為以「原生」的語言溝通而使執行速度較快,因為程式碼在執行之前會編譯為二進位,而且不需要解譯為機器語言。
本文將聚焦於最佳化以 NDK 為基礎的應用程式。這些應用程式可能僅使用 C/C++ 程式碼,或可能包含第三方程式庫及 / 或組合碼。
附註:如果尚未擁有 Android 開發環境 (IDE),新工具套件 Intel® INDE (Intel® Integrated Native Developer Experience) 將載入選取的 Android IDE 並下載及安裝多種 Intel 工具,以便協助您製作、編譯、疑難排解及發佈 Android 應用程式。按一下這些連結以瞭解一系列有關註冊及安裝 Intel® INDE 及使用 Eclipse* IDE 進行設定的方法。以及有關設定 NDK 與 SDK*、Eclipse*,以及執行於模擬器 (包括如何加快其速度) 或 Intel® 架構裝置的影片連結。
在高階部分,NDK 開發包括以下步驟及最低變更,以便在 x86 架構上運作。
  1. 建立 Android 專案及 jni 資料夾。編輯 Application.mk 以顯示 APP_ABI = all (如果檔案大小是 ARM* 及 x86 允許的,即可放入相同的套件中) 或 x86。附註:APP_ABI 設定也會影響浮點運作操作 - 請參閱以下內容。
  2. 程式碼。任何原生 (C++) 程式碼皆可重複使用。重新撰寫所有內嵌組譯碼或 ARM 特定程式碼。使用 javah 建立 JNI/ 原生程式碼標頭檔。在 Windows 標準 C++ 慣例與 Java/JNI 之間進行解譯時,請務必使用 JNIEXPORT 與 JNICALL 巨集。
  3. 編譯 / 建置程式庫 (呼叫會產生 .so 程式庫並將它們放入適當的專案目錄)。使用「ndk-build APP_ABI = X86」以及少數幾個建置旗標變更 - 請參閱以下內容。另外,請務必重新編譯所有的第三方程式庫。
  4. 從 Java 呼叫。在 Java 中宣告原生 (C++) 函式呼叫,並使用 System.loadlibrary() 載入共用的程式庫。
  5. 除錯。執行 ndk-build 並將資訊清單設為可除錯,即可使用 ndk-gdb 除錯。請確定 adb 目錄已加入至 PATH,且只有一個目標正在執行。
除了基本的「移植」之外,還有一些最佳化可以使用,

最佳化秘訣:

  1. 使用 Intel® HAXM 硬體輔助模擬,加快以軟體為基礎的 Android 模擬器。Intel® HAXM 需要將 Intel® 虛擬化技術 (Intel® VT) 及 XD 設為開啟。
  2. 依據您的檔案大小限制,設定 APP_ABI = x86 (建立一個內含所有二進制檔案的 APK) 或 = armeabi armeabi-v7a x86。(注意,x86 與 armeabi-v7a-x86 (在某種程度上) 同樣包含硬體浮點)
  3. 在編譯過程中,使用 gcc "-malign-double"。(這用於記憶體對齊 - 另請參閱 #9)
  4. 在編譯過程中,新增適當的 CPU 執行緒旗標
    針對 Intel® Atom™ 處理器超執行緒功能,可嘗試 -mtune=atom -mssse3 -mfpmath=sse
    針對非超執行緒 (BYT、SLM、Merrifield),使用 -mtune=slm -msse4.2 -mfpmath=sse
    使用 -march= 以限制指定的 CPU (mtune 可執行於更多的機型,但是僅針對列出的類型進行最佳化)。
    -mavx 尚無法在 Atom 上使用。
  5. 使用 Little Endian (預設使用 NDK)。ARM* 支援 Big 與 Little Endian,Intel® Atom™ 僅支援 Little,因此請檢查您的 gcc 旗標。
     
  6. 使用 v 4.8 的 gcc。注意這兩個工具鏈路徑 (android-ndk\toolchains\arm-linux-androideabi-4.8 以及 x86 android-ndk\toolchains\x86-4.8
  7. 請務必使用正確的 JNIEXPORT 方法簽章,將輸入方法設定至原生碼 - (符合標頭檔的函式簽章,以確保原始碼可在 Windows 上編譯*)。
    JNIEXPORT void JNICALL Java_ClassName_MethodName
  8. 編譯之後,請檢查系統記錄,確定目標原生程式庫已在執行階段順利載入。(這將會在記錄中顯示為 "added shared lib //<path>"
  9. 強制對齊記憶體,以避免載入錯誤及網路封包問題。ARM 佔用 24 位元組,但需要 8 位元組對齊才能使用 64 位元變數,而 x86 佔用 16 位元組,因此請盡量確保資料結構的 16 位元組對齊。接著,從該結構載入至 XMM 暫存器時,請使用對齊搬移 (MOVAPS、MOVNTA)。請參閱減少非對齊記憶體存取的影響
  10. 由於 Intel® Atom™ 處理器沒有 L3 快取,因此請將資料直接寫入至主記憶體 (串流儲存指令 MOVNTPS、MOVNTQ);如此一來,也能避免快取收回時發生「dirty writeback」以節省使用的頻寬。
  11. 避免因為有限的 L2 快取而造成停滯。除了特定實例之外 (資料載入與儲存至相同的位址,相同大小的運算元,以及從一般目的暫存器完成),Intel® Atom™ 處理器上的負載將會在寫入至快取時停滯幾個週期。另外,SSE 運算元儲存 (來自 xmm 暫存器) 永遠不會轉送至後續負載。
    因此針對轉送與非轉送的情況,應嘗試在暫存器中運算資料,在 xmm 暫存器中執行加總。以 mp3 解碼器為例,有一個「窗型」迴圈在暫存器中累加 / 運算總數,然後加總各暫存器的總數。
    如此一來,從 16 位元組儲存到 pSum 陣列,以及來自 pSum 的後續 4 位元組負載之間便會發生阻隔的儲存轉送停滯。為避免發生此問題,可使用 HADDPS 指令或一系列的 add 和 shuffle,在 xmm 暫存器中運算水平加總。(但請注意,HADDPS 序列在 Intel® Atom™ 處理器上速度較快,但在 Intel Core 其他版本的處理器上則較慢)。利用 SSE min 和 max 指令執行超出 16 位元範圍的樣品修剪。
  12. 使用前請先將整個 XMM 暫存器初始化 (MOVLPS、MOVHPS、PINSRW),因為有些指令僅載入部分的暫存器,如此會因為仍殘留於另一部分的程式碼而造成問題。
  13. 閱讀有關最佳化 SIMD 指令 (Intel SSE vs. ARM* NEON*) 的文件。考慮使用程式庫NEONvsSSE_5.h 可在此取得 (取代 arm_neon.h)。此外,此文件還提到在使用常數時會有效能損耗 (如果可能的話,不要在迴圈中進行初始化,而是以邏輯 / 比較運算取代),並且避免序列實作 (使用向量化)。
  14. divide 與 sqrt 運算耗用過多週期,可利用 table-lookup 運算、reciprocal approximation (RCPPS 指令) 或 Newton-Raphson 序列予以取代。
  1. 注意浮點呼叫。使用 Float 取代 Double (因為 Double 通常使用 SW lib 常式)。Float 不僅速度較快,還能節省 Intel® Atom™ 處理器的記憶體頻寬。除此之外,APP_ABI 設定是否使用 SW (armeabi) 或 HW (X86, armeabi-v7a x86) 浮點。您不會永遠想要使用 x86 演算法計算整個 HW FPU 運算。(舉例來說,在 integer 程式碼中除以平方是快速的右移位運算,但對於 Android 最佳化而言,應用倒數相乘取代 (y=x*.5 取代 y=x/2)
  2. 減少小型函式的負荷。在各種場合使用 Inline 函式,包括參數傳遞、新堆疊框設定 / 舊堆疊框還原 / 保留呼叫程式的堆疊框、將位址放入堆疊;傳回值呼叫以及返回函式。另請參閱 Intel® 64 與 IA-32 架構最佳化參考手冊
您有其他最佳化的方式嗎?請在下面發表評論。
有關編譯器最佳化的更完整資訊,請參閱最佳化聲明
類別:
標籤:

No comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...