2012-04-03 67 views
39

我的應用程序中有一個乘加內核,我想提高它的性能。C代碼循環性能

我使用了Intel Core i7-960(3.2GHz的時鐘),並已手動實現使用SSE內部函數如下內核:

for(int i=0; i<iterations; i+=4) { 
    y1 = _mm_set_ss(output[i]); 
    y2 = _mm_set_ss(output[i+1]); 
    y3 = _mm_set_ss(output[i+2]); 
    y4 = _mm_set_ss(output[i+3]); 

    for(k=0; k<ksize; k++){ 
     for(l=0; l<ksize; l++){ 
      w = _mm_set_ss(weight[i+k+l]); 

      x1 = _mm_set_ss(input[i+k+l]); 
      y1 = _mm_add_ss(y1,_mm_mul_ss(w,x1)); 
      … 
      x4 = _mm_set_ss(input[i+k+l+3]); 
      y4 = _mm_add_ss(y4,_mm_mul_ss(w,x4)); 
     } 
    } 
    _mm_store_ss(&output[i],y1); 
    _mm_store_ss(&output[i+1],y2); 
    _mm_store_ss(&output[i+2],y3); 
    _mm_store_ss(&output[i+3],y4); 
 } 

我知道我可以使用打包FP載體,以提高性能和我已經做到了成功,但我想知道爲什麼單個標量代碼無法達到處理器的峯值性能。這個內核在我的機器上的性能是每個週期〜1.6個FP操作,而每個週期最大爲2個FP操作(因爲FP add + FP mul可以並行執行)。

如果我正確研究生成的彙編代碼,理想的時間表如下所示,其中mov指令需要3個週期,從相關指令開始,從加載域到FP域的切換延遲需要2個週期,FP乘法需要4個週期,FP加法需要3個週期。 (請注意,乘法 - >添加的相關性不會導致任何切換延遲,因爲這些操作屬於同一個域)。

schedule

根據所測量的性能(〜最大理論性能的80%)存在的每8個週期〜3個指令的開銷。

我想要麼:

  • 擺脫這種開銷,或
  • 解釋它來自哪裏

當然有與緩存的問題錯過&數據錯位,其可以增加移動指令的延遲,但是還有其他因素可以在這裏起作用嗎?像寄存器讀取攤位什麼的?

我希望我的問題很清楚,在此先感謝您的回覆!


更新:內環的組裝如下所示:

... 
Block 21: 
    movssl (%rsi,%rdi,4), %xmm4 
    movssl (%rcx,%rdi,4), %xmm0 
    movssl 0x4(%rcx,%rdi,4), %xmm1 
    movssl 0x8(%rcx,%rdi,4), %xmm2 
    movssl 0xc(%rcx,%rdi,4), %xmm3 
    inc %rdi 
    mulss %xmm4, %xmm0 
    cmp $0x32, %rdi 
    mulss %xmm4, %xmm1 
    mulss %xmm4, %xmm2 
    mulss %xmm3, %xmm4 
    addss %xmm0, %xmm5 
    addss %xmm1, %xmm6 
    addss %xmm2, %xmm7 
    addss %xmm4, %xmm8 
    jl 0x401b52 <Block 21> 
... 
+0

它確實依賴於很多編譯器(甚至它的版本)以及您傳遞給它的優化標誌。如果數值性能對您來說如此重要,您可能還需要花時間和精力學習數值庫和/或OpenCL或CUDA(以利用GPGPU)。 Ther也是緩存考慮因素。在現在的處理器上預測循環的實際時間很困難。 – 2012-04-03 11:13:37

+4

我不明白爲什麼你會認爲循環控制總是可以並行執行,而它實際上是在無序執行方案中創建完美的依賴關係鏈。 INC指令修改一個寄存器。 CMP指令必須等待INC完成以檢查該寄存器中的值並相應地修改標誌。然後,條件跳轉指令必須等待CMP寫入標誌以決定是否實際跳轉。恐怕沒有平行化。更不要說跳躍會導致流水線延遲 - 分支預測器會處理這一點。 – 2012-04-03 11:13:39

+0

更不用說,INC指令必須等待修改標誌的先前指令才能保持CF標誌的狀態。只需將INC替換爲相應的ADD即可解決該問題。 – 2012-04-03 11:18:43

回答

29

我注意到,該意見:

  • 循環需要5個時鐘週期執行。
  • 「應該」需要4個週期。 (因爲有4個加法和4個mulitplies)

但是,您的程序集顯示5 SSE movssl說明。根據Agner Fog's tables所有浮點SSE移動指令至少爲1次/週期 Nehalem的互惠吞吐量。

既然你有5個,你不能做比5個週期/迭代更好。


因此,爲了達到最佳性能,您需要減少您擁有的負載數量。你怎麼能做到這一點,我不能立即看到這個特殊情況 - 但它可能是可能的。

一種常用的方法是使用tiling。在哪裏添加嵌套級別以改善局部性。雖然它主要用於改進緩存訪問,但它也可用於寄存器中以減少所需的加載/存儲數量。

