2012-05-15 36 views
28

TL; DR:爲什麼乘法/鑄造數據在size_t速度較慢,爲什麼這會因平臺而異?從size_t鑄造性能到雙倍

我有一些性能問題,我不完全理解。上下文是一個相機圖像採集卡,其中128x128 uint16_t圖像以幾百赫茲的頻率讀取和後處理。

在後處理我生成的直方圖​​這是uint32_t並且具有thismaxval = 2^16個元素,基本上我相符所有的強度值。使用這個柱狀圖我計算之和的平方之和:

double sum=0, sumsquared=0; 
size_t thismaxval = 1 << 16; 

for(size_t i = 0; i < thismaxval; i++) { 
    sum += (double)i * frame->histo[i]; 
    sumsquared += (double)(i * i) * frame->histo[i]; 
} 

繪製與配置文件中的代碼,我得到了以下(樣本,百分比,代碼):

58228 32.1263 : sum += (double)i * frame->histo[i]; 
116760 64.4204 : sumsquared += (double)(i * i) * frame->histo[i]; 

,或者第一行佔據了32 CPU時間的百分比,第二行64%。

我做了一些基準測試,它似乎是數據類型/鑄造有問題。當我將代碼更改爲

uint_fast64_t isum=0, isumsquared=0; 

for(uint_fast32_t i = 0; i < thismaxval; i++) { 
    isum += i * frame->histo[i]; 
    isumsquared += (i * i) * frame->histo[i]; 
} 

它運行速度快了10倍。但是,這種性能影響也因平臺而異。在工作站上,Core i7 CPU 950 @ 3.07GHz的代碼速度提高了10倍。在我的MacBook 8,1上,它具有Intel Core i7 Sandy Bridge 2.7 GHz(2620M),代碼速度只有兩倍。

現在我想知道:

  1. 爲什麼原來的代碼很慢,容易加快?
  2. 爲什麼每個平臺的差異如此之大?

更新:

我編譯上面的代碼與

g++ -O3 -Wall cast_test.cc -o cast_test 

UPDATE2:

我通過一個分析器(Instruments在Mac跑優化碼,像Shark ),並發現兩件事:

1)在某些情況下,循環本身需要相當長的時間。 thismaxvalsize_t

  1. ​​需要17%,我總運行時間
  2. for(uint_fast32_t i = 0; i < thismaxval; i++)需要3.5%
  3. for(int i = 0; i < thismaxval; i++)在探查顯示不出來,我以爲這是小於0。1%

