2011-05-17 153 views
4

我正在嘗試使用NEON在iPhone ARM上針對特定問題實現Gauss-Newton優化。下面的第一個函數是我的原始C函數。第二個是我寫的NEON asm代碼。我跑了10萬次,NEON版本比C版長7-8倍。我認爲加載(vld1.32)是大部分時間需要的。我嘗試刪除一些說明。NEON ASM代碼運行速度比C代碼慢嗎?

有沒有人有任何洞察到這個問題?謝謝!

template<class T> 
inline void GaussNewtonOperationJtr8x8(T Jtr[8], const T J[8], T residual) 
{ 
    Jtr[0] -= J[0]*residual; 
    Jtr[1] -= J[1]*residual; 
    Jtr[2] -= J[2]*residual; 
    Jtr[3] -= J[3]*residual; 
    Jtr[4] -= J[4]*residual; 
    Jtr[5] -= J[5]*residual; 
    Jtr[6] -= J[6]*residual; 
    Jtr[7] -= J[7]*residual;  
} 

inline void GaussNewtonOperationJtr8x8_NEON(NFloat Jtr[8], const NFloat J[8], NFloat residual) 
{ 
    __asm__ volatile (
         // load Jtr into registers 
         "vld1.32 {d0-d3}, [%0]\n\t" 
         // load J into registers 
         "vld1.32 {d4-d7}, [%1]\n\t" 
         // load residual in register 
         "vmov.f32 s16, %2\n\t" 
         // Jtr -= J*residual 
         "vmls.f32 q0, q2, d8[0]\n\t" 
         "vmls.f32 q1, q3, d8[0]\n\t" 
         // store result 
         "vst1.32 {d0-d3}, [%0]\n\t" 
         // output 
         : 
         // input 
         : "r"(Jtr), "r"(J), "r"(residual) 
         // registers 
         : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "d12", "d13", "d14" 
        ); 
} 

回答

5
  1. 請勿使用d8-d15。在使用前必須將它們保存在堆疊中。之後恢復。編譯器會把這些指令放在這裏,浪費寶貴的週期。
  2. 在Jtr之前加載J。 Jtr預計在比管線晚階段。
  3. 使用VLDMIA/VSTMIA而不是VLD/VST。 VLDMIA/VSTMIA速度更快,並且在流水線方面具有優勢。
  4. 使用矢量 - 矢量乘法而不是矢量 - 標量乘法。
  5. 如果您創建循環版本,請在開始處放置pld並展開循環,以便每次迭代從每個指針讀取64bytes。

除了我上面提到的那些故障 - 這是NEON新手的典型故障 - 您的方法非常好。你在vmls中找到了最合適的指令。

幹得好。

{

__asm__ volatile (
    // load residual in register 
    "vdup.32 q12, %2\n\t" 
    // load J into registers 
    "vldmia %1, {q10-q11}\n\t" 
    // load Jtr into registers 
    "vldmia %0, {q8-q9}\n\t" 
    // Jtr -= J*residual 
    "vmls.f32 q8, q10, q12\n\t" 
    "vmls.f32 q9, q11, q12\n\t" 
    // store result 
    "vstmia %0, {q8-q9}\n\t" 
    // output 
    : 
    // input 
    : "r"(Jtr), "r"(J), "r"(residual) 
    // registers 
    : "q8", "q9", "q10", "q11", "q12" 
); 
3

編譯器本身優化由C代碼生成的程序集。它只是不會將一個代碼翻譯成另一個。

你要做的是做一個更好的優化,然後編譯器(哦)。你知道編譯器爲上面的C代碼生成的彙編代碼是什麼?那麼,你應該如果你想讓你的彙編代碼更好。

編輯:

這個討論有關於這類東西的大討論: Why ARM NEON not faster than plain C++?

+0

我還沒有看到GCC編譯器生成的代碼NEON。所以我正在通過自己生成ASM NEON代碼並與C代碼進行比較來進行試驗。 – paul 2011-05-17 17:59:50

+0

已更新的答案。 – karlphillip 2011-05-17 18:06:06

+0

我更仔細地閱讀了此鏈接。所以我想我的例子使用NEON不能很好地執行。我移動指令去除依賴,但我沒有任何改進。 – paul 2011-05-17 18:49:39

3

你NEON和VFP指令之間的切換。在Cortex-A8和A9上都有這樣的懲罰。擺脫VFP vmov.f32指令,並確保此代碼不會內聯到使用VFP代碼的地方,除非有這麼長的代碼來證明管道上下文切換的合理性。

+0

謝謝。是否有另一種方法可以將單個精確數字放入NEON寄存器中?我需要將「殘差」參數存入寄存器。 – paul 2011-05-18 19:27:09

+0

使它成爲兩個浮點數組中的第一個,並將其加載到D寄存器中。一般來說,雙浮動和四浮動操作都是NEON,單浮動操作是VFP。 – ohmantics 2011-05-20 03:03:00

+0

我會試試。謝謝。 – paul 2011-06-04 14:21:13

1

你的C++版本實際上是使用浮動的嗎?我不能說,因爲你只給了模板,並沒有顯示你使用的實例。奇怪的是,NEON在Cortex-A8上的代碼速度要遠遠低於VFP,但對於u32s,我可以看出它可能以這種方式工作。

我不知道ABI是什麼,但是如何傳遞殘差(也就是編譯器在進入該%2寄存器時做了什麼)可能會有一些開銷。嘗試使用指針,並在單元素上使用vld1 - 您可以用這種方式在NEON中加載一個浮點數。

如果您使用16字節對齊的加載和存儲,您將獲得更好的陣列性能,但您可能需要玩一些遊戲才能使輸入以這種方式工作。不幸的是,你永遠不會得到真正出色的性能,因爲你不能避免vmls指令的大部分延遲(由於鏈接NEON乘法和端到端添加流水線)。由於依賴指令是一個商店,它需要在NEON管道中提前輸入,這更糟糕。理想情況下,您可以同時執行其中幾項操作,並且可以將多個實例交錯在一起 - 儘可能多地放入寄存器中。

+0

是的,C++版本使用浮動。 – paul 2011-06-04 14:22:16

相關問題