一直以為float加減運算很簡單,無非就是將之轉換為__float32_add和__float32_sub這兩個函數調用而已,然後用軟件模擬進行加減運算。但真的如此簡單嗎?當一些讓人不太舒服的條件出現的時候,還是如此嗎?
1.1 Vdsp對float加減運算的處理
在vdsp下,可以很簡單地用:
float add(float x, float y)
{
float r = x + y;
return r;
}
float sub(float x, float y)
{
float r = x - y;
return r;
}
來完成浮點加減運算,編譯器自動將裡面的加法操作轉換為___float32_add的函數調用,而將減法操作轉換為___float32_sub的函數調用,這兩個函數的調用實現在libdsp/fpadd.asm中:
___float32_sub:
BITTGl (R1,31); // Flip sign bit of Y, fall through to add
.align 2;
___float32_add:
從這幾行代碼可以看出減法無非就是把減數改變符號再用加法實現而已。
1.2 當y為0時
看__float32_add的代碼:
// check for addition of 0.0
R3 = R1 << 1; // Remove sign
CC = R3; // If Y=0, return X
IF !CC JUMP .return_x_nopop;
………..
.return_x_nopop:
#if CHECKING_FOR_NEGZERO_RES
R1 = R0 << 1;
CC = R1;
IF !CC R0 = R1; // convert any -0.0 to 0.0
#endif
RTS;
直接返回x的值,此時的操作需要的CYCLE數為25。
1.3 當x為0時
R2 = R0 << 1; // Remove sign
CC = R2; // If X=0, return Y
IF !CC JUMP .return_y_nopop;
……….
.return_y_nopop:
R0 = R1;
RTS;
直接返回y的值,此時的操作需要的CYCLE數為26。
1.4 當x為NAN或者INF時
// Check for all exponent bits set, indicating a NaN or inf operand
R4 = R2 >> 24; // Extract X exponent
R5 = R3 >> 24; // Extract Y exponent
R6 = MAXBIASEXP+1;
CC = R4 == R6;
// Handle identities where X is NaN or inf.
IF CC JUMP .handle_nan_inf_x;
…………….
.handle_nan_inf_x:
// If x == inf, y a number ,return x
// If y == inf, and x&y have same sign, return x; (x may be NaN)
// else return NaN
CC = R5 < R6; // If exp Y < MAXBIASEXP+1
R2 = R0 << 9; // and X is inf
CC &= AZ;
IF CC JUMP .return_x_not0; // Return inf
CC = AZ; // If X is inf
R2 = R1 << 9; // then we can deduce all Y exponent bits set
CC &= AZ; // so Y is inf if no significand bits set
R2 = R0 ^ R1; // and Y is of the same sign
R2 >>= 31;
CC &= AZ;
R1 = -1; // R1 = default NaN
IF !CC R0 = R1;
.return_x_not0:
(R7:4) = [SP++];
RTS;
很多判斷條件:
l x == inf且y是一個正常數
返回x
add(inf, 4)的結果就是inf。
add(-inf, 4)的結果就是-inf。
此時的操作需要的CYCLE數為50。
l y == inf且xy同號
返回x
add(inf, inf)的結果為inf。
add(-inf, -inf)的結果為-inf。
add(inf, -inf)的結果為nan。
add(nan, inf)的結果為nan。
add(nan, -inf)的結果為nan。
此時的操作需要的CYCLE數為50。
l 其它
返回nan
此時的操作需要的CYCLE數為50。
1.5 當x為正常數且y為nan或者inf時
// If X is a number, but Y is NaN or inf, return Y.
CC = R5 == R6;
IF CC JUMP .return_y;
………….
.return_y: // no need for -0.0 return check for this case
(R7:4) = [SP++];
.return_y_nopop:
R0 = R1;
RTS;
直接返回y的值,如
add(4, inf)的值為inf
add(4, -inf)的值為-inf
add(4, nan)的值為nan
此時的操作需要的CYCLE數為40。
1.6 當指數差大於24時
fpadd.asm裡面這樣解釋這個條件:
// Extract and compare the two exponents. Since there are
// 23 bits of mantissa, if the difference between exponents (D)
// is greater than 24, the operand with the smaller exponent
// is too insignificant to affect the other. If the difference
// is exactly, the 24th (hidden) bit will be shifted into the
// R position for rounding, and so can still affect the result.
// (R is the most significant bit of the remainder, which is
// all the bits shifted off when adjusting exponents to match)
由於float裡面的尾數部分只有23位,因此當兩個數的指數差大於24時可以直接忽略這個比較小的數,轉換為十進制的差別就是1.6777216e7。
比如add(1<<24, 1)的結果為1<<24。
此時的CYCLE為136。
1.7 當小數加上大數
看__float32_add的代碼可以發現,當加法操作的第一個操作數較小時它會交換兩個操作數的位置:
// If the exponents are different, then we arrange the
// operands so that X is the larger, and we're adding
// a less-significant number to it. Because the exponents
// are biased (the eeeeeeee bits are the true exponent,
// with +127 added), we remove the sign bits of X and Y,
// and then compare directly.
CC = R3 <= R2 (IU); // compare X and Y values (exp and mant)
IF CC JUMP .no_swap; // okay if Y exp is smallest
// Y exp is biggest. Swap.
P1 = R5; // default exp of result
R5 = R0; // swap x and y
R0 = R1;
R1 = R5;
R4 = -R4; // negate D.
.no_swap:
………………
初看這個注釋,得到的印象是如果第一個操作數大於第二個操作數,那麼應該可以節省幾個CYCLE,在大量運算時就可以很可觀地節省很多時間。
但實際測試的結果卻是:
add(10000.0, 10.0)需要的CYCLE為136。
add(10.0, 10000.0)需要的CYCLE則為132。
為什麼???
在VDSP下跟蹤進去,發現了一個很有意思的現象,當需要進行交換的時候(CC=1),這個時候表示PC的光標會指向
P1 = R5; // default exp of result
這行語句,而不是直接跳轉到.no_swap。但是光標的顏色由正常的黃色變為灰色,寄存器的值也不會改變。
於是乎想起了pipeline,在pipeline viewer裡面可以看到pipeline進行了一個很明顯的清空操作,這樣造成了從
IF CC JUMP .no_swap; // okay if Y exp is smallest
到.no_swap跳轉完成整整花了10個CYCLE!
當需要交換的時候,由於pipeline沒有中斷,從
IF CC JUMP .no_swap; // okay if Y exp is smallest
執行到.no_swap只花了6個CYCLE!
第一次這麼近距離地感受到了JUMP對效率的傷害!!也明白了uclinux內核裡面likely和unlikely對效率的貢獻!!
1.8 溢出
當兩個數相加超過float的表示范圍,將返回inf或者-inf
比如:
add(FLT_MAX, FLT_MAX)的結果就是inf