2010年2月3日 星期三

C語言寫法及效能差異

這是Bee的舊文章,貼出來讓大家參考。

這是Bee見過最好的文章,來解釋指標效能及寫法的影響。
取自"Pointer on C"由Kenneth A. Reek所著。
簡體書為"C和指針",人民郵電出版社出版。
內容的一段我簡單說明。

範例是使用68000處理器
程式碼共用此段宣告
#define SIZE 50
int x[SIZE];
int y[SIZE];
int i;
int *p1, *p2;


第一個程式
void try1()
{
        for( i =0; i< SIZE; i++ )
                x[i] = y[i];
}
很直覺的寫法。
組合語言碼為
_try1:  clrl    _i
        jra     L20
L20001: movl    _i,d0
        asll    #2,d0
        movl    #_y,a0
        movl    _i,d1
        asll    #2,d1
        movl    #_x,a1
        movl    a0@(0,d0:L),a1@(0,d1:L)
        addql   #1,_i
L20:    moveq   #50,d0
        cmpl    _i,d0
        jgt     L20001


第二個程式
void try2()
{
        for( p1 = x, p2 = y; p1 - x < SIZE; )
                *p1++ = *p2++;
}              
改使用指標。
組合語言碼為
_try2:  movl    #_x,_p1
        movl    #_y,_p2
        jra     L25
L20003: movl    _p2,a0
        movl    _p1,a1
        movl    a0@,a1@
        addql   #4,_p2
        addql   #4,_p1
L25:    moveq   #4,d0
        movl    d0,sp@-
        movl    _p1,d0
        subl    #_x,d0
        movl    d0,sp@-
        jbsr    ldiv
        addql   #8,sp
        moveq   #50,d1
        cmpl    d0,d1
        jgt     L20003
組合語言碼變長了,而且因為指標相減調整(除4)而去呼叫ldiv函式。增加許多執行時間。


第三個程式
void try3()
{
        for( i = 0, p1 = x, p2 = y; i < SIZE; i++ )
                *p1++ = *p2++;
}                      
計數改用變數,不讓指標調整。
組合語言碼為
_try3:  clrl    _i
        movl    #_x,_p1
        movl    #_y,_p2
        jra     L30
L20005: movl    _p2,a0
        movl    _p1,a1
        movl    a0@,a1@
        addql   #4,_p2
        addql   #4,_p1
        addql   #1,_i
L30:    moveq   #50,d0
        cmpl    _i,d0
        jgt     L20005
似乎不錯。但還有更好。 


第四個程式
void try4()
{
        register int *p1, *p2;
        register int i;
       
        for( i = 0, p1 = x, p 2= y; i < SIZE; i++ )
                *p1++ = *p2++;
}              
使用暫存器變數宣告,將區域變數使用暫存器。
組合語言碼為
_try4:  moveq   #0,d7
        movl    #_x,a5
        movl    #_y,a4
        jra     L35
L20007: movl    a4@+,a5@+
        addql   #1,d7
L35:    moveq   #50,d0
        cmpl    d7,d0
        jgt     L20007
如果夠了解編譯器及語言特性,就會找到更好的調整。       


第五個程式
void try5()
{
        register int *p1, *p2;
       
        for( p1 = x, p2 = y; p1 < &x[SIZE]; )
                *p1++ = *p2++;
}
移除計數。                             
組合語言碼為
_try5:  movl    #_x,a5
        movl    #_y,a4
        jra     L40
L20009: movl    a4@+,a5@+
L40:    cmpl    #_x+200,a5
        jcs     L20009 
最精簡。但C原始碼可讀性極差。


這個例子可以看出效能及可讀性之間的移轉。
也可以看出如何依組合語言碼而調整C程式碼。
到底是要好讀,還是效能,端看使用場合而定。
   