最終,您的目標是減少負載的數量,使其小於添加/ muls的數量。所以這可能是要走的路。

+0

我還會提到整數SSE寄存器到寄存器的mov有3個inst /循環的吞吐量,但這是不相關的。所有加載/存儲仍然是1次/週期。 – Mysticial 2012-04-03 17:04:03

+2

你怎麼能在多任務系統上說這個?真的嗎? 80%的理論吞吐量與Linux的桌面調度程序和上下文切換有關......我真的很想看看他是否可以通過1條指令減少循環,並獲得更好的速度(使用不完整的內核) – 2012-04-03 18:00:18

+1

@OrgnlDave操作系統/內核開銷通常比你想象的要小。根據我的經驗,這可以忽略不計(<1%)。請參閱[此問題](http://stackoverflow.com/q/8389648/922184)以獲得在Windows和Linux上達到峯值觸發器97 +%的代碼示例。 – Mysticial 2012-04-03 18:17:29

0

使這個從我的評論的答案。

在非服務器Linux發行版中,我相信中斷計時器默認情況下通常設置爲250Hz,雖然由於發行版的不同,它幾乎總是超過150.這種速度對於提供30 fps交互式GUI是必需的。該中斷計時器用於搶佔代碼。這意味着每秒150次以上的代碼被中斷,並且調度程序代碼運行並決定要花更多時間。這聽起來像你做得很好,只是獲得最大速度的80%,沒有問題。如果你需要更好的安裝說,Ubuntu服務器(默認爲100Hz),並調整內核(搶佔關閉)

編輯:在2+核心系統,這個影響要小得多,因爲你的進程幾乎肯定會被打到一個核心和更多或更少的左派去做自己的事情。

+4

對不起,但這是無稽之談。我能夠測量linux系統上簡單指令序列的處理器週期,搶佔式和1kHz調度器。即使運行X,系統的開銷通常也低於1%。另外,如果OP問題中的週期數從4到5恰好是由於開銷 - 更自然的解釋是該循環實際需要5個週期,這將是非常不可能的巧合。 – hirschhornsalz 2012-04-03 23:23:28

+0

@drhirsch我敢打賭你有兩個核心。這是在對另一個問題的評論中提到的。我會編輯這個以反映這一點。 – 2012-04-03 23:27:58

+0

不改變事物。在運行n個測試程序實例時,我仍然可以進行相同的測量,其中n是核心數。 – hirschhornsalz 2012-04-03 23:34:16

1

非常感謝您的回答,這解釋了很多。 繼續對我的問題,當我使用打包指令,而不是標量指令使用內聯函數的代碼看起來非常相似:

for(int i=0; i<size; i+=16) { 
    y1 = _mm_load_ps(output[i]); 
    … 
    y4 = _mm_load_ps(output[i+12]); 

    for(k=0; k<ksize; k++){ 
     for(l=0; l<ksize; l++){ 
      w = _mm_set_ps1(weight[i+k+l]); 

      x1 = _mm_load_ps(input[i+k+l]); 
      y1 = _mm_add_ps(y1,_mm_mul_ps(w,x1)); 
      … 
      x4 = _mm_load_ps(input[i+k+l+12]); 
      y4 = _mm_add_ps(y4,_mm_mul_ps(w,x4)); 
     } 
    } 
    _mm_store_ps(&output[i],y1); 
    … 
    _mm_store_ps(&output[i+12],y4); 
    } 

這個內核的實測性能是每循環約5.6 FP操作,但我希望它恰好是標量版本的4倍,即每個週期4.1,6 = 6,4個FP操作。

以權重因子的戶口遷入(感謝指出了這一點),日程安排是這樣的:

schedule

它看起來像計劃不會改變,但有一個額外的指令在執行movss操作後將標量權值移至XMM寄存器,然後使用shufps將該標量值複製到整個矢量中。考慮到負載到浮點域的切換延遲時間,似乎權重矢量已準備好用於mulps,所以這不應該導致任何額外的延遲。這是在這個內核使用

movaps(排列,包裝移動),addps & mulps指令(彙編代碼覈對)具有相同的延遲&吞吐量,其標版本,因此這不應該要麼招致任何額外的延遲。

有沒有人知道這個額外的週期每8個週期花費在哪裏,假設這個內核可以獲得的最大性能是每個週期6.4 FP操作並且每個週期運行在5.6 FP操作?

再次感謝您的幫助!

+1

我認爲這是適合作爲一個單獨的問題。從現在開始,你有一個洗牌新問題。 (我現在沒有看到答案)您可以將它鏈接回這個並聲明它是一個延續。 – Mysticial 2012-04-04 07:58:55

+0

容易找出。確保權重向量不包含任何非規格化值。嘗試沒有shuffle指令的循環。它不會產生任何有用的結果,但也許你會發現哪個指令會花費你額外的週期(當然,我懷疑是洗牌)。 – hirschhornsalz 2012-04-04 08:23:32

+0

@drhirsch新問題在這裏:http://stackoverflow.com/questions/10007243/c-code-loop-performance-continued因此,在那裏重新發布您的評論。 – Mysticial 2012-04-04 08:33:35