俺的垃圾箱

架构分析 解释编译原理
posts - 36, comments - 233, trackbacks - 12, articles - 1
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理
初步评测世界上最快的脚本引擎的解释器执行效率 ——  EUPHORIA, Lua,以及俺写的TurboScript解释器核心原型【也许会开源】。

衡量脚本引擎效率,对于非JITter执行的解释型脚本来说,解释器本身的执行效率就是核心的核心,关键之关键了。
总所周知,解释的速度慢于编译的原因就是,因为多了一道工序,指令必须经过解释才能执行。要想提高解释器的
性能,自然是要尽量缩短解释的过程。这个解释的过程就是空耗了,也就是我们平时说的做的无用功。因此,这里
将以编译执行的速度视为空耗为0,以它作为基准,对解释器的效率进行比较。

用什么指令来测试呢,就挑一个最简单的整数加法运算,两数相加,绝大多数脚本都该支持的。由于机器码执行速度
太快,如果只重复执行几次,就算是用高性能计数器也无法计时,所以这里将重复执行2017次加法指令,以300累加,
结果为300*2017。测试全部采用 QueryPerformanceCounter 高精度计数。

在解释器直接执行VM指令的效率之后,我们还想测试下解释器做过程调用的效率,毕竟在实际应用中大部分都是执行的
过程调用(虚方法这些更是过程的升华,当然由于虚方法要查表所以又要比调用普通过程慢)。就将加法指令放到子过
程中,就可以测试了。

其实如果脚本引擎都能实现一个空指令那么就能精确的看出解释器的空耗了!就不比这样麻烦了。

== 速度效率图 ==

该图以x86汇编为基准计算。从图中我们可以看出,EUPHORIA解释器在直接指令执行这块运行性能最高,达到了x86指令
执行速度的73%,也就是说它的空耗只有27%。
ScriptPerfomGraph
实际上 Lua 是用Float实现的整数的运算,在它的数字类型实现中,其实在内部只有浮点类型,所以在执行上占了劣势。
而EUPHORIA则在内部专门实现了整数类型,不过奇怪的是它就没有专门实现浮点类型了,剩下的就全部是用atom类型进
行扩充的。所以这个加法的执行效率还和虚拟机的架构大有关系。另外Lua子过程调用的效率太低,应该还有很大提升余地。

至于俺的TurboScript架构则是类似于 CLR 的架构——面向堆栈的32位虚拟机,所以占了不用传递参数的便宜,再加上
TurboScript解释器则又是用高度优化的x86嵌入汇编写的,这是TurboScript解释器运行高性能的原因。

后面是测试脚本以及结果。

== 直接指令执行效率测试 ==

=== x86汇编基准效率 ===
<code>
  ;计时开始
  MOV EBX, 300  BB2C010000
  ADD EBX, 300  81C32C010000
  ..... --- 总计 2017 次
  ADD EBX, 300  81C32C010000
  ;计时结束
</code>
 
结果运行时间:54; 这里以其为基准,视其为速度为 = 100%; 空耗为0%

=== Lua Script 脚本 ===
<code>
  (计时开始)
  local count = 300
  count = count + 300
    ..... (总计 2017 次)
  count = count + 300
  (计时结束)
</code>

【Lua5.0.2】运行时间: 110;是x86执行速度的49%;空耗为51%
【Lua5.1 】 运行时间: 120;是x86执行速度的45%;空耗为55%


=== EUPHORIA 加法指令顺序执行效率测试脚本 ===
<code>
  --计时开始
    count = 300
    count +=300
    ..... --- 总计 2017 次
    count +=300
  --计时结束
</code>
 
结果运行时间:74 ;是x86执行速度的73%;空耗为27%

=== TurboScript 脚本 ===
<code>
  //计时开始
  300  //Push 300 to the data stack.
  300 +
    ..... (总计 2017 次)
  300 +
  //(计时结束
</code>
 
结果运行时间:87;是x86执行速度的62%;空耗为38%


== 子过程调用的性能 ==

