Re: [轉錄][好文]將LPC程式最佳化

看板mud_sanc (Sanctuary - 聖殿)作者 (小太保)時間16年前 (2010/01/05 18:10), 編輯推噓1(100)
留言1則, 1人參與, 最新討論串2/2 (看更多)
※ 引述《laechan (小太保)》之銘言: : for ( i = 0; i < max; i++) : if ( list == some_condition ) : do_something_with( list, this_player()->query("name") ); : 假設 "name" 是不變的, 則以下的方法會比較好: : name = this_player()->query("name"); : for ( i = 0; i < max; i++) : if ( list == some_condition ) : do_something_with( list, name ); 這個是基本的寫法,若一個東西在程式裡頭會被讀取兩次以上時, 通常都會建議宣告一個變數來取代之。 再來的話就是內迴的概念,以上面為例,寫法可再改為.. do_something_with(list,name,max) ^^^^^ 然後在 do_something_with 函數中做 some_condition 的判斷, 它們的差別就在於一個是呼叫了 max 次的 do_something_with 函數,一個只「呼叫一次」。 enable 中後期的最佳化就是以這個為主。 : write( @ENDMESSAGE : This is a forect : and is really boring : You are feeling sleepy. : ENDMESSAGE : ); 各位只要知道 write(@LONG ... LONG); 的用法即可。 通常若是多行式的,可以使用底下的方式。 write(@LONG . . . LONG @LONG . . . LONG ); 原文則是建議採用兩段 write 的方式,因為 write 是 efun ,依目前一般主機的水準這也是可以的,但不建議。 : printf(Your name is %s\nand your level is %d\n", : this_player()->query("name"), this_player()->query("level") ); 這與 sprinf 的用法類似,上面的意思就是.. write("你的 id 是 "+ppl->query("name")+",你的"+ "等級是 "+ppl->query("level")+".\n"); 將之修改為.. write(sprintf("你的 id 是 %s,你的等級是 %d.\n", ppl->query("name"), ppl->query("level"))); 或像上面那樣直接用 printf 輸出。 (但偶爾會遇到需要做字串累加的情況就不能用 printf) 我自己的實驗結果,使用 printf 或 sprintf 在迴圈式的字串 累加及訊息輸出上確實有幫助─程式不容易因為沉重的迴圈而 自行中斷執行。 : 摘自 TMI-2 的 /adm/daemons/cmd_d.c : : bin_ls = get_dir(path + "/"); : result = ({ }); : for (i = 0; i < sizeof(bin_ls); i++) { : if(sscanf(bin_ls, "_%s.c", tmp) == 1) : result += ({ tmp }); : } : cmd_table[path]=result; : 這裡, 並不是所有的項目都有選到, 被選到的也是在被修改過後再放到最後 : 的陣列中. : 以下面的方法取代: : bin_ls = get_dir(path + "/"); : i = sizeof(bin_ls); : result = allocate(i); : j = 0; : while (i--) { // 使用 'while' 的原因請參考下面 : if(sscanf(bin_ls, "_%s.c", tmp) == 1) : result[j++] = tmp; : } : cmd_table[path]=result[0..j-1]; 在聖殿的實務上,我們還是以底下的方法為主... mixed ids=({}),usr=users(); foreach(ppl in usr) if(ppl->query("level")>119) ids+=({ppl->query("name")}); 因為通常我們都無法事先預知「ids 到最後會有多大」, 當然因為它最大頂多等於 sizeof(usr),所以若照底下那 樣寫也是可以的... mixed ids=({}),usr=users(); int i,j=sizeof(usr),k; ids=allocate(j); // 預先配置 size 給 ids while(i++<j) if(usr[i]->query("level")>119) ids[k++]=usr[i]->query("level"); 實務上因為這樣子寫的結果必須額外多宣告一些變數,所 以通常都會採取「變數盡量減少」的寫法。 我自己本身也相當少使用到 allocate,但有需要的時候我 倒是會用啦..但我自己比較少有印象說哪裡有用到。 : 不要用下列方法來格式你的 mapping: : emotemap = ([ ]); : 應該改用: : emotemap = allocate_mapping( 200 ); 這個我跟 nobu 求證過,allocate_mapping 在目前的聖殿是無效 的指令。 所以 mapping 變數的初始化還是以 emotemap = ([ ]); 為主。 : 3. 控制程序 (CONTROL FLOW) 以及 迴圈 (LOOPING) : 在一個完整的 LPC 程式中, 控制程式執行的程序 (由測試, 以及部份的迴圈所 : 組成) 可以因為正確的使用不同的形式而變得比較有效率. : 3.1. WHILE : 最簡單的 (也是在簡化型裡最快的) 迴圈使用方式. : 常見的是: : list = users(); : for ( i = 0 ; i < sizeof(list) ; i++ ) : do_something_with_item( list ); 這個以前有盯過,各位 wiz 們應該都已經避免這樣子寫了。 : 這是非常的沒有效率, 因為 sizeof(list) 的值每次都要被重新計算出來 (參考 : 上面的 GENERAL POINTS 一節). 如果你想將整個 list 反過來排列, 試著使用以 : 下的方法: : max = sizeof(list); // slight performance gain : i = -1; : while ( ++i < max ) //evaluate and increment at same time : do_something_with_item(list); 這種寫法可以減少一個變數 j = sizeof(list) : 如果順序沒有什麼太大的關係, 以下是最快的方式: : i = sizeof(list); : while (i--) : do_something_with_item(list); 這種迴圈會比 for 執行起來更快速。 : 這就是 '簡單的調件 while 迴圈' ('simple condition while loop'). 和下列的 : 實在是沒什麼太大的不同. : for ( i = sizeof(list) ; i-- ; ) ; : 以及相對等的 while 迴圈. 但還是有少許利益可得. : 3.2. FOR : 最常見的迴圈結構之一, 這在當你每次都需要在迴圈結述前執行某個比較複雜的 : 動作時非常有用. 如果只是使用簡單的指數增加或減少, 用 'while' 敘述會比較 : 好. : 如果 '結述運作' (ending operation) 比較複雜, 則以下的通用例子: : for ( initialise ; test ; final ) { : main body : } : 比下列清楚: : initialize : while ( test ) { : main body : final : } 目前已經進化到使用 foreach,foreach 的用法各位 wiz 們應該也已經知道了吧。 理論上 foreach 在通常條件下執行效率不會比 while 差。 : 3.3. 開關 (SWITCH) : switch 敘述應該盡可能的用來取代 'if-then-else' 型結構. 因為新的 driver : 都有對 switch 敘述作相當程度的簡化. 另一方面, 用 switch 敘述的程試碼看來 : 也比較 '乾淨'. : 用 switch 敘數的另一個優點是 `狀況範圍' (case range) 的支援. (一個 C 沒有 : 的特色). 如果你的測試設定如下面的範圍: : case 1: case 2: case 3: : 可用以下代替: : case 1..3: : 可能比較有效率 (至少字少打一些) 這是 case by case,有些東西 switch 無法支援。 但不管如何,用到 if...else 的場合,要特別注意.. 一、判斷式的先後「判斷」─哪些可以盡量挪前判斷,哪些   可以盡量挪後判斷。 二、如何減化 if..else 的結構。例如底下... if(xxx) { if(ooo1) . . else . . } else { if(ooo1) . . else . . } 它們或許可減化為... if(xxx && ooo1) . . else if(xxx & !ooo1) . . else if(!xxx & ooo1) . . else . . : 4. 結論 : 我確定這份文件中一定有錯誤, 但是人不是完美的. : 以下的人對本文的 1.1 版提出問題, 意見: : @TMI-2: Blackthorn, Mobydick, Square, Alexus, Amylaar, Darin : @Ivory.tower: Telsin, Vampyr : @Underdark: Cynic, Brian : Luke [Zak]. : 程式的最佳化 : ============== : 在optimizing_code裡已經談到了一些最佳化的簡單注意事項。然而最 : 佳化並不是少數adm或大巫師的事,而是每一個巫師寫程式都必須注意 : 的事項。因此請大家在完成一個作品時,再花一些時間重看一下程式 : ,看看可不可以 藉由一些簡單的更改來避免一些不必要的計算,即使 : 只是節省一兩個計算也是好的。當然也不必為了省一點點的計算而花 : 很多時間或是把程式寫得很複雜。但是一些簡單的原則,或是稍微注 : 意一下,就可能 會有很大的影響。 : 我們來看一個簡單的例子,底下是/adm/daemons/aim_d.c其中的一段 : 程式,注意看第七行: : if( random(100) < 30 && !me->query("npc") ) return 0; : 本來放在倒數第五行,也就是變成註解的地方。我看到了,就把它移 : 到現在的位置。這樣有什麼差別呢?如果在原來的位置,中間作了一 : 堆關於skill的計算,當此行成立,這些計算都浪費了。而移到第七行 : 的位置,當此行成立,就不會去計算skill,只是更改一下程式的位置 : ,就可以節省許多不必要的計算。這個程式是醫生每回合攻擊時都會 : 呼叫到,你可以想像原來的寫法作了多少不必要的計算。 : int aim_target(object me, object victim) : { : int skill, diffculty; : string loc; : object weapon; : // difficulty for player : if( random(100) < 30 && !me->query("npc") ) return 0; : skill = (int)me->query_skill("anatomlogy"); : loc = (string)me->query("aiming_loc"); : if( !skill || !loc ) return 0; : diffculty = diffs[loc]; : if( undefinedp(diffculty) ) return 0; : diffculty += diffculty*(int)victim->query("aim_difficulty/"+loc)/100; : [中間省略] : skill /= 10; : skill += (int)me->query_stat("int") * 2 + (int)me->query_stat("kar"); : skill -= (int)victim->query_stat("int") * 2 + (int)victim->query_stat("kar"); : // if( random(100) < 30 && !me->query("npc") ) return 0; : if( random(skill) < diffculty ) return 0; : return (int)call_other( this_object(), "hit_" + loc, me, victim ); : } : 在考慮程式最佳化時,先想想這段程式被使用的頻率有多高,如果是 : 使用頻率很高的程式,那就要多花些心力注意最佳化。一般使用頻率 : 較高的程式大略是/adm/, /cmds/, /std下的程式,不過這不是一般 : 巫師可以動的,另外公會的一些程式,武器的特攻、NPC的tactic,以 : 及一些init,relay_message的程式等等。這些都是使用頻率很高的程式 一、最佳化通常來自於經驗。我 coding 十一年了,哪些東西   可以怎麼寫,其實最後都是出於自然。 二、在追求最佳化的過程中,有時會遇到系統能否負荷的問題   ,例如我們將一個 500 次迴圈的字串累加,改成讓系統去   跑 500 次的 printf,「理論上」這是很不錯的寫法,但   實際上它可能遇到的問題就是跑三百多次後系統就不跑了   所以追求最佳化的過程不一定都會順利,這時該怎麼辦?   就是不要「最佳」,而是退一步採取「最適寫法」。 這同樣是 case by case,我在 /cmds/std/_who.c 以及 /cmds/std/_skilldata.c 的寫法就不一樣,因為我有實   際 try 過,實務上還是要以能跑出「結果」為首要目標   ,然後再 try 幾種寫法,從中挑出對系統負擔沒那麼大   、然後又可以得到我們想要的結果,這就是最適寫法。 三、通常最佳化會面臨的另一個問題,就是某一程式段最佳化   的同時,卻在其它的地方用了又耗資源的寫法,這使得你   的最佳化的效果無法突顯。   所以才說最佳化的工作通常要靠經驗,熟悉最佳化,你在   寫每一段程式時就自然而然會採取對系統較好的寫法,它   到最後甚至會影響你在系統撰寫之初,就同時對資料庫做   「最佳規劃」,我以底下的範例做結尾... mapping seed_data = ([ "rice":(["name":"稻子", 中文名字 "price":5000, 售價 "value":15000, 種子價格 "mature":36000, 10小時 . . ]), ]); 問題:種子資料一定都得像上面那樣宣告為雙層 mapping 資料嗎? 變通方式之一.. mappnig seed_data=([ "rice":({"稻子",5000,15000,36000,....}),]); }); 一、因為欄位"順序"是固定的 二、這樣的資料非常容易宣告,也非常容易做資料增減 有權限的 wiz 可以比較 /d/skill/skill_stat.c 與 /d/skill/skill_guide.c 的資料宣告方式。            Laechan -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 61.225.161.60 ※ 編輯: laechan 來自: 61.225.161.60 (01/05 18:16)

01/05 22:17, , 1F
受教了:)
01/05 22:17, 1F
文章代碼(AID): #1BGm_wen (mud_sanc)
討論串 (同標題文章)
文章代碼(AID): #1BGm_wen (mud_sanc)