日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【genius_platform软件平台开发】第二十八讲:NEON指令集优化(附实例)

發布時間:2023/12/8 编程问答 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【genius_platform软件平台开发】第二十八讲:NEON指令集优化(附实例) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  • 當在ARM芯片上進行一些例如圖像處理等計算的時候,常常會因為計算量太大造成計算幀率較低的情況。因而,需要選擇一種更加簡單快捷的計算方式以獲得處理速度上的提升。ARM NEON就是一個不錯的選擇.

※ Neon指令優化

  • NEON是一種SIMD(Single Instruction Multiple Data)*指令,也就是說,NEON可以把若干源(source)操作數(operand)打包放到一個源寄存器中,對他們執行相同的操作,產生若干目的(dest)操作數,這種方式也叫向量化(vectorization)。

  • 可能你對這個描述還不夠清晰,簡單來說,就是:NEON指令優化的精髓就在于同時在不同通道內進行并行運算。通常可用于圖像等矩陣數據的循環優化。

  • 更簡單的說,就是,將Neon寄存器分為多個通道,每個通道存儲一個數據。一條對Neon寄存器的計算指令,實際上,是對各通道的數據分別的計算指令。即寄存器位寬,直接影響到數據的通道數。

  • 例如:在ARMv7的NEON unit中,register file總大小是1024-bit,可以劃分為16個128-bit的Q-register(Quadword register)或者32個64-bit的D-register(Dualword register),也就是說,最長的寄存器位寬是128-bit。那么,假設我們采用32-bit單精度浮點數float來做浮點運算,那么可以把最多128/32=4個浮點數打包放到Q-register中做運算,即4個4個參與計算,從而提高吞吐量,減少loop次數。

  • Neon指令的使用
    主流支持目標平臺為ARM CPU的編譯器基本都支持NEON指令。可以通過在代碼中嵌入NEON匯編來使用NEON,但是更加常見的方式是通過類似C函數的NEON Instrinsic來編寫NEON代碼。本文統一采用后者。

※ 硬件平臺

  • 本文的例子都是基于ARMV7架構平臺。ARMV7架構包含:
    16個通用寄存器(32bit),R0-R15
    16個NEON寄存器(128bit),Q0-Q15(同時也可以被視為32個64bit的寄存器,D0-D31)
    16個VFP寄存器(32bit),S0-S15
    其中:NEON和VFP的區別在于VFP是加速浮點計算的硬件不具備數據并行能力,同時VFP更盡興雙精度浮點數(double)的計算,NEON只有單精度浮點計算能力。

● 頭文件和編譯選項

  • 在使用NEON Instrinsic來進行編寫NEON代碼前,需要引入頭文件:
#include <arm_neon.h>
  • 同時,在編譯的時候,需要指定編譯參數。如果使用CMakeLists.txt,可以指定:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon")
  • 關于編譯選項,可以參考:ARM平臺NEON指令的編譯和優化

※ NEON Instrinsic詳細解釋

● 數據類型

  • 對于數據類型的命名,一般遵循這樣的規則:

    <基本類型>x<lane個數>x<向量個數>_t

    其中,向量個數如果省略表示只有一個。
    基本類型:int8,int16,int32,int64,uint8,uint16,uint32,uint64,float16,float32

  • lane個數表示并行處理的基本類型數據的個數。
    按照上述的規則,比如:
    float32x4_t

● 指令函數

  • 對于指令函數的命名,一般遵循這樣的規則:

    v<指令名>[后綴]_<數據基本類型簡寫>

  • 其中,后綴如果沒有,表示64位并行;如果后綴是q,表示128位并行;如果后綴是l,表示長指令,輸出數據的基本類型位數是輸入的2倍;如果后綴是n,表示窄指令,輸出數據的基本類型位數是輸入的一半。
    數據基本類型簡寫:s8,s16,s32,s64,u8,u16,u32,u64,f16,f32。

    按照上述的規則,比如:

vadd_u16:兩個uint16x4相加為一個uint16x4 vaddq_u16:兩個uint16x8相加為一個uint16x8 vaddl_u16:兩個uint8x8相加為一個uint16x8

● 指令名

  • Neon的指令名主要分為:算術和位運算指令數據移動指令訪存指令
    算術和位運算指令:包括add(加法),sub(減法),mul(乘法)這些基本指令。

  • 實際編程中經常要在不同NEON數據類型間轉移數據,有時還要按lane來get/set向量值,NEON intrinsics也提供了這類操作。

    dup[后綴]n<數據基本類型簡寫>:用同一個標量值初始化一個向量全部的lane;

    set[后綴]lane<數據基本類型簡寫>:對指定的一個lane進行設置

    get[后綴]lane<數據基本類型簡寫>:獲取指定的一個lane的值

    mov[后綴]_<數據基本類型簡寫>:數據間移動

  • NEON訪存指令可以將內存讀到NEON數據類型中去,或者將NEON數據類型寫進內存。可以支持一次讀寫多向量數據類型。

    ld<向量數>[后綴]<數據基本類型簡寫>:讀內存
    st<向量數>[后綴]<數據基本類型簡寫>:寫內存

實例

  • 實例內容:對于1280 * 720 * 3的圖片數據,需要對每個像素點進行同樣的加法和乘法運算,比較非Neon和Neon兩種方式的耗時。源碼:
# include <iostream> # include <chrono> # include <random> #include <arm_neon.h>int main(int argc, char const *argv[]) {float *data_tmp = new float[1080 * 720 * 3];std::default_random_engine e;std::uniform_real_distribution<float> u(0, 255);for(int i = 0; i < 1080 * 720 * 3; ++i) {*(data_tmp + i) = u(e);}float *data = data_tmp;float *data_res1 = new float[1080 * 720 * 3];std::chrono::microseconds start_time = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());for(int i = 0; i < 1080 * 720 * 3; ++i) {*data_res1 = ((*data) + 3.4 ) / 3.1;++data_res1;++data;}std::chrono::microseconds end_time = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());std::cout << "cost total time : " << (end_time - start_time).count() << " microseconds -- common method" << std::endl;data = data_tmp;float *data_res2 = new float[1080 * 720 * 3];start_time = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());float32x4_t A = vdupq_n_f32(3.4);float32x4_t B = vdupq_n_f32(3.1);for(int i = 0; i < 1080 * 720 * 3 / 4; ++i) {float32x4_t C = (float32x4_t){*data, *(data + 1), *(data + 2), *(data + 3)};float32x4_t D = vmulq_f32(vaddq_f32(C, A), B);vst1q_f32(data_res2, D);data = data + 4;data_res2 = data_res2 + 4;}end_time = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch());std::cout << "cost total time : " << (end_time - start_time).count() << " microseconds -- neon method" << std::endl;return 0; }
  • 編寫CMakeLists.txt,用于項目編譯:
cmake_minimum_required(VERSION 3.0) project(main)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon") add_definitions("-Wall -g")add_executable(${PROJECT_NAME} main.cpp )install(TARGETS ${PROJECT_NAME}RUNTIME DESTINATION ${PROJECT_SOURCE_DIR})
  • 在同級目錄下編寫main.sh,進行項目編譯:
#/bin/bashexport ANDROID_NDK=/opt/env/android-ndk-r14brm -r build mkdir build && cd build cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \-DANDROID_ABI="armeabi-v7a" \-DANDROID_PLATFORM=android-22 \..make && make installcd ..
  • 將生成的可執行文件main,push到設備端進行運行,最終的運行結果:
cost total time : 112538 microseconds -- common method cost total time : 44217 microseconds -- neon method
  • 可以看出,使用Neon指令集優化,省下了近60.71%的運行時間。

總結

以上是生活随笔為你收集整理的【genius_platform软件平台开发】第二十八讲:NEON指令集优化(附实例)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。