=== x86 汇编 子过程调用效率基准测试脚本 ===
<code>
function add(a,b: integer): integer;
asm
  mov EAX, a
  add EAX, b
end;
--计时开始
  asm
    mOV EAX, 300
    MOV EDX, 300    CALL ADD
    ..... (总计 2017 次)
    MOV EDX, 300    CALL ADD
--计时结束
</code>

结果运行时间:126

=== Lua Script 脚本 ===
<code>
function iAdd(a, b)
  return a+b
end function
  (计时开始)
  local count = 300
  count = iAdd(count, 300)
    ..... (总计 2017 次)
  count = iAdd(count, 300)
  (计时结束)
</code>

【Lua5.0.2】 运行时间:1270;是x86执行速度的10%;空耗为90%
【Lua5.1 】  运行时间:1820;是x86执行速度的7%;空耗为93%


=== EUPHORIA 子过程调用效率测试脚本 ===
<code>
function iAdd(integer a, integer b)
  return a+b
end function
--计时开始
count = 300
count = iAdd(count, 300)
..... --- 总计 2017 次
count = iAdd(count, 300)
--计时结束
</code>
 
结果运行时间:522;是x86执行速度的24%;空耗为76%

=== TurboScript 子过程近调用效率测试 ===
<code>
: Add +; //定义子过程。
//--计时开始
300
300 Add
..... (总计 2017 次)
300 Add
//--计时结束
</code>
结果运行时间:169;是x86执行速度的75%;空耗为25%

最后附上: 完整的测试脚本和相关程序下载

Feedback

#1楼   回复  引用    

2006-12-25 23:41 by 航天奇侠

结果是什么?
你的脚本全面胜利?


能支持中文编程么?
语言用的是什么?可以和C#或者c++结合使用么?

#2楼   回复  引用    

2006-12-25 23:43 by 航天奇侠
换一个博客风格吧,字体太小了,不舒服。

#3楼   回复  引用  查看    

2006-12-26 07:34 by 小生      
是呀﹐這個skin在我的800*600下面很糟糕

#4楼   回复  引用  查看    

2006-12-26 08:22 by Allen Zhang      
不知道你的TurboScript是用来做什么的,用在哪里,谢谢!

#5楼   回复  引用  查看    

2006-12-26 08:54 by 空明流转      
脚本的速度往往不是第一位的.主要是语言本身易用性,可扩展性,与常规语言的接合程度,还有就是要有个比较良好的调试环境和相对完整的IDE,我觉得这个比较重要点.还有就是俺觉得你那个语法有些个怪异...

#6楼   回复  引用  查看    

2006-12-26 09:58 by simonw      
lua 虚拟机不是基于寄存器的么

#7楼   回复  引用    

2006-12-26 10:11 by 虫子[匿名][未注册用户]
不错.

#8楼   回复  引用  查看    

2006-12-26 10:13 by Wisdom-zh      
测试典型性不太好, 你可以试试这段代码:
==============Lua:
function Factorial ( N )
if (N <= 1) then
return 1
else
return N * Factorial(N - 1)
end
end

function Fibonacci ( N )
if(N == 0) then
Result = '[Null]'
elseif(N == 1) then
Result = '1'
else
N1 = 1
N2 = 1

Result = ''

I = 1
while(I <= N) do
if(I > 1) then
Result = Result .. ', '
end
if(I % 2 == 1) then
Result = Result .. tostring(N1)
N1 = N1 + N2
else
Result = Result .. tostring(N2)
N2 = N2 + N1
end

I = I + 1
end
end

return Result
end

function IsPrimeNumber ( N )
if(N == 2) then
return true
end

J = 2
while(true) do
if(N % J == 0) then
return false
end

if(J ^ 2 > N) then
return true
end

J = J + 1
end
end

function PrimeNumberList ( N )
Result = ''

I = 2
while(I <= N) do
if( IsPrimeNumber(I) ) then
if(I > 2) then
Result = Result .. ', '
end
Result = Result .. tostring(I)
end

I = I + 1
end

return Result
end

tick = os.clock ()
print( Factorial(10) )
print( Fibonacci(10) )
print( PrimeNumberList(10000) )
print( (os.clock () - tick) * 1000 )

