duff's device也是利用了相似的原理減小比較的次數來提升了效率。ios
前幾天在網上看見了一段代碼,叫作「Duff's Device」,後經驗證它曾出如今Bjarne的TC++PL裏面:
void send( int * to, int * from, int count) // Duff設施,有幫助的註釋被有意刪去了 { 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語句和一個do-while語句糅合在了一塊兒。而在我看過的全部關於C和C++的書中,這樣的代碼都是毫無道理的。然而,不管是在VS2005仍是在GCC4.1.2下,這段代碼都能正確地經過編譯。加上適當的main函數,它均可以正常運行。我百思不得其解。上網去查,也沒查到好答案。 怎麼辦?先看看它的彙編代碼吧,也許能夠經過它的彙編代碼看出它的意思。 gcc -S send.cpp 粗略地一看,彙編代碼都已經上百行了,並且裏面還有一個跳轉表,十幾個標號。通常狀況下,幾十行的彙編代碼都已經不太好看懂了,要把這幾百行彙編徹底看懂,估計須要花不少時間。 既然直接來太麻煩,那就用簡便一點的方法吧: #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 ); } } 實驗結果 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循環,但這個循環的入口點並不必定是在do那裏,而是由這個switch語句根據n,把循環的入口定在了幾個case標號那裏。也就是說,程序的執行流程是:程序一開始順序執行,當它執行到了switch的時候,就會根據n的值,直接跳轉到 case n那裏(今後,這個swicth語句就再也沒有用了)。程序繼續順序執行,再當它執行到while那裏時,就會判斷循環條件。若爲真,則while循環開始,程序跳轉到do那裏開始執行循環(這時候因爲已經沒有了switch,因此後面的標號就變成普通標號了,即在沒有goto語句的狀況下就能夠忽略掉這些標號了);爲假,則退出循環,即程序停止。 忙活了幾個小時,終於明白這段代碼是怎麼回事了。回想一下,本身之前也曾寫過相似C的語法但比C語法簡單不少的解釋器,用的是遞歸子程序法。而若是用遞歸降低法來分析這段代碼,是確定會有問題的。 至於它是怎麼正確編譯並運行的,這須要去研究一下C編譯器,這個之後再說。如今,仍是再來看看達夫設備吧。其實,這個send函數的簽名就已經很具備提示性了:把from數組中的元素拷貝count個到to裏面去。因而有人會說,這個工做簡單,不就這樣嗎: void my_send( int * to, int * from, int count) { for ( int i = 0 ; i != count; ++ i) { * to ++ = * from ++ ; } } 這段代碼的確很簡潔,也是正確的,並且生成的機器碼也比send函數短不少。可是卻忽略了一個因素:執行效率。計算一下就能夠知道,my_send函數裏面的循環條件,即i和count的比較運算的次數,是達夫設備的8倍!在作整數賦值這種耗時不多的工做時,這種耗時相對較高的比較工做是會大大地影響函數總體的效率的。達夫設備則是一種很是巧妙的解決辦法(固然,它利用到了編譯器的一些實現上的工做),並且若是把8換成更大的數的話,效率就還能夠提升! 它的思路是這樣的:把原數組以8個int爲單位分紅若干個小組,複製的時候以小組爲單位複製,即一次複製8個 int。也就是說,在my_send函數中以一次比較運算的代價換來1個int的複製,而在達夫設備中,卻能以一次比較運算的代價換來8個int的複製。而switch語句則是用來處理分組時剩下的不到8個的int(這些剩餘的不是數組最後的,而是數組最開始的),很巧妙。 總結:像達夫設備這樣的代碼,從語言的角度來看,我我的以爲不值得咱們借鑑。由於這畢竟不是「正常」的代碼,至少C/C++標準不會保證這樣的代碼必定不會出錯。另外,這種代碼估計有不少人根本都沒見過,若是本身寫的代碼別人看不懂,這也會是一件很讓人頭疼的事。然而,從算法的角度來看,我以爲達夫設備是個很高效、很值得咱們去學習的東西。把一次消耗相對比較高的操做「分攤「到了屢次消耗相對比較低的操做上面,就像vector<T>中實現可變長度的數組的思想那樣,節省了大量的機器資源,也大大提升了程序的效率。這是值得咱們學習的。