《深入理解計算機系統》讀書筆記:5.5 vs 5.6

0x00 前言

沒有看過或者沒有看到這裏的小夥伴們,看到這個標題一定覺得摸不着頭腦。那這裏就先來解釋一下背景。

double poly(double a[], double x, long degree)
{
    long i;
    double result = a[0];
    double xpwr = x;
    for (i = 1; i <= degree; i++) {
        result += a[i] * xpwr;
        xpwr = x * xpwr;
    }
    return result;
}
double polyh(double a[], double x, long degree)
{
    long i;
    double result = a[degree];
    for (i = degree; i >= 0; i--) {
        result = a[i] + x * result;
    }
    return result;
}

這是 CSAPP 的兩道題,每一題是一段代碼,這兩段代碼實現了同一個功能。這兩道題有一個共同的問題,比較這兩段代碼的性能。

0x01 答案

這裏的答案是,poly 的性能比 polyh 的性能要高。poly 的 CPE 是 5,而 polyh 的 CPE 是 8。

這就顯得很尷尬了,我原以爲兩個函數的 CPE 都是 8。

0x02 我的猜想

polyh 的 CPE 是 8 我沒有疑問,因爲這個循環裏的操作是無法並行的,也就是下一次迭代會依賴上一次迭代產生的結果。所以,CPE = 5 + 3,5 是浮點數乘法的延遲下屆,3 是浮點數加法的延遲下界。

poly 的 CPE 我原本認爲也是 8,兩個乘法是可以並行的,但是這個加法的是依賴於第一個乘法的值,無法並行,所以 CPE = 5 + 3 = 8。

0x03 指令集並行和流水線

上面的是我的猜想,所以我認爲這裏的答案是它們的 CPE 是相同的,性能也是相同的。但是如前面所寫,答案並不是這樣的。於是,我把之前看的東西都翻出來想了一下,真的不是這樣的。

現代 CPU 是有一個流水線的概念的。什麼是流水線呢,想象一下汽車車間,我們造一輛汽車,是分成了很多道工序的,比如裝配發動機、裝車門、輪子等等。現代 CPU 也是類似的,我們看到的一條指令,在執行的時候,經歷了一長串的流水線,導致了指令真正的執行順序和我們看到的可能是不一樣的,但是由於現代出來的這種機制,可以確保最後的結果是和我們看到的是一樣的。

0x04 解釋

poly 函數,在執行的時候,由於有兩個浮點數乘法單元,所以 a[i] * xpwrxpwr = x * xpwr 可以並行執行。而 a[i] * xpwr 可以通過流水線的數據轉移,讓這個加法 result + a[i] * xpwr 可以在下一次迭代的時候執行,因爲每次迭代的時候,兩個乘法都不會依賴 result 這個結果。這樣,加法和乘法可以並行執行。浮點乘法的延遲下界是 5,浮點加法的延遲下界是 3,所以浮點乘法是關鍵路徑,CPE 也自然就是 5 了。

再來看看 polyh 函數。這個函數的循環裏只有一個浮點乘法運算和一個浮點加法運算。先來看看浮點乘法運算,x * result,很顯然,每一次乘法都需要依賴上一次迭代的結果,導致了加法無法和乘法並行執行。於是,CPE 就成了 5 + 3 = 8 了。

0x05 最後

這個例子,我覺得很有趣,因爲它涉及到了一個流水線的細節。同時,也說明了,並不是操作少的代碼,效率就高。

本文爲作者自己讀書總結的文章,由於作者的水平限制,難免會有錯誤,歡迎大家指正,感激不盡。

0x06 參考文獻

《深入理解計算機系統(第 3 版)》第 4、5 章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章