精华内容
下载资源
问答
  • Google V8 引擎使用 C++ 代码编写,实现了 ECMAScript 规范的第五版,可以运行在所有的主流 操作系统中,甚至可以运行在移动终端 ( 基于 ARM 的处理器,如 HTC G7 等 )。V8 最早被开发用以嵌入到 Google 的开源...

    Google V8 引擎使用 C++ 代码编写,实现了 ECMAScript 规范的第五版,可以运行在所有的主流

    操作系统中,甚至可以运行在移动终端 ( 基于 ARM 的处理器,如 HTC G7 等 )。V8 最早被开发用以嵌入到 Google 的开源浏览器 Chrome 中,但是 V8 是一个可以独立的模块,完全可以嵌入您自己的应用,著名的 Node.js( 一个异步的服务器框架,可以在服务端使用 JavaScript 写出高效的网络服务器 ) 就是基于 V8 引擎的。

    和其他 JavaScript 引擎一样,V8 会编译 / 执行 JavaScript 代码,管理内存,负责垃圾回收,与宿主语言的交互等。V8 的垃圾回收器采用了众多技术,使得其运行效率大大提高。通过暴露宿主对象 ( 变量,函数等 ) 到 JavaScript,JavaScript 可以访问宿主环境中的对象,并在脚本中完成对宿主对象的操作。

     

    V8 引擎基本概念


    图 1. V8 引擎基本概念关系图 ( 根据 Google V8 官方文档 )
     

    handle

    handle 是指向对象的指针,在 V8 中,所有的对象都通过 handle 来引用,handle 主要用于 V8 的垃圾回收机制。

    在 V8 中,handle 分为两种:持久化 (Persistent)handle 和本地 (Local)handle,持久化 handle 存放在堆上,而本地 handle 存放在栈上。这个与 C/C++ 中的堆和栈的意义相同 ( 简而言之,堆上的空间需要开发人员自己申请,使用完成之后显式的释放;而栈上的为自动变量,在退出函数 / 方法之后自动被释放 )。持久化 handle 与本地 handle 都是 Handle 的子类。在 V8 中,所有数据访问均需要通过 handle。需要注意的是,使用持久化 handle 之后,需要显式的调用 Dispose() 来通知垃圾回收机制。

    作用域 (scope)

    scope 是 handle 的集合,可以包含若干个 handle,这样就无需将每个 handle 逐次释放,而是直接释放整个 scope。

    在使用本地 handle 时,需要声明一个 HandleScope 的实例,scope 是 handle 的容器,使用 scope,则无需依次释放 handle。

     HandleScope handle_scope; 
     Local<ObjectTemplate> temp; 
    

     

    上下文 (context)

    context 是一个执行器环境,使用 context 可以将相互分离的 JavaScript 脚本在同一个 V8 实例中运行,而互不干涉。在运行 JavaScript 脚本是,需要显式的指定 context 对象。

    数据及模板

    由于 C++ 原生数据类型与 JavaScript 中数据类型有很大差异,因此 V8 提供了 Data 类,从 JavaScript 到 C++,从 C++ 到 JavaScrpt 都会用到这个类及其子类,比如:

     Handle<Value> Add(const Arguments& args){ 
    	 int a = args[0]->Uint32Value(); 
    	 int b = args[1]->Uint32Value(); 
    
    	 return Integer::New(a+b); 
     } 
    

     

    Integer 即为 Data 的一个子类。

    V8 中,有两个模板 (Template) 类 ( 并非 C++ 中的模板类 ):对象模板 (ObjectTempalte) 和函数模板 (FunctionTemplate),这两个模板类用以定义 JavaScript 对象和 JavaScript 函数。我们在后续的小节部分将会接触到模板类的实例。通过使用 ObjectTemplate,可以将 C++ 中的对象暴露给脚本环境,类似的,FunctionTemplate 用以将 C++ 函数暴露给脚本环境,以供脚本使用。

    初始化 context 是使用 V8 引擎所必需的过程,代码非常简单:

     Persistent<Context> context = Context::New(); 
    

     


    V8 引擎使用示例

    有了上面所述的基本概念之后,我们来看一下一个使用 V8 引擎的应用程序的基本流程:

    1. 创建 HandleScope 实例
    2. 创建一个持久化的 Context
    3. 进入 Context
    4. 创建脚本字符串
    5. 创建 Script 对象,通过 Script::Compile()
    6. 执行脚本对象的 Run 方法
    7. 获取 / 处理结果
    8. 显式的调用 Context 的 Dispose 方法

    基本代码模板


    清单 1. 代码模块

    				
     #include <v8.h> 
    
     using namespace v8; 
    
     int main(int argc, char *argv[]) { 
    	 // 创建一个句柄作用域 ( 在栈上 ) 
    	 HandleScope handle_scope; 
    
    	 // 创建一个新的上下文对象
    	 Persistent<Context> context = Context::New(); 
    
    	 // 进入上一步创建的上下文,用于编译执行 helloworld 
    	 Context::Scope context_scope(context); 
    
    	 // 创建一个字符串对象,值为'Hello, Wrold!', 字符串对象被 JS 引擎
    	 // 求值后,结果为'Hello, World!'
    	 Handle<String> source = String::New("'Hello' + ', World!'"); 
    
    	 // 编译字符串对象为脚本对象
    	 Handle<Script> script = Script::Compile(source); 
    
    	 // 执行脚本,获取结果
    	 Handle <Value> result = script->Run(); 
    
    	 // 释放上下文资源
    	 context.Dispose(); 
    
    	 // 转换结果为字符串
    	 String::AsciiValue ascii(result); 
    
    	 printf("%s\n", *ascii); 
    
    	 return 0; 
     } 
    

     

    以上代码为一个使用 V8 引擎来运行脚本的基本模板,可以看到,开发人员可以很容易的在自己的代码中嵌入 V8 来处理 JavaScript 脚本。我们在下面小节中详细讨论如何在脚本中访问 C++ 资源。

    使用 C++ 变量

    在 JavaScript 与 V8 间共享变量事实上是非常容易的,基本模板如下:


    清单 2. 共享变量

    				
     static type xxx; 
    
     static Handle<Value> xxxGetter( 
    	 Local<String> name, 
    	 const AccessorInfo& info){ 
    
    	 //code about get xxx 
     } 
    
     static void xxxSetter( 
    	 Local<String> name, 
    	 Local<Value> value, 
    	 const AccessorInfo& info){ 
    
    	 //code about set xxx 
     } 
    

     

    首先在 C++ 中定义数据,并以约定的方式定义 getter/setter 函数,然后需要将 getter/setter 通过下列机制公开给脚本:

     global->SetAccessor(String::New("xxx"), xxxGetter, xxxSetter); 
    

     

    其中,global 对象为一个全局对象的模板:

     Handle<ObjectTemplate> global = ObjectTemplate::New(); 
    

     

    下面我们来看一个实例:


    清单 3. 实例 1

    				
     static char sname[512] = {0}; 
    
     static Handle<Value> NameGetter(Local<String> name, 
    		 const AccessorInfo& info) { 
    	 return String::New((char*)&sname,strlen((char*)&sname)); 
     } 
    
     static void NameSetter(Local<String> name, 
    		 Local<Value> value, 
    		 const AccessorInfo& info) { 
    	 Local<String> str = value->ToString(); 
    	 str->WriteAscii((char*)&sname); 
     } 
    

     

    定义了 NameGetter, NameSetter 之后,在 main 函数中,将其注册在 global 上:

     // Create a template for the global object. 
     Handle<ObjectTemplate> global = ObjectTemplate::New(); 
    
     //public the name variable to script 
     global->SetAccessor(String::New("name"), NameGetter, NameSetter); 
    
    在 C++ 中,将 sname 的值设置为”cpp”:
    
     //set sname to "cpp" in cpp program 
     strncpy(sname, "cpp", sizeof(sname)); 
    
    然后在 JavaScript 中访问该变量,并修改:
    
     print(name); 
    
     //set the variable `name` to "js"
     name='js'; 
     print(name); 
    

     

    运行结果如下:

     cpp 
     js 
    

     

    运行脚本,第一个 print 调用会打印在 C++ 代码中设置的 name 变量的值:cpp,然后我们在脚本中修改 name 值为:js,再次调用 print 函数则打印出设置后的值:js。

    调用 C++ 函数

    在 JavaScript 中调用 C++ 函数是脚本化最常见的方式,通过使用 C++ 函数,可以极大程度的增强 JavaScript 脚本的能力,如文件读写,网络 / 数据库访问,图形 / 图像处理等等,而在 V8 中,调用 C++ 函数也非常的方便。

    在 C++ 代码中,定义以下原型的函数:

     Handle<Value> function(constArguments& args){ 
    	 //return something 
     } 
    

     

    然后,再将其公开给脚本:

     global->Set(String::New("function"),FunctionTemplate::New(function)); 
    

     

    同样,我们来看两个示例:


    清单 4. 实例 2

    				
     Handle<Value> Add(const Arguments& args){ 
    	 int a = args[0]->Uint32Value(); 
    	 int b = args[1]->Uint32Value(); 
    
    	 return Integer::New(a+b); 
     } 
    
     Handle<Value> Print(const Arguments& args) { 
    	 bool first = true; 
    	 for (int i = 0; i < args.Length(); i++) { 
    		 HandleScope handle_scope; 
    		 if (first) { 
    			 first = false; 
    		 } else { 
    			 printf(" "); 
    		 } 
    		 String::Utf8Value str(args[i]); 
    		 const char* cstr = ToCString(str); 
    		 printf("%s", cstr); 
    	 } 
    	 printf("\n"); 
    	 fflush(stdout); 
    	 return Undefined(); 
     } 
    

     

    函数 Add 将两个参数相加,并返回和。函数 Print 接受任意多个参数,然后将参数转换为字符串输出,最后输出换行。

     global->Set(String::New("print"), FunctionTemplate::New(Print)); 
     global->Set(String::New("add"), FunctionTemplate::New(Add)); 
    

     

    我们定义以下脚本:

     var x = (function(a, b){ 
    	 return a + b; 	
     })(12, 7); 
    
     print(x); 
    
     //invoke function add defined in cpp 
     var y = add(43, 9); 
     print(y); 
    

     

    运行结果如下:

     19 
     52 
    

     

    使用 C++ 类

    如果从面向对象的视角来分析,最合理的方式是将 C++ 类公开给 JavaScript,这样可以将 JavaScript 内置的对象数量大大增加,从而尽可能少的使用宿主语言,而更大的利用动态语言的灵活性和扩展性。事实上,C++ 语言概念众多,内容繁复,学习曲线较 JavaScript 远为陡峭。最好的应用场景是:既有脚本语言的灵活性,又有 C/C++ 等系统语言的效率。使用 V8 引擎,可以很方便的将 C++ 类”包装”成可供 JavaScript 使用的资源。

    我们这里举一个较为简单的例子,定义一个 Person 类,然后将这个类包装并暴露给 JavaScript 脚本,在脚本中新建 Person 类的对象,使用 Person 对象的方法。

    首先,我们在 C++ 中定义好类 Person:


    清单 5. 定义类

    				
     class Person { 
     private: 
    	 unsigned int age; 
    	 char name[512]; 
    
     public: 
    	 Person(unsigned int age, char *name) { 
    		 this->age = age; 
    		 strncpy(this->name, name, sizeof(this->name)); 
    	 } 
    
    	 unsigned int getAge() { 
    		 return this->age; 
    	 } 
    
    	 void setAge(unsigned int nage) { 
    		 this->age = nage; 
    	 } 
    
    	 char *getName() { 
    		 return this->name; 
    	 } 
    
    	 void setName(char *nname) { 
    		 strncpy(this->name, nname, sizeof(this->name)); 
    	 } 
     }; 
    

     

    Person 类的结构很简单,只包含两个字段 age 和 name,并定义了各自的 getter/setter. 然后我们来定义构造器的包装:

     Handle<Value> PersonConstructor(const Arguments& args){ 
    	 Handle<Object> object = args.This(); 
    	 HandleScope handle_scope; 
    	 int age = args[0]->Uint32Value(); 
    
    	 String::Utf8Value str(args[1]); 
    	 char* name = ToCString(str); 
    
    	 Person *person = new Person(age, name); 
    	 object->SetInternalField(0, External::New(person)); 
    	 return object; 
     } 
    

     

    从函数原型上可以看出,构造器的包装与上一小节中,函数的包装是一致的,因为构造函数在 V8 看来,也是一个函数。需要注意的是,从 args 中获取参数并转换为合适的类型之后,我们根据此参数来调用 Person 类实际的构造函数,并将其设置在 object 的内部字段中。紧接着,我们需要包装 Person 类的 getter/setter:

     Handle<Value> PersonGetAge(const Arguments& args){ 
    	 Local<Object> self = args.Holder(); 
    	 Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); 
    
    	 void *ptr = wrap->Value(); 
    
    	 return Integer::New(static_cast<Person*>(ptr)->getAge()); 
     } 
    
     Handle<Value> PersonSetAge(const Arguments& args) 
     { 
    	 Local<Object> self = args.Holder(); 
    	 Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); 
    
    	 void* ptr = wrap->Value(); 
    
    	 static_cast<Person*>(ptr)->setAge(args[0]->Uint32Value()); 
    	 return Undefined(); 
     } 
    

     

    而 getName 和 setName 的与上例类似。在对函数包装完成之后,需要将 Person 类暴露给脚本环境:

    首先,创建一个新的函数模板,将其与字符串”Person”绑定,并放入 global:

     Handle<FunctionTemplate> person_template = FunctionTemplate::New(PersonConstructor); 
     person_template->SetClassName(String::New("Person")); 
     global->Set(String::New("Person"), person_template); 
    

     

    然后定义原型模板:

     Handle<ObjectTemplate> person_proto = person_template->PrototypeTemplate(); 
    
     person_proto->Set("getAge", FunctionTemplate::New(PersonGetAge)); 
     person_proto->Set("setAge", FunctionTemplate::New(PersonSetAge)); 
    
     person_proto->Set("getName", FunctionTemplate::New(PersonGetName)); 
     person_proto->Set("setName", FunctionTemplate::New(PersonSetName)); 
    

     

    最后设置实例模板:

     Handle<ObjectTemplate> person_inst = person_template->InstanceTemplate(); 
     person_inst->SetInternalFieldCount(1); 
    

     

    随后,创建一个用以测试的脚本:

     //global function to print out detail info of person 
     function printPerson(person){ 
        print(person.getAge()+":"+person.getName()); 
     } 
    
     //new a person object 
     var person = new Person(26, "juntao"); 
    
     //print it out 
     printPerson(person); 
    
     //set new value 
     person.setAge(28); 
     person.setName("juntao.qiu"); 
    
     //print it out 
     printPerson(person); 
    

     

    运行得到以下结果:

     26:juntao 
     28:juntao.qiu 

    简单示例

    在这一小节中,我们将编写一个简单的桌面计算器:表达式求值部分通过 V8 引擎来进行,而流程控制部分则放在 C++ 代码中,这样可以将表达式解析等复杂细节绕开。同时,我们还得到了一个额外的好处,用户在脚本中可以自定义函数,从而可以在计算器中定义自己的运算规则。

    桌上计算器

    计算器程序首先进入一个 MainLoop,从标准输入读取一行命令,然后调用 V8 引擎去求值,然后将结果打印到控制台,然后再进入循环:


    清单 6. 桌面计算器示例

    				
     void MainLoop(Handle<Context> context) { 
    	 while(true) { 
    		 char buffer[1024] = {0}; 
    		 printf("$ "); 
    		 char *str = fgets(buffer, sizeof(buffer), stdin); 
    		 if(str == NULL) { 
    			 break; 
    		 } 
    		 HandleScope handle_scope; 
    		 ExecuteString(String::New(str), String::New("calc"), true); 
    	 } 
     } 
    

     

    在 main 函数中设置全局对象,创建上下文对象,并进入 MainLoop:

     int main(int argc, char *argv[]){ 
    	 HandleScope handle_scope; 
    
    	 // Create a template for the global object. 
    	 Handle<ObjectTemplate> global = ObjectTemplate::New(); 
    
    	 // Expose the local functions to script 
    	 global->Set(String::New("load"), FunctionTemplate::New(Load)); 
    	 global->Set(String::New("print"), FunctionTemplate::New(Print)); 
    	 global->Set(String::New("quit"), FunctionTemplate::New(Quit)); 
    
    	 // Create a new execution environment containing the built-in 
    	 // functions 
    	 Handle<Context> context = Context::New(NULL, global); 
    
    	 // Enter the newly created execution environment. 
    	 Context::Scope context_scope(context); 
    
    	 // Enter main loop 
    	 MainLoop(context); 
    
    	 V8::Dispose(); 
    
    
    	 return 0; 
     } 
    

     

    在 main 函数中,为脚本提供了三个函数,load 函数用以将用户指定的脚本加载进来,并放入全局的上下文中一边引用,print 函数用以打印结果,而 quit 提供用户退出计算器的功能。

    测试一下:

     $ 1+2 
     3 
    
     $ (10+3)/(9.0-5) 
     3.25 
    
     $ typeof print 
     function 
    
     $ typeof non 
     undefined 
    
     // 自定义函数
     $ function add(a, b){return a+b;} 
     $ add(999, 2323) 
     3322 
    
     // 查看 print 标识符的内容
     $ print 
     function print() { [native code] } 
    

     

    load 函数提供了用户自定义函数的功能,将脚本文件作为一个字符串加载到内存,然后对该字符串编译,求值,并将处理过的脚本对象放入当前 context 中,以便用户使用。

     Handle<Value> Load(const Arguments& args){ 
    	 if(args.Length() != 1){ 
    		 return Undefined(); 
    	 } 
    
    	 HandleScope handle_scope; 
    	 String::Utf8Value file(args[0]); 
    
    	 Handle<String> source = ReadFile(*file); 
    	 ExecuteString(source, String::New(*file), false); 
    
    	 return Undefined(); 
     } 
    
    而 ExecuteString 函数,负责将字符串编译运行:
    
     bool ExecuteString(Handle<String> source, 
    				   Handle<Value> name, 
    				   bool print_result) 
     { 
    	 HandleScope handle_scope; 
    	 TryCatch try_catch; 
    	 Handle<Script> script = Script::Compile(source, name); 
    	 if (script.IsEmpty()) { 
    		 return false; 
    	 } else { 
    		 Handle<Value> result = script->Run(); 
    		 if (result.IsEmpty()) { 
    			 return false; 
    		 } else { 
    			 if (print_result && !result->IsUndefined()) { 
    				 String::Utf8Value str(result); 
    				 const char* cstr = ToCString(str); 
    				 printf("%s\n", cstr); 
    			 } 
    			 return true; 
    		 } 
    	 } 
     } 
    

     

    将下列内容存入一个文本文件,并命令为 calc.js:

     function sum(){ 
    	 var s = 0; 
    	 for(var i = 0; i < arguments.length; i++){ 
    		 s += arguments[i]; 
    	 } 
    	 return s; 
     } 
    
     function avg(){ 
    	 var args = arguments; 
    	 var count = args.length; 
    	 var sum = 0; 
    	 for(var i = 0; i < count; i++){ 
    		 sum += args[i]; 
    	 } 
    	 return sum/count; 
     } 

     

    展开全文
  • Nodejs与V8引擎原理

    千次阅读 2018-10-11 15:19:48
    对于了解Node的开发人员,我们都知道Node是基于Chrome V8引擎开发的能使JavaScript在服务器端运行的运行时环境(runtimeenvironment)。一方面,它提供了多种可调用的API,如读写文件、网络请求、系统信息等。另一...

    对于了解Node的开发人员,我们都知道Node是基于Chrome V8引擎开发的能使JavaScript在服务器端运行的运行时环境(runtime environment)。一方面,它提供了多种可调用的API,如读写文件、网络请求、系统信息等。另一方面,因为CPU执行的是机器码,它还负责将JavaScript代码解释成机器指令序列执行,这部分工作是由V8引擎完成。

    Motivation

    JavaScript 是一款拥有「自动垃圾回收」功能的编程语言。

    市面上具有这样功能的语言,一般都是拥有相对应的虚拟机的,像 JavaJVM ,C#CLR ,PHPZend

    虚拟机一般实现了代码解析,内存的管理、布局、垃圾回收等功能。

    不像C/C++这种没有虚拟机的语言,它们需要手动管理内存。

    C/C++语言编译后的文件,是可以直接运行的。

    我认为学习一门开发语言,除了知道一些语法上的使用,各种API的调用以外。学习相应的虚拟机也是很有必要的。而 JavaScript 由于其特殊的历史原因,并不是只有 V8 一个引擎。但是目前 V8 它是业界最优秀的 JavaScript 引擎,也就成为了一个学习样本。

    如今的 JavaScript 不仅仅是用在浏览器端了,也因为 NodeJS 的关系得以在服务器端运行。和浏览器端不同的地方在于服务器端对资源的敏感性是很高的。当业务规模大了,并发量上来了,一些很细小的问题会放大。这时候一些小小的内存泄漏,都会酿造灾难。

    所以作为一个 JavaScript 开发者,搞清楚从敲入 console.log('hello world') ,直到后面交由CPU执行的中间过程是很重要的。

    这也对如何用 JavaScript 这门松散的语言编写出高质量的代码是具有指导作用的。

    想真正做到 JavaScript 全栈,路漫漫其修远兮。

    NodeJS 概述

    根据百度百科解释,Node.js是一套用来编写高性能网络服务器的JavaScript工具包。Node.js是一个可以快速构建网络服务及应用的平台,该平台的构建是基于Chrome's JavaScript runtime,也就是说,实际上它是对GoogleV8引擎(应用于Google Chrome浏览器)进行了封装。V8引 擎执行Javascript的速度非常快,性能非常好。

    NodeJS并不是提供简单的封装,然后提供API调用,如果是这样的话那么它就不会有现在这么火了。Node对一些特殊用例进行了优化,提供了替代的API,使得V8在非浏览器环境下运行得更好。例如,在服务器环境中,处理二进制数据通常是必不可少的,但Javascript对此支持不足,因此,V8.Node增加了Buffer类,方便并且高效地 处理二进制数据。因此,Node不仅仅简单的使用了V8,还对其进行了优化,使其在各环境下更加给力。

    即时编译JIT 概述

    V8采用即时编译技术(JIT),直接将JavaScript代码编译成本地平台的机器码。宏观上看,其步骤为JavaScript源码—>抽象语法树—>本地机器码,并且后一个步骤只依赖前一个步骤。这与其他解释器不同,例如Java语言需要先将源码编译成字节码,然后给JVM解释执行,JVM根据优化策略,运行过程中有选择地将一部分字节码编译成本地机器码。V8不生成中间代码,一步到位,编译成机器码,CPU就开始执行了。比起生成中间码解释执行的方式,V8的策略省去了一个步骤,程序会更早地开始运行。并且执行编译好的机器指令,也比解释执行中间码的速度更快。不足的是,缺少字节码这个中间表示,使得代码优化变得更困难。

    V8 概述

    V8 作为一个 JavaScript 引擎,最初是服役于 Google Chrome 浏览器的。它随着 Chrome 的第一版发布而发布以及开源。现在它除了 Chrome 浏览器,已经有很多其他的使用者了。诸如 NodeJSMongoDBCouchDB 等。

    JavaScript 作为 Prototype-Based Language , 基于它使用 Prototype 继承的特征,V8 使用了直译的方式,即把 JavaScript 代码直接编译成机器码( Machine Code, 有些地方也叫 Native Code ),然后直接交由硬件执行。

    与传统的「编译-解析-执行」的流程不同,V8 处理 JavaScript,并没有二进制码或其他的中间码。

    简单来说,V8主要工作就是:「把 JavaScript 直译成机器码,然后运行」

    但这中间,往往是一个复杂的过程,它需要处理很多的难题,诸如:

    编译优化
    
    内存管理
    
    垃圾回收
    
    

    V8 In NodeJS/NodeJS源码小览

    NodeJS,是怎么引入V8的?

    我们关注 Node的源码 目录:

    .
    ├── ...
    ├── deps
    │   ├── ...
    │   ├── v8
    │   ├── ...
    ├── ...
    ├── lib
    │   ├── ...
    │   ├── buffer.js
    │   ├── child_process.js
    │   ├── console.js
    │   ├── ...
    ├── node -> out/Release/node
    ├── ...
    ├── out
    │   ├── ...
    │   ├── Release
    |         ├── node
    |         ├── node.d
    |         ├── obj
    |             └── gen
    |                 ├── ...
    |                 ├── node_natives.h
    |                 ├── ...
    │   ├── ...
    ├── src
    │   ├── ...
    │   ├── debug-agent.cc
    │   ├── debug-agent.h
    │   ├── env-inl.h
    │   ├── env.cc
    │   ├── ...
    ├── 
    ...
    

    需要关注的几个目录和文件:

    /deps/v8 :这里是V8源码所在文件夹,你会发现里面的目录结构跟 V8源码 十分相似。NodeJS除了移植V8源码,还在增添了一些内容。

    /src :由C/C++编写的核心模块所在文件夹,由C/C++编写的这部分模块被称为「Builtin Module」

    /lib :由JavaScript编写的核心模块所在文件夹,这部分被称为「Native Code」,在编译Node源码的时候,会采用V8附带的 js2c.py 工具,把所有内置的JavaScript代码转换成C++里面的数组,生成 out/Release/obj/gen/node_natives.h 文件。有些 Native Module 需要借助于 Builtin Module 实现背后的功能。

    /out :该目录是Node源码编译(命令行运行 make )后生成的目录,里面包含了Node的可执行文件。当在命令行中键入 node xxx.js ,实际就是运行了 out/Release/node 文件。

    来张图说明一下V8在Node运行时的整体过程。

     

    Node在启动的时候,就已经把 Native Module,Builtin Module 加载到内存里面了。后来的 JavaScript 代码,就需要通过 V8 进行动态编译解析运行。

     

    转自:https://segmentfault.com/a/1190000014722508?utm_source=index-hottest

    展开全文
  • V8 引擎如何执行JS,之前看过 Webkit 技术内幕,也只是走马观花。并没有深入理解,突然看到这篇文章,翻译之How does the Google V8 engine work? Google V8 引擎是如何工作的?这是一个非常好的问题,这里有少许...

    V8 引擎如何执行JS,之前看过 Webkit 技术内幕,也只是走马观花。并没有深入理解,突然看到这篇文章,翻译之How does the Google V8 engine work?

    Google V8 引擎是如何工作的?这是一个非常好的问题,这里有少许流出的官方文档来讲解,到底 V8 内部都做了什么。我会把我知道的东西分享给你(你需要自己猜,哪部分我给拿掉了),还有很多有用的地址去帮助你明白这些内容。

    V8 有两个编译器:

    • 一个非常简单并且非常快的编译器用于将 js 编译成简单但是很慢的机械码,叫做 full-codegen
    • 另一个是非常复杂的实时优化编译器,编译高性能的可执行代码,叫做 Crankshaft

    V8 里面也使用了一些线程:

    • 主线程,做你希望它做的事情:加载你的代码,编译它们,执行他们。
    • 有一个独立的编译线程,当主线程执行的时候,它去优化代码。
    • 一个 profiler 线程(不知道还有没有了,但是会有一个同样职责的线程存在),用于发现执行过程中哪个方法耗费了大量时间,这样 Crankshaft 就可以优化这些代码。
    • 一些用于 GC 处理的线程(译者注:这里是一些用于 GC 的线程,不止一个线程用于垃圾回收)。

    最开始执行你的代码的时候,V8 开始使用 full-codegenfull-codegen 直接将 JS 代码解释成机械码,没有做任何转化。这可以让 V8 快速执行机械码。注意,V8 并不使用中间字节码,因此也就不再需要转译处理。

    当你的代码被执行的时候,profiler 线程有足够的数据来找出哪些方法需要被优化。我不确定 V8 如何选择使用哪个线程做的优化,简单起见,我们就认为它用的主线程吧。

    主线程通过停止正在执行的代码(也许就在需要优化的方法这里),开始使用 Crankshaft 进行优化。JS 代码首先会被编译成一种叫做 Hydrogen 的高级描述,它是控制流图的静态单赋值表示。大多数优化都是在这个级别完成的。

    首先,对尽可能多的代码进行内联,这使得优化变得更有意义。然后进行类型转化。这个优化移除了打包和拆包的处理,可以认为是执行了很多指令。这意味着,如果你的代码在操作整数,并且没有做类型转换,比如转换成 stringdouble 这些,那么它会跑的很快。内联缓存会在这个阶段起到非常重要的作用,提供了类型判断。就像你猜到的那样,我们需要小心类型转换:如果你希望一个变量是一个整数,但是过一会却被修改成了其它类型,那么你的假设就失败了,那么一次重新编译就在所难免了。还有其它的优化,比如 loop-invariant code motion(译者注:貌似是讲将循环内不变化的代码移到外面,减少每一次循环执行的代码数量),移除死代码(译者注:不被执行的代码也要移除,否则 V8 始终都要要对这些代码处理的,带来了额外负担)等。

    一旦 Hydrogen graph 被优化,Crankshaft 会降低它到一个低级别的描述,叫做 Lithium。大部分的 Lithium 执行于特定架构。分配寄存器就是在这个级别进行的。

    最终,Lithium 被编译成机械码。然后一些叫做 OSR (on-stack replacement)的事情就发生了。记住,在我们开始编译和优化运行耗时较长的方法之前,我们喜欢先执行它。我们不要忘记我们刚刚放慢了执行,然后开始执行优化后的代码。相反,我们将要转换所有的上下文,因此我们才能在执行的中间过程中选择执行优化后的代码。我让你们感到复杂,提醒一下,其它的优化中,我们内联了一些东西。V8 并不是唯一一个这么做的虚拟机,但是我发现一些比较疯狂的地方。有一些保护机制叫 -- 去优化,在做相反的事情,并且会在一些假设的特定情况下反转一些优化后的代码。

    还有。就是编译/执行部分。我忘了提到 GC,不过这很短,因为我对它不太了解。

    对于垃圾收集来说,V8 使用了传统的方法,采用标记计数的方式来进行垃圾收集。标记阶段必须停止 JavaScript 执行。为了控制 GC 成本,使执行更加稳定,V8 采用增量标记。这就是说,他们不是在堆中试图标记每一个可能的对象,而是处理一部分堆,然后恢复正常的执行。下一个 GC 停止执行代码的时候处理之前未处理的堆。这允许非常短的暂停。如前所述,扫描阶段由单独的线程来处理。

    posts tagged "v8"

    Posts Tagged 'v8'

    V8 Resources

    thlorenz/v8-perf

    docs.google.com/document...

    floitsch.blogspot.de/2012/04/opt…

    还有这里源码

    展开全文
  • V8引擎

    2019-07-11 14:44:42
    V8引擎什么是V8引擎V8引擎工作原理 什么是V8引擎 https://www.jianshu.com/p/81f6ded64ab2 开源 Google开发 c++编写 同类引擎 Rhin、SpiderMonkey、JavaScriptCore 、KJS、Chakra、Nashorn、JerryScript… 被设计...

    什么是V8引擎

    https://www.jianshu.com/p/81f6ded64ab2

    1. 开源
    2. Google开发
    3. c++编写
    4. 同类引擎 Rhin、SpiderMonkey、JavaScriptCore 、KJS、Chakra、Nashorn、JerryScript
    5. 被设计用来提高网页浏览器内部JavaScript执行的性能

    V8引擎工作原理

    https://juejin.im/post/5b5014565188251ad06b6091

    V8引擎有2个编译器

    1. fullCodegen 简单而迅速的编译器,作用 将JS变成简单而效率低的机器码
    2. Crankshaft 复杂的,实时优化RTO)的编译器,编译高性能可执行代码

    V8使用的线程

    1. 主线程 加载代码,编译代码,执行代码
    2. 独立的编译线程,在主线程执行代码时优化代码
    3. 一个分析 线程,用于发现执行过程中哪个方法耗费了大量时间,这样 Crankshaft 就可以优化这些代码。
    4. 一些垃圾回收的线程。

    V8工作的流程
    1 代码开始时
    V8使用fullCodegen 直接将JS代码解释成机器码,没有任何转换。
    2.代码被执行时
    分析线程找出要优化的代码,使用XXX线程用作优化线程,XXX线程通过停止正在执行的代码开始使用 Crankshaft 进行优化。JS 代码首先会被编译成一种叫做 Hydrogen 的高级描述,它是控制流图的静态单赋值表示。大多数优化都是在这个级别完成的。(一脸懵逼)

    V8引擎优化
    首先,对尽可能多的代码进行内联,这使得优化变得更有意义。然后进行类型转化。这个优化移除了打包和拆包的处理,可以认为是执行了很多指令。这意味着,如果你的代码在操作整数,并且没有做类型转换,比如转换成 string,double 这些,那么它会跑的很快。内联缓存会在这个阶段起到非常重要的作用,提供了类型判断。就像你猜到的那样,我们需要小心类型转换:如果你希望一个变量是一个整数,但是过一会却被修改成了其它类型,那么你的假设就失败了,那么一次重新编译就在所难免了。还有其它的优化,比如 loop-invariant code motion(译者注:貌似是讲将循环内不变化的代码移到外面,减少每一次循环执行的代码数量),移除死代码(译者注:不被执行的代码也要移除,否则 V8 始终都要要对这些代码处理的,带来了额外负担)等。
    一旦 Hydrogen graph 被优化,Crankshaft 会降低它到一个低级别的描述,叫做 Lithium。大部分的 Lithium 执行于特定架构。分配寄存器就是在这个级别进行的。
    最终,Lithium 被编译成机械码。然后一些叫做 OSR (on-stack replacement)的事情就发生了。记住,在我们开始编译和优化运行耗时较长的方法之前,我们喜欢先执行它。我们不要忘记我们刚刚放慢了执行,然后开始执行优化后的代码。相反,我们将要转换所有的上下文,因此我们才能在执行的中间过程中选择执行优化后的代码。我让你们感到复杂,提醒一下,其它的优化中,我们内联了一些东西。V8 并不是唯一一个这么做的虚拟机,但是我发现一些比较疯狂的地方。有一些保护机制叫 – 去优化,在做相反的事情,并且会在一些假设的特定情况下反转一些优化后的代码。
    还有。就是编译/执行部分。我忘了提到 GC,不过这很短,因为我对它不太了解。
    对于垃圾收集来说,V8 使用了传统的方法,采用标记计数的方式来进行垃圾收集。标记阶段必须停止 JavaScript 执行。为了控制 GC 成本,使执行更加稳定,V8 采用增量标记。这就是说,他们不是在堆中试图标记每一个可能的对象,而是处理一部分堆,然后恢复正常的执行。下一个 GC 停止执行代码的时候处理之前未处理的堆。这允许非常短的暂停。如前所述,扫描阶段由单独的线程来处理。

    展开全文
  • 关于qml引擎原理,要从google V8入手,开始使用qml的时候会对它的语法感到不解,虽然是javascript语言,但总是对各种操作感到困惑,尤其是与后台c++的交互,为什么需要做类型注册、对象注册,直到最近做的一些技术...
  • 浏览器工作原理-v8引擎 内存数据存储 栈空间 栈空间是存储调用栈的上下文的。 堆空间 堆空间是存储引用类型的数据。 为什么会有堆空间?因为栈空间需要频繁的释放和添加。需要快速的响应。栈空间的大小不大、而...
  • (一) v8执行一段js代码的过程 预解析 检查语法错误但不生成AST 通过词法分析和语法分析生成AST(抽象语法树) 将AST转换为字节码 通过V8的解释器 / 基线编译器(Ignition)将AST转换成字节码 字节码是AST和机器...
  • 引擎是如何运作的 这是我目前为止对JavaScript编译原理(不考虑优化)的大致理解: 词法作用域 这其中我们着重关注词法作用域的规则,包括var/let的区别,提升,闭包等概念 至此,关于JavaScript词法部分的内容基本...
  • 第二篇文章将深入V8的JavaScript引擎的内部。我们还会提供一些关于如何编写更好的JavaScript代码的技巧。 概述 JavaScript引擎是执行JavaScript代码的程序或解释器。JavaScript引擎可以用标准解释器(interpreter)...
  • 几个星期前,我们开始了一系列旨在深入挖掘JavaScript及其实际工作...第二篇文章将深入探讨谷歌V8 JavaScript引擎的内部部分。我们还将提供一些关于如何编写更好的JavaScript代码的快速提示 - 在构建产品时我们的...
  • 前言 本文将为大家介绍,V8引擎如何执行... V8引擎的工作原理; 浏览器和Node.js架构的区别和练习; Node.js架构的应用场景和REPL; Node.js架构的REPL使用; PS:本篇文章为「Node.js系列」的第一篇,为邂逅Node
  • V8如何存储JS对象我们知道JS一门动态语言,这意味着在代码执行过程中,变量的类型是不定的,可以被改变,非常灵活,这也是JS语言的特性之一。但这也带来了一个问题,我们访问一个JS对象的某个属性时不能直接根据偏移...
  • 本章将会深入谷歌 V8 引擎的内部结构。我们也会为如何书写更好的 JavaScript 代码提供几条小技巧-SessionStack 开发小组在构建产品的时候所遵循的最佳实践。 概述 一个 JavaScript 引擎就是一个程序或者一个解释...
  • 个人总结: 一个Javascript引擎由一个标准解释程序,或者即时编译器来...V8引擎是一种即时编译器。 V8引擎的优化策略: 1.内联:将函数被调用的内行代码置换为被调用的函数体。 2.隐藏类:大多数动态语言使用类字...
  • JS解析过程 首先V8引擎 会收到 JS源程序; 在服务端写服务的时候, 首先是用JS写一个小的脚本; 脚本经过V8, 通过parser,解析成Abstract Syntax Tree, 最终交给解析器【interpreter】, 【interpreter】将其解析...
  • 最近学习了极客时间上李兵大佬的谷歌V8引擎课程,总结了一下,在公司内部小组分享了一波,在此也分享一下,原理图直接用的专栏的图,由于时间有限,总结略显粗糙 注:解释执行、编译执行各有优缺点: 解释...
  • 本系列持续更新中,Github 地址请查阅这里。...本章将会深入谷歌 V8 引擎的内部结构。我们也会为如何书写更好的 JavaScript 代码提供几条小技巧-SessionStack 开发小组在构建产品的时候所遵循的最佳实践。概述一个...
  • 前言 这段时间在写编译原理的课设,对于编译器的实现算是入了个门,着就激起了我心中的一个本源问题,...那么V8引擎到底是怎么工作的呢? 两个编译器的故事 V8会编译所有JavaScript到原生代码,而在V8中,有两个...
  • 本章将会深入谷歌 V8 引擎的内部结构。我们也会为如何书写更好的 JavaScript 代码提供几条小技巧-SessionStack 开发小组在构建产品的时候所遵循的最佳实践。概述一个 JavaScript 引擎就是一个程序或者一个解释程序...
  • V8引擎里面也使用了多个线程: 主线程:获取代码,编译代码然后执行它 还有一个被用来编译的单独的线程,因此主线程可以继续执行,而它也同时可以优化代码 一个分析线程,它将告诉运行时哪些方法耗费了大量的时间...
  • javascript引擎——V8

    2018-04-13 14:15:15
    通过上一篇文章,我们知道了JavaScript...我们经常听说的JavaScript引擎就是V8引擎,这篇文章我们就来认识一下V8引擎,我们先来看一下除了V8引擎,还有哪些JS引擎: V8 开源 由Google开发,用C++编写。V8 最早被...
  • V8引擎里面也使用了多个线程: 主线程:获取代码,编译代码然后执行它 还有一个被用来编译的单独的线程,因此主线程可以继续执行,而它也同时可以优化代码 一个分析线程,它将告诉运行时哪些方法耗费了大量的时间...
  • 前言本文将为大家介绍,V8引擎...V8引擎的工作原理;浏览器和Node.js架构的区别和练习;Node.js架构的应用场景和REPL;Node.js架构的REPL使用;PS:本篇文章为「Node.js系列」的第一篇,为邂逅Node.js。之后会保持...
  • 弱类型:意味着你不需要JavaScript引擎声明的变量是什么类型,JavaScript引擎在运行代码时候会自己算出来 动态:可以使用同一个变量保存不同类型的数据 var bar bar = 12 bar = "极客时间" bar = true bar = null ...
  • 本文对所有 JavaScript 引擎中常见的一些关键基础知识进行了介绍,不仅仅局限于V8引擎。作为 JavaScript 开发人员,深入了解 JavaScript 引擎的工作原理有助于你了解自己代码的性能特征。 JavaScript 引擎的工作...

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 211
精华内容 84
关键字:

v8引擎原理