==============Nuva:
<.
/*========================================================
Factorial
========================================================*/
function Factorial ( N )
if (N <= 1)
Result = 1;
else
Result = N * Factorial(N - 1); // Recursion Call
end if;
end function;

/*========================================================
Fibonacci
========================================================*/
function Fibonacci ( N )
if(N = 0)
Result = '[Null]';
elseif(N = 1)
Result = '1';
else
var N1 = 1;
var N2 = 1;

for(I = 1, N)
if(I <> 1)
Result ~= ', ';
end if;
if(I % 2 = 1)
Result ~= N1;
N1 += N2;
else
Result ~= N2;
N2 += N1;
end if;
end for;
end if;
end function;

/*========================================================
IsPrimeNumber
========================================================*/
function IsPrimeNumber ( N )
Result = true;

if(N = 2)
exit function;
end if;

var I = 2;
loop
if(N % I = 0)
Result = false;
exit loop;
end if;

if(I ^ 2 > N)
exit loop;
end if;

I++;
end loop;
end function;

/*========================================================
PrimeNumberList
========================================================*/
function PrimeNumberList ( N )
Result = "";

for(I = 2, N)
if( IsPrimeNumber(I) )
if(I <> 2)
Result ~= ', ';
end if;
Result ~= I;
end if;
end for;
end function;
.>
==========================================================
<.var tick = System.TickCount.>
[.=Factorial(N = 10).]
[.=Fibonacci(N = 10).]
[.=PrimeNumberList(N = 10000).]
Time: <.= System.TickCount - tick.] ms
==========================================================

#9楼   回复  引用  查看    

2006-12-26 10:13 by Wisdom-zh      
晕死, 居然不支持空格:(

#10楼   回复  引用  查看    

2006-12-26 12:33 by neoragex2002      
语法大致跟IL一样,逆波兰式+堆栈解释器? 这是中间语言形式,还谈不上脚本,空明流转说得对,脚本应该比高级语言还要高级,nuva就不错,看上去该有的都有,继续努力:)

#11楼   回复  引用    

2006-12-26 13:28 by jameswei[未注册用户]
以前用vb6写过一个仿vb的脚本引擎

#12楼[楼主]   回复  引用  查看    

2006-12-27 17:48 by Riceball LEE      
我的构思和CLR差不多,所以一旦开发出高级语言也可以和CLR的高级语言一样(Pascal, C#,VB之流的)。当然架构要比它简单,只分了代码区和数据区(MetaData),该框架实现既能解释执行,又能JIT执行,或者直接编译成Exe。不限制非要StrongType,由用户决定是否需要StrongType(有这个反编译简直是太容易了),由于数据区格式简单,宿主程序可以直接访问脚本引擎的数据区取得/设置数据,调用脚本内的过程。这本来只是为我的人工智能项目设想开发的嵌入脚本引擎,要求过程。变量(字段)能分布到数据库运行时刻动态绑定。

@neoragex2002
对,这相当于CLR的ILAsm,实际上这是我基于FORTH语言规则搞的中间语言汇编, 只是为了试验我的虚拟机原型,我也并没有说是测试脚本的性能,标题说的是脚本引擎的解释器的性能啊。高级语言还早,我准备重构,在IL指令集上部分兼容CLR,这样汇编指令格式就改成和ILAsm靠拢了。


@Wisdom-zh
谢谢,指令的综合测试也是有必要的,等以后试下,这个只有和高级语言相比了。不过你这测试的是指令的综合性能了,和我目前测试的目的不一样的——我测试的是解释器本身的性能而不是指令,底层架构决定上层建筑。如果不尽量把底层解释器弄快,我想综合起来也快不了多少。

#13楼   回复  引用    

2006-12-29 10:19 by tankaiha[未注册用户]
呵呵,支持开源

#14楼[楼主]   回复  引用  查看    

2006-12-30 09:39 by Riceball LEE      
@tankaiha
如果没有合作开发者,开源就是空谈。



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 603468




相关文章:

相关链接: