Builder和VM和动态脚本解释器msvbvm50.dll是什么么? 怎么用?

当前访客身份:游客 [
已有文章 2094 篇
当前位置:
使用 C 语言实现一个虚拟机
英文原文:
0人收藏此文章,
推荐于 3个月前 (共 14 段, 翻译完成于 05-13) ()
参与翻译(8人):
, , , , , , ,
GitHub 展示了我们将会构建的东西, 你也可以在发生错误的时候拿你的代码同这个资源库进行对比.&
我考虑过会写一篇有关使用C语言构建专属虚拟机的文章. 我喜欢研究“底层”的应用程序,比方说编译器、解释器以及虚拟机。我也爱谈论到它们。我也有另外一个系列的有关使用Go来编写一个解释器的文章(目前正在准备中)。我也在开发自己的编程语言&.
&翻译的不错哦!
必要的准备工作及注意事项:
在开始之前需要做以下工作:
一个C编译器——我使用了 clang 3.4,也可以用其它支持 c99/c11 的编译器;
文本编辑器——我建议使用基于IDE的文本编辑器,我使用 E
基础编程知识——最基本的变量,流程控制,函数,数据结构等;
Make 脚本——能使程序更快一点。
为什么要写个虚拟机?
有以下原因:
想深入了解计算机工作原理。本文将帮助你了解计算机底层如何工作,虚拟机提供简洁的抽象层,这不就是一个最好的学习它们原理的方法吗?
更深入了解一些编程语言是如何工作。例如,当下多种经常使用那些语言的虚拟机。包括JVM,Lua VM,FaceBook 的 Hip—Hop VM(PHP/Hack) 等。
只是因为有兴趣学习虚拟机。
我们将要实现一种非常简单的自定义的指令集。我不会讲一些高级的如位移寄存器等,希望在读过这篇文章后掌握这些。
&翻译的不错哦!
我们的虚拟机具有一组寄存器,A,B,C,D,E,&和F。这些是通用寄存器,也就是说,它们可以用于存储任何东西。一个程序将会是一个只读指令序列。这个虚拟机是一个基于堆栈的虚拟机,也就是说它有一个可以让我们压入和弹出值的堆栈,同时还有少量可用的寄存器。这要比实现一个基于寄存器的虚拟机简单的多。
言归正传,下面是我们将要实现的指令集:
PSH&5&&&&&&&;&pushes&5&to&the&stack
PSH&10&&&&&&;&pushes&10&to&the&stack
ADD&&&&&&&&&;&pops&two&values&on&top&of&the&stack,&adds&them&pushes&to&stack
POP&&&&&&&&&;&pops&the&value&on&the&stack,&will&also&print&it&for&debugging
SET&A&0&&&&&;&sets&register&A&to&0
HLT&&&&&&&&&;&stop&the&program
这就是我们的指令集,注意,POP 指令将会打印我们弹出的指令,这样我们就能够看到 ADD 指令工作了。我还加入了一个 SET 指令,主要是让你理解寄存器是可以访问和写入的。你也可以自己实现像MOV&A&B(将A的值移动到B)这样的指令。HTL 指令是为了告诉我们程序已经运行结束。
&翻译的不错哦!
虚拟机是如何工作的呢?
现在我们已经到了本文最关键的部分,虚拟机比你想象的简单,它们遵循一个简单的模式:读取;解码;执行。首先,我们从指令集合或代码中读取下一条指令,然后将指令解码并执行解码后的指令。为简单起见,我们忽略了虚拟机的编码部分,典型的虚拟机将会把一个指令(操作码和它的操作数)打包成一个数字,然后再解码这个指令。
开始编程之前,我们需要设置好我们的项目。第一,你需要一个C编译器(我使用 clang 3.4)。还需要一个文件夹来放置我们的项目,我喜欢将我的项目放置于~/Dev:
$cd&~/Dev/
如上,我们先 cd 进入~/Dev 目录,或者任何你想放置的位置,然后新建一个目录(我称这个虚拟机为"mac")。然后再 cd 进这个目录并新建我们 src 目录,这个目录用于放置代码。
&翻译的不错哦!
makefile 相对直接,我们不需要将什么东西分成多个文件,也不用包含任何东西,所以我们只需要用一些标志来编译文件:
SRC_FILES&=&main.c
CC_FLAGS&=&-Wall&-Wextra&-g&-std=c11
CC&=&clang
&&&&${CC}&${SRC_FILES}&${CC_FLAGS}&-o&mac
这对目前来说已经足够了,你以后还可以改进它,但是只要它能完成这个工作,我们应该满足了。
指令编程(代码)
现在开始写虚拟机的代码了。第一,我们需要定义程序的指令。为此,我们可以使用一个枚举类型enum,因为我们的指令基本上是从0到X的数字。事实上,可以说你是在组装一个汇编文件,它会使用像 mov 这样的词,然后翻译成声明的指令。 我们可以只写一个指令文件,例如 PSH, 5&是0, 5,但是这样并不易读,所以我们使用枚举器!
typedef&enum&{
}&InstructionS
现在我们可以将一个测试程序存储为一个数组。我们写一个简单的程序用于测试:将5和6相加,然后将他们打印出来(用POP指令)。如果你愿意,你可以定义一个指令将栈顶的值打印出来。
&翻译的不错哦!
指令应该存储成一个数组,我将在文档的顶部定义它;但你或许会将它放在一个头文件中,下面是我们的测试程序:
const&int&program[]&=&{
&&&&PSH,&5,
&&&&PSH,&6,
上面的程序将会把5和6压入栈,调用 ADD 指令,这将会把栈顶的两个值弹出,相加后将结果压回栈中,接下来我们弹出结果,因为 POP 指令将会打印这个值,但是你不必自己再做了,我已经做好并测试过了。最后,HLT 指令结束程序。
很好,这样我们有了自己的程序。现在我们实现了虚拟机的读取,解码,求值的模式。但是要记住,我们没有解码任何东西,因为我们给出的是原始指令。也就是说我们只需要关注读取和求值!我们可以将它们简化成两个函数 fetch 和 evaluate。
&翻译的不错哦!
取得当前指令
因为我们已经将我们的程序存成了一个数组,所以很简单的就可以取得当前指令。一个虚拟机有一个计数器,一般来说叫做程序计数器,指令指针等等,这些名字是一个意思取决于你的个人喜好。在虚拟机的代码库里,IP 或 PC 这样的简写形式也随处可见。
如果你之前有记得,我说过我们要把程序计数器以寄存器的形式存储...我们将那么做——在以后。现在,我们只是在我们代码的最顶端创建一个叫 ip 的变量,并且设置为 0。
int&ip&=&0;
ip 变量代表指令指针。因为我们已经将程序存成了一个数组,所以使用 ip 变量去指明程序数组中当前索引。例如,如果创建了一个被赋值了程序 ip 索引的变量 x,它将存储我们程序的第一条指令。
[假设ip为0]
int&ip&=&0;
int&main()&{
&&&&int&instr&=&program[ip];
&&&&return&0;
如果我们打印变量 instr,本来应是 PSH 的它将显示为0,因为在他是我们枚举里的第一个值。我们也可以写一个取回函数像这样:
int&fetch()&{
&&&&return&program[ip];
这个函数将会返回当前被调用指令。太棒了,那么如果我们想要下一条指令呢?很容易,我们只要增加指令指针就好了:
int&main()&{
&&&&int&x&=&fetch();&//&PSH
&&&&ip++;&//&increment&instruction&pointer
&&&&int&y&=&fetch();&//&5
那么怎样让它自己动起来呢?我们知道一个程序直到它执行 HLT 指令才会停止。因此我们使用一个无限的循环持续直到当前指令为HLT。
//&INCLUDE&&stdbool.h&!
bool&running&=&
int&main()&{
&&&while&(running)&{
&&&&&&&int&x&=&fetch();
&&&&&&&if&(x&==&HLT)&running&=&
&&&&&&&ip++;
这工作的很好,但是有点凌乱。我们正在循环每一条指令,检查是否 HLT,如果是就停止循环,否则“吃掉”指令接着循环。
&翻译的不错哦!
判断一条指令
因此这就是我们虚拟机的主体,然而我们想要确实的评判每一条指令,并且使它更简洁一些。好的,这个简单的虚拟机,你可以写一个“巨大”的 switch 声明。让 switch 中的每一个 case 对应一条我们定义在枚举中的指令。这个 eval 函数将使用一个简单的指令的参数来判断。我们在函数中不会使用任何指令指针递增除非我们想操作数浪费操作数。
void&eval(int&instr)&{
&&&&switch&(instr)&{
&&&&&&&&case&HLT:
&&&&&&&&&&&&running&=&
&&&&&&&&&&&&
因此如果我们在回到主函数,就可以像这样使用我们的 eval 函数工作:
bool&running&=&
int&ip&=&0;
//&instruction&enum&here
//&eval&function&here
//&fetch&function&here
int&main()&{
&&&&while&(running)&{
&&&&&&&&eval(fetch());
&&&&&&&&ip++;&//&increment&the&ip&every&iteration
&翻译的不错哦!
很好,那会很完美的完成这个工作。现在,在我们加入其他指令之前,我们需要一个栈。幸运的是,栈是很容易实现的,我们仅仅需要使用一个数组而已。数组会被设置为合适的大小,这样它就能包含256个值了。我们也需要一个栈指针(常被缩写为sp)。这个指针会指向栈数组。
为了让我们对它有一个更加形象化的印象,让我们来看看这个用数组实现的栈吧:
[]&//&empty
PSH&5&//&put&5&on&**top**&of&the&stack
[]&//&empty
那么,在我们的程序里发生了什么呢?
我们首先把5压入了栈
然后压入6:
接着添加指令,取出这些值,把它们加在一起并把结果压入栈中:
//&pop&the&top&value,&store&it&in&a&variable&called&a
a&=&&//&a&contains&6
[5]&//&stack&contents
//&pop&the&top&value,&store&it&in&a&variable&called&b
b&=&&//&b&contains&5
[]&//&stack&contents
//&now&we&add&b&and&a.&Note&we&do&it&backwards,&in&addition
//&this&doesn't&matter,&but&in&other&potential&instructions
//&for&instance&divide&5&/&6&is&not&the&same&as&6&/&5
result&=&b&+&a;
push&result&//&push&the&result&to&the&stack
[11]&//&stack&contents
那么我们的栈指针在哪起作用呢?栈指针(或者说sp)一般是被设置为-1,这意味着这个指针是空的。请记住一个数组是从0开始的,如果没有初始化sp的值,那么他会被设置为C编译器放在那的一个随机值。
&翻译的不错哦!
如果我们将3个值压栈,那么sp将变成2。所以这个数组保存了三个值:
sp指向这里(sp&=&2)
&0&&1&&2&&-&数组下标
现在我们从栈上出栈一次,我们仅需要减小栈顶指针。比如我们接下来把9出栈,那么栈顶将变为5:
sp指向这里(sp&=&1)
&0&&1&&-&数组下标
所以,当我们想知道栈顶内容的时候,只需要查看sp的当前值。OK,你可能想知道栈是如何工作的,现在我们用C语言实现它。很简单,和ip一样,我们也应该定义一个sp变量,记得把它赋为-1!再定义一个名为stack的数组,代码如下:
int&ip&=&0;
int&sp&=&-1;
int&stack[256];&//&用数组或适合此处的其它结构
//&其它C代码
现在如果我们想入栈一个值,我们先增加栈顶指针,接着设置当前sp处的值(我们刚刚增加的)。注意:这两步的顺序很重要!
//&sp&=&-1
sp++;&//&sp&=&0
stack[sp]&=&5;&//&栈顶现在变为5
所以,在我们的执行函数eval()里,可以像这样实现push出栈指令:
void&eval(int&instr)&{
&&&&switch&(instr)&{
&&&&&&&&case&HLT:&{
&&&&&&&&&&&&running&=&
&&&&&&&&&&&&
&&&&&&&&case&PSH:&{
&&&&&&&&&&&&sp++;
&&&&&&&&&&&&stack[sp]&=&program[++ip];
&&&&&&&&&&&&
现在你看到,它和我们之前实现的eval()函数有一些不同。首先,我们把每个case语句块放到大括号里。你可能不太了解这种用法,它可以让你在每条case的作用域里定义变量。虽然现在不需要定义变量,但将来会用到。并且它可以很容易得让所有的case语句块保持一致的风格。
&翻译的不错哦!
其次是神奇的表达式program[++ip]。它做了什么?呃,我们的程序存储在一个数组里,PSH指令需要获得一个操作数。操作数本质是一个参数,就像当你调用一个函数时,你可以给它传递一个参数。这种情况我们称作压栈数值5。我们可以通过增加指令指针(译者注:一般也叫做程序计数器)ip来获取操作数。当ip为0时,这意味着执行到了PSH指令,接下来我们希望取得下一条指令——即压栈的数值。这可以通过ip自增的方法实现(注意:增加ip的位置十分重要,我们希望在取得指令前自增,否则我们只是拿到了PSH指令),接下来需要跳到下一条指令否则会引发奇怪的错误。当然我们也可以把sp++简化到stack[++sp]里。
对于POP指令,实现非常简单。只需要减小栈顶指针,但是我一般希望能够在出栈的时候打印出栈值。
&翻译的不错哦!
我省略了实现其它指令的代码和swtich语句,仅列出POP指令的实现:
//&记得#include&&stdio.h&!
case&POP:&{
&&&&int&val_popped&=&stack[sp--];
&&&&printf("popped&%d\n",&val_popped);
现在,POP指令能够工作了!我们刚刚做的只是把栈顶放到变量val_popped里,接着栈顶指针减一。如果我们首先栈顶减一,那么将得到一些无效值,因为sp可能取值为0,那么我们可能把stack[-1]赋给val_popped,通常这不是一个好主意。
最后是ADD指令。这条指令可能要花费你一些脑细胞,同时这也是我们需要用大括号{}实现case语句内作用域的原因。
case&ADD:&{
&&&&//&首先我们出栈,把数值存入变量a
&&&&int&a&=&stack[sp--];
&&&&//&接着我们出栈,把数值存入变量b
&&&&//&接着两个变量相加,再把结果入栈
&&&&int&result&=&a&+&b;
&&&&sp++;&//&栈顶加1&**放在赋值之前**
&&&&stack[sp]&=&&//&设置栈顶值
&&&&//&完成!
&翻译的不错哦!
寄存器是虚拟机中的选配件,很容易实现。之前提到过我们可能需要六个寄存器:A,B,C,D,E和F。和实现指令集一样,我们也用一个枚举来实现它们。
typedef&enum&{
&&&A,&B,&C,&D,&E,&F,
&&&NUM_OF_REGISTERS
小技巧:枚举的最后放置了一个数&NUM_OF_REGISTERS。通过这个数可以获取寄存器的个数,即便你又添加了其它的寄存器。现在我们需要一个数组为寄存器存放数值:
int&registers[NUM_OF_REGISTERS];
接下来你可以读取寄存器内的值:
printf("%d\n",&registers[A]);&//&打印寄存器A的值
我没有在寄存器花太多心思,但你应该能够写出一些操作寄存器的指令。比如,如果你想实现任何分支跳转,可以通过把指令指针(译者注:或叫程序计数器)和/或栈顶指针存到寄存器里,或者通过实现分支指令。
前者实现起来相对快捷、简单。我们可以这样做,增加代表IP和SP的寄存器:
typedef&enum&{
&&&&A,&B,&C,&D,&E,&F,&PC,&SP,
&&&&NUM_OF_REGISTERS
现在我们需要实现代码来使用指令指针和栈顶指针。一个简单的办法——删掉上面定义的sp和ip变量,用宏定义实现它们:
#define&sp&(registers[SP])
#define&ip&(registers[IP])&&&译者注:此处应同Registers枚举中保持一致,IP应改为PC
这个修改恰到好处,你不需要重写很多代码,同时它工作的很好。
&翻译的不错哦!
如何实现分支指令?
我把问题留给你!记住指令指针(程序计数器)指向当前指令,并且其数值存储在一个寄存器里。所以你需要写一条指令设置寄存器的值,例如:SET REG value。接下来可以通过设置IP寄存器为某条指令的位置,进而跳转到这条指令。如果你想看一个更复杂的例子,请访问代码库,那里有一个递减某个值直到其为0的。
这里有几道题目,实现MOV指令:MOV REG_A, REG_B。换句话说,MOV指令把数值从REG_A移到REG_B。同样SET REG_A VALUE,会设置REG_A内容为VALUE。
你可以从github访问源码。如果你想更“高级”虚拟机是如何实现MOV和SET指令的,请浏览bettervm.c文件。你可以拿自己的实现和它作比较。如果你只想大体了解一下代码结构,请先浏览main.c。
好了!现在你拿到代码了。在根目录下运行make,它会自动编译,接下来运行./mac。
多谢阅读本文!
&翻译的不错哦!
感觉原文作者没什么文采。我开始有点生锈的脚本语言,只要他们大跌眼镜如雨后春笋般近来:)
今天,我认为这将是不错的一种脚本语言,说话无缝的C ++,也就是C ++类,并且,最重要的可以为C ++或DLL / .SO(其H) CodeGo.net,这样我可以链接成脚本定义的类的我的C ++程序和或
我知道我可以嵌入任何流行的脚本语言,如Lua中,ruby,python...但有一种“EVAL”函数,用于评估所提供的脚本代码。根据不同的情侣C ++和脚本语言,整合了脚本回调成C ++可能是或多或少容易写,但我还没有看到任何脚本语言,实际上写公开为一个独立的模块。 H和.so / dll文件到我的程序(可能沿着一个脚本语言,生成C ++代码的行)。
你知道有这样的工具/脚本语言?
在此先感谢。
帕金森病。我一直在想沿着维拉或Haskell的GHC的线条。他们生成C,而不是C ++的...
本文地址 :CodeGo.net/170714/
-------------------------------------------------------------------------------------------------------------------------
1. 该要求在这方面是:我怎么暴露我的C ++类,使他们能够从脚本中实例化?而答案往往是像
你问相反的问题,这听起来像你'重要的一点。 。这将是在这种情况下,你的C ++-,产生h文件,所以文件不会真的是一个脚本引擎,脚本引擎。
脚本引擎不喜欢的工作。你通过他们的脚本和回调 CodeGo.net,提供了一组可以从脚本中调用的函数,而引擎解释的脚本。
C ++类在Lua你
要生成tolua的++:
它需要一个清理头作为输入和输出,做的辛勤工作交流文件。方便,美观和愉快的事情。
Lua中的对象的C ++我会带写一个通用的代理对象一样(场,setField,callMethod,方法,字段)的方法。
如果你想有一个dll文件,你可以有。LUA作为一种资源(在Windows中,我不知道这可能是一个合适的等同于Linux)和你的DllMain初始化代理对象的Lua代码。
在C ++代码可以调用代理对象的Lua代码,与代理也许一些,使这项工作更容易。
你可以只代理对象的每LUA库你想写,只是改变提供给它的Lua代码。
下面的是更多的C ++面向集成比语言绑定:
ChaiScript-试图在一个小项目,有趣的,这个是用C ++的思想和工作方式只是其中一个头!不知道这是很好的一个大项目,但还没有看到,尝试有味道!
猎鹰-试图在一个大的项目,优秀的,它不是“一个包括嵌入”为ChaiScript但它是真正灵活,并且在C ++(仅适用于C ++代码的库),完全认为-我决定坚持下去我这需要大量的脚本灵活性(与ruby/ Python)的最大项目
AngelScript-没有尝试尚未
-没有尝试尚未
IO-没有尝试尚未
对你来说,如果你真的想要写你的脚本模块中的C ++,轻松地将其暴露给脚本语言,我会去与猎鹰。它完全建立在C ++中,所有的模块/库编写的方式。
这是稍微超出了我的专业领域,但我愿意adventure的downvotes。 :-)
增加:: Python的似乎是你在找什么。有点神奇的宏做了的东西,但它确实暴露Python类为C ++而干净。
我LikeMagic,对于IO语言C ++绑定库的作者。 (我不是木卫一的作者。)
我的一个明确的目标与LikeMagic和总C ++的互操作性,在两个方向。 LikeMagic将本土名帅木卫一类型,C ++类型(包括STL容器和木卫一的原生List类型之间的转换),它会代表在木卫一的C ++类,方法,字段和数组。你甚至可以通过木卫一代码块了木卫一它在C ++中的函数对象!
包装C ++类型了在木卫一脚本消费是简单,快捷,方便。访问脚本对象从C ++确实需要你描述的“EVAL”函数一样,但基于模板的类型转换和封送处理可以很容易地访问执行脚本字符串的结果。还有就是把木卫一块()对象为C ++仿函数的能力。
目前该项目仍处于早期阶段,但它已全面投入使用。我还需要做的事情像它的生成步骤和依赖,而且只能用gcc 4.4.1内置+(不是微软的Visual C ++)C ++0 x特性尚不支持MSVC。但是,它完全支持Linux和Windows,以及Mac移植计划。
现在,坏消息:。制作脚本生成h文件等,或从C ++调用的dll文件,这不仅需要(某种),但它也必须是一个。这是(在很多脚本语言,但大多数尤其是在IO)对象'和字段不为人所知,直到-在木卫一,方法甚至可以添加和删除从活动对象!起初我会说,张女士你问这个奇迹,如果你也许并不真正了解一个动态语言是什么。但我相信在设计的方式,你先试着想象这样的理想和最简单的方式,然后向后工作从有到什么是真正可能的。所以我承认从易用性的角度来看,你描述什么样的声音更容易
但是,尽管它的理想,只是勉强能够(使用脚本语言,它是不是很实用,所以我还是不确定,如果你问什么是你真正想要的。如果,H和。这样/。 dll文件从脚本JITTED,和脚本改变,你需要你的C ++程序采取变化的优势!不违反摆在首位的主要好处脚本?
如果接口定义的脚本没有发生变化,而你只是正在C ++包装的脚本函数很实用,唯一的办法是。你最终会拥有很多C ++的函数,如:
int get_foo() { return script.eval("get_foo()"); }
int get_bar() { return script.eval("get_bar()"); }
我承认,是从视图的包装函数的调用者的角度寻找更清洁的代码。但如果这是你想要的,为什么不反思在脚本语言中,并生成一个h文件关闭了脚本中存储的对象名单?这种反射可以在木卫一很容易做到。在一点上,我计划在OpenC ++源到源转换器结合起来,作为从LikeMagic一个可调用的库,你可以在C ++代码生成,而不是写出来的字符串。
为此,您可以用Lua中,但如果你有很多的类,你会希望像swig或tolua的++的工具生成的胶水代码给你。
没有这些工具将处理你的问题的一部分,这是有一个。h文件的背后隐藏的是一种脚本语言,并且让你的C ++代码调用脚本不知道是脚本。为了这一点,你必须做到以下几点:
写的胶水代码自己。 (对于Lua中,这是比较容易的,直到你进入类,于是它不是那么容易,这就是为什么像SWIG和tolua工具++存在。)
隐藏背后的接口类型的脚本解释器的全局状态。
假如你有每个正在使用多个脚本h文件,你必须决定在脚本语言中哪些相同的状态以及其中单独的脚本状态。 (什么你基本上是一个虚拟机的脚本语言,并且是(a)所有。h时VM和(b)各h文件都有自己单独的,孤立的虚拟机。其他的选择是
如果你决定这样做自己,写的胶水代码把Lua中的表转换为C ++类(让Lua代码看起来像C ++的程序的其余部分)是相当简单的。要在另一个方向上,在那里你包装你的C ++中的Lua(使C ++对象看起来像Lua中值的脚本)是在屁股大的痛苦。
不管你做什么,你的工作在你前面。
好问题,我经常想到这个问题我自己,但可惜没有简单的解决方案,这种事情。如果你是在Windows上(我想不会),那么你可以达到这样通过创建在C ++和VB(考虑到作为脚本语言)。会说话的发生通过COM接口,这是一种很好的方式,不同的语言之间进行互操作。持有为主.NET语言,可以相互之间互操作。
我也很渴望知道,如果这样的存在对于C ++,最好是开源的。
您可能检查到嵌入狡诈(一解释器)或V8引擎(谷歌的JavaScript解释器-使用-这是写在C ++)。
谷歌的V8引擎是用C ++,我期待你也许可以将它集成到一个项目中。他们谈论这样做,在这篇文章中。
本文标题 :脚本语言的C ++
本文地址 :CodeGo.net/170714/
Copyright (C) 2014 CodeGo.net 沪ICP备号 联&系& c&o&d&e&g&o &@&1&2&6&.&c&o&mGScript 脚本解释器再次更新完成,并散200分
[问题点数:200分,结帖人zhouzhipen]
GScript 脚本解释器再次更新完成,并散200分
[问题点数:200分,结帖人zhouzhipen]
只显示楼主
取消只显示楼主
本帖子已过去太久远了,不再提供回复功能。脚本引擎解释器执行效率评测 - Riceball LEE - 博客园
解释编译原理
posts - 42, comments - 217, trackbacks - 12, articles - 1
初步评测世界上最快的脚本引擎的解释器执行效率 ——& EUPHORIA, Lua,以及俺写的TurboScript解释器核心原型【也许会开源】。衡量脚本引擎效率,对于非JITter执行的解释型脚本来说,解释器本身的执行效率就是核心的核心,关键之关键了。总所周知,解释的速度慢于编译的原因就是,因为多了一道工序,指令必须经过解释才能执行。要想提高解释器的性能,自然是要尽量缩短解释的过程。这个解释的过程就是空耗了,也就是我们平时说的做的无用功。因此,这里将以编译执行的速度视为空耗为0,以它作为基准,对解释器的效率进行比较。用什么指令来测试呢,就挑一个最简单的整数加法运算,两数相加,绝大多数脚本都该支持的。由于机器码执行速度太快,如果只重复执行几次,就算是用高性能计数器也无法计时,所以这里将重复执行2017次加法指令,以300累加,结果为300*2017。测试全部采用 QueryPerformanceCounter 高精度计数。在解释器直接执行VM指令的效率之后,我们还想测试下解释器做过程调用的效率,毕竟在实际应用中大部分都是执行的过程调用(虚方法这些更是过程的升华,当然由于虚方法要查表所以又要比调用普通过程慢)。就将加法指令放到子过程中,就可以测试了。其实如果脚本引擎都能实现一个空指令那么就能精确的看出解释器的空耗了!就不比这样麻烦了。== 速度效率图 ==该图以x86汇编为基准计算。从图中我们可以看出,EUPHORIA解释器在直接指令执行这块运行性能最高,达到了x86指令执行速度的73%,也就是说它的空耗只有27%。实际上 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):asm& mov EAX, a& add EAX, b--计时开始& 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+bend 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+bend function--计时开始count = 300count = iAdd(count, 300)..... --- 总计 2017 次count = iAdd(count, 300)--计时结束&/code&&结果运行时间:522;是x86执行速度的24%;空耗为76%=== TurboScript 子过程近调用效率测试 ===&code&: Add +; //定义子过程。//--计时开始300300 Add..... (总计 2017 次)300 Add//--计时结束&/code&结果运行时间:169;是x86执行速度的75%;空耗为25%最后附上:

我要回帖

更多关于 msvbvm50.dll是什么 的文章

 

随机推荐