5 則留言:

  1. 這樣的分析只對這樣的系統有效,不同時代的c compiler以及不同廠家提供的不同compiler,分析結果就又不同,尤其是更精密效能探討更沒有絕對結果,我指的是當程式以特殊手法設計,而必須干涉系統的編譯來設計程式時更甚,此時關鍵程式仍然只能靠直接使用組合語言才容易實現,現行與以前的所有c compiler都仍沒有我講的這種能力,也許將來的才可以。
    根據我使用c設計的gforth系統,已有十幾年七大版的成品,就呈現這種現象,如果要逐年逐版逐個硬體分析後,更改其中核心指令,採用最佳指標使用方法才能完成設計,才能改善品質,或讓性能符合真正forth的要求,gforth的原設計人自己也不願意做這麼囉嗦的事情,看來c仍然有許多比組合語言高階後就辦不到的事情,不巧這樣的要求卻又都是絕對關鍵,對forth而言就非要不可。
    但無論如何,這種分析仍然對單一系統有效,可以就地解決暫時性的問題,是單一系統分析時很好的範例。
    丁陳漢蓀老師需要一個c的好手來寫eforth的核心碼,如果您熟,何不下功夫接手?
    曾慶潭
    [版主回覆02/04/2010 10:42:25]您說得沒錯。
    不過我這篇是要給一般C的使用者看。因為現在會C/C++的人很多,但沒幾個人會調。
    我看到有人寫了近十年的C/C++,指標仍是他不想碰的,並會以不符合物件的觀念,違反軟體工程原則加以否決。後來有另一人去調整程式碼,使用指標寫出來的程式直接快三倍。
    至於Forth語言,我暫時沒時間,近幾年會很忙,學業及家庭都有事。這個私下寄e-mail再談。
    感謝您的回應,我想這對許多人都很有幫助。

    回覆刪除
  2. ,指標仍是他不想碰的,並會以不符合物件的觀念,違反軟體工程原則加以否決。

    我很想聽他的說法哩
    這種講法我還第一次聽到哩

    在C++ polymorphism 的實現下,電腦底層還是用指標阿...

    [版主回覆04/29/2010 10:07:36]指標是許多初階工程師的痛。我看到的是面子和技術相比,有許多人還是面子重要,所以搬出一堆理由。

    我也看過使用VB寫了三十年的老工程師,不管為何,就是不想前進到C語言的領域,其中指標搞不懂應是原因。

    但是人各有所長,不碰指標只是代表他不願前進到高階程式設計的領域。

    回覆刪除
  3. hi,我實作了這幾個副程式,發現裡面的code有部份應該是筆誤
    tyr1()  和 tyr2()  應該是寫反了,應該是try1()  和try2()  我猜啦
    在try4()裡 p = y;   應該是p2=y;   才對
    不過我拿microchip C30模擬的結果,try4()  和 try5() 並沒有特別的優化,可能是register並沒有發揮出應該有的特性吧!我想請問的是,如何更能掌握指標和mcu的關系? 謝謝
    [版主回覆11/21/2010 22:18:20]你真細心,我筆誤沒發現。已修正了。
    在暫存器少的CPU上,try4和try5應是差不了太多。

    回覆刪除
  4. 怎麼我做出來的結果try3()的效果最差??
    C30 V1.2 dspic30f5015
                    clk       rom    ram
    try1()  1525   6021  248 
    try2() 1483   6033  248
    try3() 1582  6045  248
    try4() 1026  6012  248
    try5() 926  6003  248
    [版主回覆11/01/2012 11:15:56]和處理器的結構有關。和編譯器最佳化也有關。
    我的例子是以68K處理器為目標。它在指標上的管理本來就比其他處理器好很多。
    所以指標操作的程式上會有加速。

    換處理器測的話,還是看輸出的組合語言才會準。
    在不善長指標操作的處理器上面,try3會是最吃虧。
    若是如此,也表示此處理器亦不善長於影像相關的工作。

    回覆刪除
  5. 嗯嗯,明白了,有前輩可以問的感覺真好 ^^
     
    [版主回覆11/01/2012 13:29:08]南部也有不錯的社團可以問,只是對我來說太遠了。
    http://mosut.org/

    回覆刪除