Re: [轉錄][好文]將LPC程式最佳化
看板mud_sanc (Sanctuary - 聖殿)作者laechan (小太保)時間16年前 (2010/01/05 18:10)推噓1(1推 0噓 0→)留言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
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 2 之 2 篇):
mud_sanc 近期熱門文章
PTT遊戲區 即時熱門文章
24
41