2019年11月24日 星期日

[不專業研究]for、while、goto效能評比

本文同步自友站  goto語法教學網 發佈

我們常常聽說goto是一把雙面刃,它可以在程式碼中像任意門一樣穿梭自如,但是卻破壞了程式碼的結構與可讀性。

但是,goto的優點只有跳躍而已嗎?

本文將設計針對for、while、goto的效能做比較的實驗,探討goto是否能對程式碼的效能進行優化。

測試環境:
程式語言:C++
作業系統:Windows7
CPU:Intel i5-3470
使用IDE及編譯器:Visual Studio 2019
平台模式:64位元,debug模式(無最佳化)

以下是實驗程式碼:
 unsigned int length = 1024 * 1024 * 1024 * 10;
     int length2 = 1024 * 1024 * 1024;
     int key = 0;
std::cout << "一層迴圈\n";
     i = 0;
     clock.restart();
     for (i = 0;i < length;i++)
     {
          key = i;
     }
     std::cout << "for的時間為" << clock.GetTime() << "毫秒\n";


     i = 0;
     clock.restart();
     while (i < length)
     {
          key = i;
          i++;
     }
     std::cout << "while的時間為" << clock.GetTime() << "毫秒\n";

     i = 0;
     clock.restart();
flag0:
     key = i;
     i++;
     if (i < length) goto flag0;

     std::cout << "goto的時間為" << clock.GetTime() << "毫秒\n";


     std::cout << "二層迴圈\n";

     i = 0;
     j = 0;
     clock.restart();
     for (j = 0;j < 10;j++) {
          for (i = 0;i < length2;i++)
          {
                key = i;
          }
     }
     std::cout << "for的時間為" << clock.GetTime() << "毫秒\n";

     j = 0;
     i = 0;
     clock.restart();
     while (j < 10) {
          i = 0;
          while (i < length2)
          {
                key = i;
                i++;
          }
          j++;
     }
     std::cout << "while的時間為" << clock.GetTime() << "毫秒\n";

     i = 0;
     j = 0;
     clock.restart();
flag:
     i = 0;
flag2:
     key = i;
     i++;
     if (i < length2) goto flag2;
     j++;
     if (j < 10) goto flag;
     std::cout << "goto的時間為" << clock.GetTime() << "毫秒\n";

我們分別利用單層迴圈和雙層迴圈測試for、while、goto的效能。
首先,定義了一個 unsigned int的變數length,大小為1024 * 1024 * 1024 * 10。
然後分別設計for、while、goto的單層迴圈去運行它,次數為length。

然後,定義了一個  int的變數length2,大小為1024 * 1024 * 1024。
然後分別設計for、while、goto的雙層迴圈去運行它,次數為10*length2。

所以,單層迴圈和雙層迴圈運轉的次數是一樣的。

最後,我們在迴圈中加入了key=i,模擬實際迴圈中常見的狀況。

測試結果:

在一層迴圈中,for的效能最差,花費時間為4627毫秒,次之則為while,為4426毫秒,令人驚訝的是,goto的效能竟然最高,僅花費了4402毫秒。



然而,進入第二階段的雙層迴圈試驗,原本趨於劣勢的for迴圈竟逆轉局勢並驚險的勝出,花費了22026毫秒,而goto在這場比賽則敬陪末座,總花費時間為22119毫秒。

但是,真的只差了一點點,我們再重跑一次程式:


在第二次,在一層迴圈中,一樣是由goto勝出,但是呢,在雙層迴圈中,goto再次贏過for和while拿下勝利,總花費時間為22018毫秒,while則敬陪末座。

可見在第二層迴圈中,三者的速度是差不多的。

但是,如何讓goto在雙層迴圈中發揮它在單層迴圈的優勢呢?

我們再做個實驗
     unsigned int length = 1024 * 1024 * 1024 * 10;
     int length2 = 1024 * 1024 * 1024;
     int key = 0;

     i = 0;
     j = 0;
     clock.restart();
     for (j = 0;j < 10;j++) {
          for (i = 0;i < length2;i++)
          {
                key = i;
          }
     }
     std::cout << "for+for的時間為" << clock.GetTime() << "毫秒\n";

     i = 0;
     j = 0;
     clock.restart();
     for (j = 0;j < 10;j++)
     {
          i = 0;
     flag1:
          i++;
          key = i;
          if (i < length2)goto flag1;
     }


     std::cout << "for+goto的時間為" << clock.GetTime() << "毫秒\n";


     i = 0;
     j = 0;
     clock.restart();
flag3:
     i = 0;
     for (i = 0;i < length2;i++)
     {
          key = i;
     }
     j++;
     if (j < 10) goto flag3;
     std::cout << "goto+for的時間為" << clock.GetTime() << "毫秒\n";

在這一次,我們分別使用雙層for迴圈、外圈為for內圈為goto、外圈為goto內圈為for,來做測試,跑的次數跟上一次的實驗一樣。

測試結果:

果不其然,讓goto在內層迴圈,效能上會有顯著優勢,而放在外層則不具有優勢。

為了防止運氣問題,我們再讓程式跑一次:


一樣的,for+goto再度以19074毫秒壓倒性的勝出,for+for最差,為22196毫秒,goto+for則為22123毫秒


結論:
在單層迴圈中,goto在效能上會比for和while來的有優勢,而在雙層迴圈中,goto在內層迴圈中依然可以為效能帶來貢獻。

這次的for、while、goto不專業效能評比就到這邊結束了,如果有其他點子想測試的,歡迎留言一起來做討論,我們下次見囉!

沒有留言:

張貼留言

有興趣或有疑問的歡迎提問與交流喔!!!