代碼高效性和健壯性的權衡
http://www.cnblogs.com/walzer/archive/2009/12/14/1623945.html
Posted on 2009-12-14 17:29
Walzer 閱讀(428) 評論(0)
編輯 收藏
這個是比較早, 09年4月份的事情了。整理文檔翻出來,覺得還有點意思.
當時CLIENT-SERVER的通訊封包格式有兩種方案
a. 以7E為開頭和結尾, PAYLOAD中所有7E的位元組, 都在其後擴充一個BYTE, 寫為7E, 7D, (稱為轉義). 封包中不帶CHECKSUM, CRC等校正用的欄位
b. 以7E為開頭和結尾, 帶一個CHECKSUM欄位, PAYLOAD中不進行7E->7E 7D的轉義.
幾個同事就這個通訊封包格式, 採用方案一或方案二, 開會激烈討論了個把小時。
我原先反對轉義方案的出發點比較模糊, 只是覺得原先轉義的方案"不優雅"; 後來才想清楚了不優雅的"本質"在哪裡.
所有的代碼, 可以在抽象意義上分作兩大塊, 兩者的著重點是不同的.
(1) 正常啟動並執行代碼. 首要追求高效性,
這個"高效性"如果從邏輯的角度來解釋, 那麼一方面是"高效"地對正確的資料執行正確的演算法(方法/策略), 另一方面是"高效"地找出異常, 然後丟給異常處理代碼去處理.
(2) 處理異常的代碼. 首要追求健壯性.
就是程式必須能從異常中自我恢複. 由於代碼多數時間跑的是"正常"邏輯, 少數情況下才不得不處理"異常", 所以"異常"處理的代碼中, 首要任務是健壯, 跑不死, 而高效性則是次要的.
那麼回到轉義的策略上來看,原先的7E -> 7E 7D, 使得裝包和拆包的時候, 時間上都必須挨位元組掃描過去, 空間上必須另開一塊記憶體, 這些"不優雅"的工作是為了應對網路傳輸時包資料丟失. 包資料丟失是一個異常情況,而轉義策略本質上就是不論好包壞包,一棍子打死, 統統要經過轉義演算法. 用上面的觀點解釋, 即"為了異常情況下的健壯性,犧牲了正常情況下的高效性".
而用Header + Length + CheckSum + Payload + Tailer的做法, 邏輯上是這樣的
Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->if ( 以Length為基礎, 得知CheckSum正確 和 Tailer正確){ 正確的包,走正常處理流程, 直接把Payload傳給上層邏輯處理} else{ 錯誤的包,走異常處理流程,挨位元組掃描下一個Header, 然後再算length, checksum, tailer等}
這是在上面"正常->高效性 & 異常->健壯性"指導思想下的做法. 那麼現在就剩最後一個問題, 計算checksum和轉義的工作相比, 哪一個更快? 如果轉義處理的效率, 比checksum更高,那麼上面的假設就不成立了.
所以我做了個實驗, 代碼如下
代碼 Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->BYTE* pBuf1 = new BYTE[1024];BYTE* pBuf2 = new BYTE[1024*2];UINT8 sum = 0;DWORD tStart = 0, tEnd = 0;// CODE 1, 轉義處理tStart = GetTickCount(); // WM上的毫秒級時間for(int j = 0; j < 100000; j++){ for(int i1 = 0, i2 = 0; i1 < 1024; i1++, i2++) { if (pBuf1[i1] == 0x7E && pBuf1[i1+1] == 0x7D) { pBuf2[i2] = pBuf1[i1]; i1++; } else { pBuf2[i2] = pBuf1[i1]; } }tEnd = GetTickCount(); printf("copy 1024 bytes * 100K times, use %d ms\n", tEnd - tStart);// CODE 2, CHECK SUMtStart = GetTickCount();for(int j = 0; j< 100000; j++){ for(int i = 0; i < 1024; i++) { sum += pBuf1[i]; }}tEnd = GetTickCount();printf("check sum 1024 bytes *100K times, use %d ms\n", tEnd - tStart);
上面這段代碼,在SAMSUNG 2442 400MHz的CPU, WM 6.1系統上運行結果是
copy 1024 bytes * 100K times, use 11677 ms
check sum 1024 bytes *100K times, use 7504 ms
所以, 一個正確的資料包, 經過CHECKSUM計算的時間, 比其經過轉義計算的時間要快得多, 僅為其64%. 這是手機上的情況, 伺服器上的百分比不太清楚是什麼樣,但至少有一點是肯定的,就是用CHECKSUM的方案比用轉義的方案,在正常邏輯情況下速度更快、記憶體開銷更少。當伺服器同時處理十萬數量級網路資料包的時候, 效能提升還是比較可觀的。
這篇文章的重點不在於哪個方案更嚴謹,或者上面的邏輯對不對,而是在於這麼一個思想:
(1) 正常啟動並執行代碼. 首要追求高效性,
(2) 處理異常的代碼. 首要追求健壯性.