0 ); } } 咋的一看,這啥玩意啊, switch/while 這組合能編譯通過" />

衡阳派盒市场营销有限公司

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

加速循環(huán)語句的C編碼技巧

科技綠洲 ? 來源:技術(shù)讓夢(mèng)想更偉大 ? 作者:技術(shù)讓夢(mèng)想更偉大 ? 2023-06-22 11:44 ? 次閱讀

相信大家寫業(yè)務(wù)邏輯的時(shí)候,都是面向if、elseforwhileswitch編程。但是你見過switch嵌套do..while嗎?

先上代碼

void send( int * to, int * from, int count)
{
    int n = (count + 7 ) / 8 ;
    switch (count % 8 ) {
    case 0 :    do { * to ++ = * from ++ ;
    case 7 :          * to ++ = * from ++ ;
    case 6 :          * to ++ = * from ++ ;
    case 5 :          * to ++ = * from ++ ;
    case 4 :          * to ++ = * from ++ ;
    case 3 :          * to ++ = * from ++ ;
    case 2 :          * to ++ = * from ++ ;
    case 1 :          * to ++ = * from ++ ;
           } while ( -- n >    0 );
    }  
}

咋的一看,這啥玩意啊,switch/while 這組合能編譯通過嗎?您可別懷疑,還真能。這個(gè)就是達(dá)夫設(shè)備(Duff's Device)

什么是達(dá)夫設(shè)備

百度百科說法如下:

在計(jì)算機(jī)科學(xué)領(lǐng)域,達(dá)夫設(shè)備(英文:Duff's device)是串行復(fù)制(serial copy)的一種優(yōu)化實(shí)現(xiàn),通過匯編語言編程時(shí)一常用方法,實(shí)現(xiàn)展開循環(huán),進(jìn)而提高執(zhí)行效率。這一方法據(jù)信為當(dāng)時(shí)供職于盧卡斯影業(yè)的湯姆·達(dá)夫于1983年11月發(fā)明,并可能是迄今為止利用C語言switch語句特性所作的最巧妙的實(shí)現(xiàn)。

達(dá)夫設(shè)備是一個(gè)加速循環(huán)語句的C編碼技巧。 其基本思想是 --減少循環(huán)測(cè)試的執(zhí)行次數(shù)。

簡(jiǎn)單講下背景

時(shí)間要回到1983年,那是一個(gè)雨過天晴的夏天,在盧卡斯影業(yè)上班的程序員 Tom Duff ,他是想為了加速一個(gè)實(shí)時(shí)動(dòng)畫程序,實(shí)現(xiàn)從一個(gè)數(shù)組復(fù)制數(shù)據(jù)到一個(gè)寄存器這樣一個(gè)功能,真臉如下。

一般情況下,若要將數(shù)組元素復(fù)制進(jìn)存儲(chǔ)器映射輸出寄存器,較為直接的做法如下所示

do {
  /* count > 0 assumed (假定count的初始值大于0) */    
  *to = *from++;            
  /* Note that the 'to' pointer is NOT incremented 
  (注意此處的指針變量to指向并未改變) */
} while(--count > 0);

但是達(dá)夫洞察到,若在這一過程中將一條switch和一個(gè)循環(huán)相結(jié)合,則可展開循環(huán),應(yīng)用的是C語言里面case 標(biāo)簽的Fall through特性,實(shí)際就是沒有break繼續(xù)執(zhí)行。實(shí)現(xiàn)如上代碼所示。

其實(shí)第一版是這樣寫的:

void send(to, from, count)
register short *to, *from;
register int count;
{
    /* count > 0 assumed */
    do {        
        *to++ = *from++;
    } while (--count > 0);
}

這段代碼等價(jià)于:

void send(register short* to, register short* from, register int count)
{
    /* count > 0 assumed */
    do {                          
        *to++ = *from++;
    } while (--count > 0);
}

但是在這種使用場(chǎng)景下,不易于移植和應(yīng)用,然后他就更新了第二版,代碼如下:

void send2(short* to, short* from, int count)
{
    int n = count / 8;
    do {
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
    } while (--n > 0);
}

這種寫法減少了比較次數(shù),在匯編層面單純講到下面代碼的時(shí)候

do... while(--count > 0)

總共有6條指令。大家可以用godbolt.org/ 測(cè)一下。如下(匯編測(cè)試參考網(wǎng)上資源,大家可以自行測(cè)試)

subl  $1,-4(%rbp)
cmp1  $0,-4(%rgp)
setg  %al,
testb %al,%al
je    ,L8
jmp   ,L7

如果原始count是256,就這一部分指令減少(256-256/8)*6=(256-32)*6=1344。對(duì)應(yīng)6條指令:

movl   -36(%rbp),%eax
leal   7(%rax),%edx
testl  %eax,%eax
cmovs  %edx,%eax
sarl   $3,%eax
movl   %eax,-4(%rbp)

但是這個(gè)版本在通用性能還不夠,count一定要是8的倍數(shù),所以經(jīng)過了這兩個(gè)版本的發(fā)展,最終才有了上述那個(gè)最終版本的誕生。雖然性能上沒有什么優(yōu)化,但是最終版的達(dá)夫設(shè)備,count不局限于一定是8的倍數(shù)了!

實(shí)現(xiàn)機(jī)制、代碼解析

實(shí)現(xiàn)機(jī)制

在達(dá)夫解決這個(gè)問題的時(shí)候,當(dāng)時(shí)的C語言對(duì)switch語句的規(guī)范是比較松的,在switch控制語句內(nèi),條件標(biāo)號(hào)(case)可以出現(xiàn)在任意子語句之前,充作其前綴。

此外若未加入break語句,則在switch語句在根據(jù)條件判定,跳轉(zhuǎn)到對(duì)應(yīng)的標(biāo)號(hào),并在開始執(zhí)行后,控制流會(huì)一直執(zhí)行到switch嵌套語句的末尾。

利用這種特性,這段代碼可以從連續(xù)地址中將count個(gè)數(shù)據(jù)復(fù)制到存儲(chǔ)器中,映射輸出寄存器中。

另一方面,C語言本身也對(duì)跳轉(zhuǎn)到循環(huán)內(nèi)部提供了支持,因而此處的switch/case語句便可跳轉(zhuǎn)到循環(huán)內(nèi)部。

代碼解析

首先說下這段代碼,編譯沒問題,我們寫個(gè)代碼如下:

#include < iostream > 
using namespace std;
int  main()
{
    int  n  = 0 ;
    switch  (n)  { 
    case 0 :  do   {cout  < <   " 0 "   < <  endl;
    case 1 :         cout  < <   " 1 "   < <  endl;
    case 2 :         cout  < <   " 2 "   < <  endl;
    case 3 :         cout  < <   " 3 "   < <  endl; 
      }   while ( -- n  > 0 ); 
   } 
}

根據(jù)n的不同輸入,實(shí)驗(yàn)結(jié)果如下

n的值 程序輸出
0 0 1 2 3
1 1 2 3
2 2 3 0 1 2 3
3 3 0 1 2 3 0 1 2 3

這段代碼的主體還是do-while循環(huán),但這個(gè)循環(huán)的入口點(diǎn)并不一定是在do那里,而是由這個(gè)switch(n),把循環(huán)的入口定在了幾個(gè)case標(biāo)號(hào)那里。

即程序的執(zhí)行流程是:

程序執(zhí)行到了switch的時(shí)候,就會(huì)根據(jù)n的值,直接跳轉(zhuǎn)到 case n那里,再當(dāng)它執(zhí)行到while那里時(shí),就會(huì)判斷循環(huán)條件。若為真,則while循環(huán)開始,程序跳轉(zhuǎn)到do那里開始執(zhí)行循環(huán);為假,則退出循環(huán),即程序中止。(這個(gè)swicth語句就再也沒有用了)

我們?cè)倏匆韵麓a,這里 count 個(gè)字節(jié)從 from 指向的數(shù)組復(fù)制到 to 指向的內(nèi)存地址,是個(gè)內(nèi)存映射的輸出寄存器。它把 swtich 語句和復(fù)制 8 個(gè)字節(jié)的循環(huán)交織在一起, 從而解決了剩余字節(jié)的處理問題 (當(dāng) count % 8 != 0)。

void send( int * to, int * from, int count)
{
    int n = (count + 7 ) / 8 ;
    switch (count % 8 ) {
    case 0 :    do { * to ++ = * from ++ ;
    case 7 :          * to ++ = * from ++ ;
    case 6 :          * to ++ = * from ++ ;
    case 5 :          * to ++ = * from ++ ;
    case 4 :          * to ++ = * from ++ ;
    case 3 :          * to ++ = * from ++ ;
    case 2 :          * to ++ = * from ++ ;
    case 1 :          * to ++ = * from ++ ;
           } while ( -- n >    0 );
    }  
}

switch內(nèi)的表達(dá)式計(jì)算被8除的余數(shù)。執(zhí)行開始于while循環(huán)內(nèi)的哪個(gè)位置由這個(gè)余數(shù)決定,直到最終循環(huán)退出(沒有break)。Duff's Device這樣就簡(jiǎn)單漂亮地解決了邊界條件的問題。

性能表現(xiàn)

我們一般使用用for循環(huán)或者while循環(huán)的時(shí)候,如果執(zhí)行循環(huán)內(nèi)容本身用不了多少時(shí)間,本質(zhì)上時(shí)間主要是消耗在了每次循環(huán)的比較語句上邊。

而事實(shí)上,比較語句是有很大優(yōu)化空間的,我們假設(shè)你要循環(huán)10000次,結(jié)果你從第一次開始就不斷的比較是否達(dá)到上界值,這是不是很徒勞呢?

我們寫一個(gè)達(dá)夫設(shè)備的函數(shù)用來測(cè)試執(zhí)行時(shí)間(參考網(wǎng)上資源,這個(gè)測(cè)試不難,不同測(cè)試會(huì)有不同效果,大家可以自行測(cè)試一下):

int duff_device(int a)
{
    resigter x = 0;
    int n = (a) / 10;
    switch(a%10){
        case 0do{ x++;
        case 9:x++; 
        case 8:x++;   
        case 7:x++;  
        case 6:x++;   
        case 5:x++;   
        case 4:x++;   
        case 3:x++;   
        case 2:x++;   
        case 1:x++;   
        }while(--n >0)
    }
    return x;
}

測(cè)試主函數(shù)如下

#include < Windows.h >
#define count 999999999
long int overtime = count;
int main()
{
    printf("over %d",duff_device(overtime));
    return 0;
}

執(zhí)行時(shí)間如下

圖片

現(xiàn)在我們看一下傳統(tǒng)的循環(huán)的執(zhí)行時(shí)間,其測(cè)試代碼如下:

int classical(int a)
{
    register x=0;
    do{
        x ++;
    }while(--a >0);
    return x;
}

測(cè)試主函數(shù)如下

#include < Windows.h >
#define count 999999999
long int overtime = count;
int main()
{
    printf("over %d",classical(overtime));
    return 0;
}

執(zhí)行時(shí)間如下

圖片

結(jié)果顯示達(dá)夫設(shè)備確實(shí)縮短了不少時(shí)間,這里x的定義是要用register關(guān)鍵字,這樣cpu就會(huì)把x盡可能存入cpu內(nèi)部的寄存器,新的cpu應(yīng)該會(huì)有很通用寄存器使用。

值得一提的是,針對(duì)串行復(fù)制的需求,標(biāo)準(zhǔn)C語言庫提供了memcpy函數(shù),而其效率不會(huì)比斯特勞斯魯普版的達(dá)夫設(shè)備低,并可能包含了針對(duì)特定架構(gòu)的優(yōu)化,從而進(jìn)一步大幅提升執(zhí)行效率。

從不同角度看達(dá)夫設(shè)備

從語言的角度來看

我個(gè)人覺得這種寫法不是很值得我們借鑒。畢竟這不是符合我們“正常”邏輯的代碼,至少C/C++標(biāo)準(zhǔn)不會(huì)保證這樣的代碼一定不會(huì)出錯(cuò)。

另外, 這種代碼冷知識(shí),估計(jì)有很多人根本都沒見過,如果自己寫的代碼別人看不懂,估計(jì)會(huì)被罵的。

算法的角度來看

我覺得達(dá)夫設(shè)備是個(gè)很高效、很值得我們?nèi)W(xué)習(xí)的東西。把一次消耗相對(duì)比較高的操作“分?jǐn)偂暗搅硕啻蜗南鄬?duì)比較低的操作上面,就像vector中實(shí)現(xiàn)可變長度的數(shù)組的思想那樣,節(jié)省了大量的機(jī)器資源,也大大提高了程序的效率。這是值得我們?nèi)W(xué)習(xí)的。

