2012-09-12 64 views
10

我用浮點數小精度執行算術運算時檢測到一個不尋常的計算時間。以下簡單的代碼表現出這種行爲:爲什麼一些算術運算需要比平常更多的時間?

#include <time.h> 
#include <stdlib.h> 
#include <stdio.h> 

const int MAX_ITER = 100000000; 

int main(int argc, char *argv[]){ 
    double x = 1.0, y; 
    int i; 
    clock_t t1, t2; 
    scanf("%lf", &y); 
    t1 = clock(); 
    for (i = 0; i < MAX_ITER; i++) 
     x *= y; 
    t2 = clock(); 
    printf("x = %lf\n", x); 
    printf("Time: %.5lfsegs\n", ((double) (t2 - t1))/CLOCKS_PER_SEC); 
    return 0; 
} 

這裏有兩種不同的程序的運行:

  • 其中Y = 0.5

    X = 0.000000
    時間:1.32000segs

  • 其中Y = 0.9

    X = 0.000000
    時間:19.99000segs

我使用一臺筆記本電腦用以下規格測試的代碼:

  • CPU:英特爾®酷睿™2雙核CPU T5800 @ 2.00GHz×2
  • RAM:4 GB
  • OS:Ubuntu的12.04(64位)
  • 型號:戴爾Studio 1535

爲什麼出現這種情況詳細有人能解釋一下嗎?我知道,在y = 0.9時,x值比y = 0.5更慢,因此我懷疑問題與此直接相關。

+0

在第二種情況下,您可能會在達到0之前獲得更多的非正規化。 –

+1

另請參閱[本答案](http://stackoverflow.com/a/9314926/1011995)有關非規範化的性能影響。 –

回答

10

非正常(或更低的正常)數字往往是性能問題。根據你的第二個例子,慢慢收斂到0將會產生更多的低於正常值。閱讀更多內容herehere。對於更嚴重的閱讀,請查看經常引用(並且非常密集)的What Every Computer Scientist Should Know About Floating-Point Arithmetic

從第二源:

在IEEE-754,浮點數在表示的二進制爲:

Number = signbit \* mantissa \* 2exponent

有代表相同數量的可能的多個方式, 使用十進制作爲示例,數字0.1可以表示爲 1 * 10-1或0.1 * 100甚至0.01 * 10。該標準規定 數字總是與f我首先是一個人。以小數表示, 對應於1 * 10-1示例。

現在假設可以表示的最低指數是-100。 所以可以用普通形式表示的最小數字是 1 * 10-100。然而,如果我們放鬆一個領導位爲 的限制,那麼我們實際上可以在相同的 空間中代表較小的數字。以小數爲例,我們可以表示0.1 * 10-100。這 被稱爲次正常數。具有低於正常數字 的目的是平滑最小正常數字與零之間的差距。

認識到低於正常數字的 代表的精確度低於正常數字是非常重要的。事實上,他們正在交易 縮小尺寸的精度。因此,使用 低於正常數字的計算結果與在正常數字上的 計算結果的精確度不同。因此,對於低於正常數值進行顯着計算的應用程序可能值得調查以查看是否重新縮放(即,將數字乘以一些縮放因子)將產生更少的子正常值,並且更準確地得到結果 。

我在考慮自己解釋一下,但上面的解釋非常好寫和簡潔。

+1

這正是我正在尋找的答案,謝謝! – jace

3

由於0.9^n以數學方式比0.5^n更慢地收斂到0,所以不會因爲0.9^n收斂到0而是因爲在IEEE-754浮點實現中它根本不會收斂到0。

在IEEE-754表示的最小正double是2 -1074,最小的正正常是2 -1021,所以用y = 0.5,所述循環遇到53張正規數。一旦達到最小正次正規,未來的產品將是2 -1075,但由於往返關係到末位零缺省舍入模式舍入爲0(IEEE-754 表示浮點數和默認的圓關係到末位零舍入模式是標準的消費類硬件產品幾乎無處不在,即使標準沒有得到完全執行。)從此,你有一個乘法0*y這是一個普通的浮點乘法(即一個快速即使y是次正規數)。

隨着0.5 < y < 1,一旦你已經達到了(正)低於正常範圍的低端,中x*y回合的x值再次(用於y = 0.9結果,迭代的固定點是5 * 2 - 1074)。因爲這是一個幾千迭代(0.9^7 < 0.5)後達成,你基本上乘以整個循環非零數字低於正常數量。在許多處理器上,這樣的乘法不能直接處理,必須用微碼處理,這比慢很多。

如果速度比IEEE-754語義更重要(或者由於其他原因,速度比IEEE-754語義更重要),許多編譯器都會提供選項來禁用該行爲並在硬件支持時將低於正常值的數字清除爲0。我找不到我的gcc的手冊頁明確了這一個選項,但-ffast-math這裏的伎倆。

+0

榮譽的證明,可以用精確的計算,並提供一個編譯器選項進行優化分析問題。你能解釋一下爲什麼在0.5 jace

+1

如果'x'是最小的正值低於正常值,並且'y> 0.5',那麼'x * y'的確切值大於'x/2',所以它不是平局,而是四捨五入到'x'和'0',它是'x'。如果'y'大於0.5,則發生在'x'達到最小的次正規's'之前。對於'y = 0.75',在數學上你有'(2 * s)* y = 1.5 * s',這產生了一條平行線 - 它恰好在's'和'2 * s'之間的一半,所以它被舍入到'2 * s',它的最後一位爲0.對於'y = 0.9'和'x = 5 * s',你會得到'4.5 * s',但最接近的'double'略大於0.9,所以它四捨五入。 –

+0

謝謝,我不知道這個低級別的細節。 – jace