作者laechan (小太保)
看板mud_sanc
标题Re: [转录][好文]将LPC程式最佳化
时间Tue Jan 5 18:09:58 2010
※ 引述《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)
1F:推 happyhero :受教了:) 01/05 22:17