總結(jié)

達(dá)夫設(shè)備能實(shí)現(xiàn)的優(yōu)化效果日趨在減弱,時(shí)代在變化,語言在發(fā)展,硬件設(shè)備在變化,編譯器性能優(yōu)化,除非特殊的需求下,一般還是沒必要做像這種層次的性能考量的。

不過,這種奇妙的 switch-case 寫法經(jīng)常研究一下還是很有樂趣的,你們覺得呢……

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 寄存器
    +關(guān)注

    關(guān)注

    31

    文章

    5363

    瀏覽量

    121171
  • 編碼
    +關(guān)注

    關(guān)注

    6

    文章

    957

    瀏覽量

    54952
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4827

    瀏覽量

    69054
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C語言基礎(chǔ)知識(shí)(5)--循環(huán)語句

    C語言基礎(chǔ)知識(shí)(5)--循環(huán)語句
    的頭像 發(fā)表于 06-15 10:18 ?2508次閱讀
    <b class='flag-5'>C</b>語言基礎(chǔ)知識(shí)(5)--<b class='flag-5'>循環(huán)</b><b class='flag-5'>語句</b>

    FOR循環(huán)語句分析與應(yīng)用

    FOR循環(huán)語句應(yīng)用比較廣泛,在機(jī)器人編程、PLC編程、C語言編程中都有應(yīng)用。能讀懂這些程序語句,可以更好地理解機(jī)電設(shè)備控制原理,為機(jī)電設(shè)備安裝維修工作帶來便利。
    的頭像 發(fā)表于 09-25 17:14 ?2980次閱讀
    FOR<b class='flag-5'>循環(huán)</b><b class='flag-5'>語句</b>分析與應(yīng)用

    單片機(jī)c語言教程第十三章--C51循環(huán)語句

    單片機(jī)c語言教程第十三課 C51循環(huán)語句 循環(huán)語句是幾乎每個(gè)程序都會(huì)用到的,它的作用就是用來實(shí)
    發(fā)表于 04-15 09:42 ?1743次閱讀

    C語言入門教程-if語句和while循環(huán)

    if語句和while循環(huán) C語言中,if語句和while循環(huán)都會(huì)用到布爾表達(dá)式。下面是一個(gè)使用if語句
    發(fā)表于 07-29 10:48 ?8576次閱讀

    C++語言基礎(chǔ)講解視頻do while循環(huán)語句

    C++語言基礎(chǔ)講解視頻do while循環(huán)語句
    發(fā)表于 01-14 15:32 ?5次下載

    C++語言基礎(chǔ)講解視頻while循環(huán)語句

    C++語言基礎(chǔ)講解視頻while循環(huán)語句,喜歡的朋友可以下載來學(xué)習(xí)。
    發(fā)表于 01-14 15:31 ?3次下載

    Java的循環(huán)語句的詳細(xì)資料說明

    本文檔的主要內(nèi)容詳細(xì)介紹的是Java的循環(huán)語句的詳細(xì)資料說明包括了:1、while循環(huán)語句,2、do…while循環(huán)
    發(fā)表于 03-22 08:00 ?0次下載
    Java的<b class='flag-5'>循環(huán)</b><b class='flag-5'>語句</b>的詳細(xì)資料說明

    C語言的for循環(huán)語句的程序和電路圖免費(fèi)下載

    1、在許多實(shí)際問題中,需要程序進(jìn)行有規(guī)律的重復(fù)執(zhí)行,這時(shí)可以用循環(huán)語句來實(shí)現(xiàn)。在c語言中。用來實(shí)現(xiàn)循環(huán)語句有for
    發(fā)表于 08-20 17:31 ?1次下載
    <b class='flag-5'>C</b>語言的for<b class='flag-5'>循環(huán)</b><b class='flag-5'>語句</b>的程序和電路圖免費(fèi)下載

    Verilog可綜合的循環(huán)語句

    Verilog中提供了四種循環(huán)語句,可用于控制語句的執(zhí)行次數(shù),分別為:for,while,repeat,forever。其中,for,while,repeat是可綜合的,但循環(huán)的次數(shù)需
    發(fā)表于 10-13 12:23 ?2w次閱讀

    什么是python break語句-終止循環(huán)

    循環(huán)的過程中如果要退出循環(huán),我們可以用break語句和continue語句
    的頭像 發(fā)表于 02-23 11:17 ?2591次閱讀

    C語言for語句介紹

    除了可以用while語句和do...while語句實(shí)現(xiàn)循環(huán)外,C語言還提供for語句實(shí)現(xiàn)循環(huán),而
    的頭像 發(fā)表于 03-09 11:14 ?1440次閱讀

    Python的循環(huán)語句介紹

    哈嘍大家好,我是知道。今天帶大家了解下Python的循環(huán)語句 定義循環(huán)語句允許我們執(zhí)行一個(gè)語句語句
    的頭像 發(fā)表于 05-11 17:39 ?964次閱讀

    Verilog常用的循環(huán)語句及用途

    本文主要介紹verilog常用的循環(huán)語句循環(huán)語句的用途,主要是可以多次執(zhí)行相同的代碼或邏輯。
    的頭像 發(fā)表于 05-12 18:26 ?2679次閱讀

    條件語句/循環(huán)語句simulink的實(shí)現(xiàn)方法(一)

    條件語句循環(huán)語句是計(jì)算機(jī)編程中常用的兩種控制結(jié)構(gòu)
    的頭像 發(fā)表于 07-21 16:48 ?1.2w次閱讀
    條件<b class='flag-5'>語句</b>/<b class='flag-5'>循環(huán)</b><b class='flag-5'>語句</b>simulink的實(shí)現(xiàn)方法(一)

    深入理解C語言:循環(huán)語句的應(yīng)用與優(yōu)化技巧

    在程序設(shè)計(jì)中,我們常常需要重復(fù)執(zhí)行某一段代碼。為了提高效率和簡(jiǎn)化代碼,循環(huán)語句應(yīng)運(yùn)而生。C語言作為一門經(jīng)典的編程語言,提供了多種循環(huán)控制結(jié)構(gòu),幫助程序員高效地實(shí)現(xiàn)重復(fù)操作。掌握
    的頭像 發(fā)表于 12-07 01:11 ?251次閱讀
    深入理解<b class='flag-5'>C</b>語言:<b class='flag-5'>循環(huán)</b><b class='flag-5'>語句</b>的應(yīng)用與優(yōu)化技巧
    永利高百家乐官网网址| 澳门百家乐送彩金| 太阳城娱乐城怎么样| 诺贝尔百家乐官网的玩法技巧和规则| 大发888全部的网站地址| 百家乐官网庄闲庄庄闲| 大发888有破解的没| 百家乐官网的薇笑打法| 大发888游戏平台 46| 多台百家乐官网的玩法技巧和规则 | 澳门百家乐| 澳门百家乐怎洋赢钱| 广州百家乐官网酒店用品制造有限公司 | 百威百家乐的玩法技巧和规则| 百家乐官网投注之对冲投注| 百家乐路书| 迪威百家乐官网赌场娱乐网规则 | 伟易博百家乐娱乐城 | 乐利来国际| 澳门百家乐赢钱公式不倒翁| 网上百家乐官网注册彩金| 网上百家乐解密| 柬埔寨百家乐官网的玩法技巧和规则| 爱拼国际娱乐| 百家乐注册彩金| 百家乐官网8点直赢| 现金网开户送彩金| 百家乐怎么赢博彩正网| 百家乐真人游戏赌场娱乐网规则| 678百家乐官网博彩娱乐网| 三易博娱乐城| 百家乐庄闲点| 米其林百家乐官网的玩法技巧和规则| 铂金娱乐| 澳门百家乐现场游戏| 百家乐官网视频桌球| 大发888游戏 下载| 百家乐专业赌徒| 高碑店市| 大发888bocai官方下载| 三国百家乐官网娱乐城|