2)的數據類型和澆鑄物如下:

  1. sumsquared += (double)(i * i) * histo[i]; 15%(與size_t i
  2. sumsquared += (double)(i * i) * histo[i]; 36%(與uint_fast32_t i
  3. isumsquared += (i * i) * histo[i]; 13%(與uint_fast32_t iuint_fast64_t isumsquared
  4. isumsquared += (i * i) * histo[i]; 11%(與int i,uint_fast64_t isumsquared,uint_fast64_t isumsquared

令人驚訝的是,intuint_fast32_t更快?

UPDATE4:

我跑了不同的數據類型和不同的編譯器更多的測試,一臺機器上。結果如下。

對於TESTD 0 - 2相關的代碼是

for(loop_t i = 0; i < thismaxval; i++) 
     sumsquared += (double)(i * i) * histo[i]; 

sumsquared雙,和loop_tsize_tuint_fast32_tint用於試驗0,1和2。

對於試驗3-- 5代碼是

for(loop_t i = 0; i < thismaxval; i++) 
     isumsquared += (i * i) * histo[i]; 

與類型的和loop_t再次size_tuint_fast32_tint對於測試3,4和5

我所用的編譯器的gcc 4.2.1,GCC 4.4.7,4.6.3 GCC和gcc 4.7.0。時間以代碼總CPU時間的百分比表示,因此它們顯示相對的性能,而不是絕對的(儘管運行時間在21s時相當穩定)。 CPU時間是兩行,因爲我不太確定分析器是否正確分隔了兩行代碼。

 
gcc: 4.2.1 4.4.7 4.6.3 4.7.0 
---------------------------------- 
test 0: 21.85 25.15 22.05 21.85 
test 1: 21.9 25.05 22  22 
test 2: 26.35 25.1 21.95 19.2 
test 3: 7.15 8.35 18.55 19.95 
test 4: 11.1 8.45 7.35 7.1 
test 5: 7.1 7.8 6.9 7.05 

或:

casting performance

在此基礎上,似乎鑄造是昂貴的,不管我用什麼樣的整數類型。

此外,似乎gcc 4.6和4.7不能正確優化循環3(size_t和uint_fast64_t)。

+0

你也可以用'uint_fast32_t'來試試嗎?人們普遍猜測,由於第二種數據類型與機器指令(64位)具有相同的位長,所以速度更快。猜測你至少有一臺64位機器。我預計fast32也會變慢。你也可以測試'uint_fast32_t'和'uint_fast64_t'的大小嗎?我的猜測是32位實際上是64位。 – Yuri

+0

你的意思是'uint_fast32_t isum'?我可以嘗試,但我認爲這可能會溢出,這就是爲什麼我使用uint_fast64_t。 – Tim

+3

嗯,對於1 .:原因以某種方式決定了將浮點數賦值爲float並進行浮點操作應該比直接進行int操作要慢(儘管int-to-float不應該像float-to-int一樣邪惡),甚至更多所以沒有那個最佳的x87堆棧。你是否用SSE支持來編譯它? –

回答

4

對於原來的問題:

  1. 的代碼是緩慢的,因爲它涉及到從整數轉換爲浮動 數據類型。這就是爲什麼當你對總和變量使用整數數據類型時也容易加速,因爲它不再需要 進行浮點數轉換。
  2. 所不同的是幾個 因素的結果。例如,它取決於平臺如何有效地執行int-> float轉換。此外,這種轉換 也可能會混淆程序中的處理器內部優化流程和預測引擎,高速緩存......以及處理器的內部並行處理特性可能會對此類計算產生巨大影響。

對於其他問題:

  • 「令人驚訝的int是不是uint_fast32_t快」?什麼是你的平臺上的sizeof(size_t)和sizeof(int)的 ?我可以做的一個猜測是,這兩個都是 可能是64位,因此一個演員到32位不僅可以給你 計算錯誤,但也包括一個不同大小的鑄造 罰款。

通常儘量避免可見和隱藏的轉換,如果這些轉換不是真的必要的話。例如,試着找出你的環境(gcc)上的「size_t」後面隱藏了什麼真正的數據類型,並將其用於循環變量。 在你的例子中,uint的平方不能是浮點數據類型,所以在這裏使用double是沒有意義的。堅持整數類型以達到最佳性能。

+0

謝謝。我知道演員陣容並不理想,但我不知道它太貴了。關於你的第二點:機器是64位,但是uint_fast32_t應該是**至少** 32位,如果我理解正確的話,那麼如果64位不應該使用它呢?我不明白爲什麼這解釋說'int'比'uint_fast32_t'快。另外我不明白爲什麼'for'循環的性能與不同的整數類型差別很大。 – Tim

+1

好吧,因爲'uint_fast32_t'應該是最快的32位整數類型,所以如果64位系統的速度更快,那麼實現應該足夠聰明以使用64位類型。但是當然,其他答案很好。 –

+0

我跑了一些更多的測試,底線是鑄造只是昂貴的。我很驚訝這是昂貴的,但。 – Tim

1

在x86上,uint64_t到浮點的轉換較慢,因爲只有轉換int64_t,int32_tint16_t的指令。 int16_t和32位模式int64_t只能使用x87指令進行轉換,而不能使用SSE。

當轉換到uint64_t浮點,GCC 4.2.1第一轉換值,好像它是一個int64_t,然後添加2 如果它是負的,以補償。 (當在Windows和* BSD上使用x87時,或者如果您更改了精度控制,請注意轉換忽略精度控制,但是增加尊重它。)

uint32_t首先擴展到int64_t

當在具有某些64位功能的處理器上以32位模式轉換64位整數時,存儲到加載轉發問題可能會導致停頓。 64位整數被寫爲兩個32位值並作爲一個64位值讀回。如果轉換是長依賴鏈(不是這種情況下)的一部分,這可能非常糟糕。