1. 程式人生 > >詳解Node.js API系列C/C++ Addons(2) Google V8引擎

詳解Node.js API系列C/C++ Addons(2) Google V8引擎

回顧,前文由node.js寫的基於addon的hello world例子

#include <node.h>
#include <v8.h>
using namespace v8;
Handle<Value> Method(const Arguments& args) {
  HandleScope scope;
  return scope.Close(String::New("world"));
}
void init(Handle<Object> exports) {
  exports->Set(String::NewSymbol("hello"
), FunctionTemplate::New(Method)->GetFunction()); } NODE_MODULE(hello, init)

HandleScope scope這是什麼玩意,哪裡才能獲取到它的介紹。請留意程式碼段中using namespace v8 ,實際上它呼叫了google v8的庫,所以要了解node.js addon需要了解google v8引擎(以下簡稱v8)。

v8 是一個javascript解釋引擎,跟傳統javascript引擎不同,主要有四個特性

  • 快速的屬性訪問
  • 動態機器碼生成
  • 高效的垃圾收集
  • 內聯快取機制

JIT編譯(Just-in-time Compile)

以java為例子,java編譯器會先將原始程式碼轉換成語法樹,語法樹編譯成虛擬中間語言(位元組碼),java vm按順序解釋位元組碼,解析成機器語言。

v8編譯流程,編譯器將原始程式碼轉換成語法樹後,直接解釋成機器語言。

傳統編譯流程

程式碼段 -> 語法樹 -> 中間碼 -> 機器語言

v8編譯流程

程式碼段 -> 語法樹 -> 機器語言

高效記憶體管理

採用精確分代垃圾收集技術(Precise Generational GC)

年輕代,是一個較小的連續地址空間,經常進行收集。

年長代,被分成幾個空間,不經常收集,先在年輕代裡創立物件,沒有被收集的物件被移到年長代

  • 可執行的程式碼空間
  • 存放隱藏類空間
  • 大的物件空間 (>8k)
  • 資料物件空間 (存放不含指標的物件)
  • 普通物件空間

垃圾收集技術

  • 清除收集
    • 只在年輕代使用複製收集演算法
    • 間隙時間 2ms
  • 全堆非壓縮收集
    • 對兩代使用標記-清除收集演算法
    • 空閒記憶體加入空閒列表
    • 可能產生碎片
    • 間隙時間 50ms
  • 全堆壓縮收集
    • 對兩袋使用標記-壓縮收集演算法
    • 間隙時間 100ms

隱藏類

傳統的javascirpt引擎使用雜湊表hash table來存取屬性和方法,每次存取屬性或找方法的時候,就會使用字串作為尋找物件的雜湊鍵key,搜尋雜湊表是一個連續動作,首先通過雜湊(hashing)值判斷資料內的位置,然後判斷陣列內的鍵值是否相等,如果不相等,位移到其他陣列,方法比較費時。

相反,對於C++和JAVA等,編譯的時候,會事先知道儲存資料的型別和偏移量。

因此,V8通過隱藏類來簡化索引,V8在定義物件的過程中,自動建立隱藏類,通過類的陣列索引來查詢物件的值。詳細過程可以參考

內聯快取機制

當進行隱藏類的行為操作的時候,會被快取,下一次採用相同的隱藏類行為的時候,能直接命中。

基於Google V8的Hello world

#include <v8.h>
using namespace v8;
int main(int argc, char* argv[]) {
  // Get the default Isolate created at startup.
  Isolate* isolate = Isolate::GetCurrent();
  // Create a stack-allocated handle scope.
  HandleScope handle_scope(isolate);
  // Create a new context.
  Handle<Context> context = Context::New(isolate);
  // Here's how you could create a Persistent handle to the context, if needed.
  Persistent<Context> persistent_context(isolate, context);

  // Enter the created context for compiling and
  // running the hello world script. 
  Context::Scope context_scope(context);
  // Create a string containing the JavaScript source code.
  Handle<String> source = String::New("'Hello' + ', World!'");
  // Compile the source code.
  Handle<Script> script = Script::Compile(source);

  // Run the script to get the result.
  Handle<Value> result = script->Run();

  // The persistent handle needs to be eventually disposed.
  persistent_context.Dispose();
  // Convert the result to an ASCII string and print it.
  String::AsciiValue ascii(result);
  printf("%s\n", *ascii);
  return 0;
}

Isolate

Isolate表示一個獨立的v8引擎例項,每個例項維護不同的狀態。一個Isolate中的物件不能在其他Isolate中使用。當v8被初始化的時候,一個預設isolate被預設建立。開發者可以通過建立額外的Isolate在多執行緒環境下並行使用。一個Isolate任意時間只允許一個執行緒在其中執行,可以使用Locker和Unlocker來進行多個執行緒對一個Isolate的同步。

Context

V8允許不同的JavaScript程式碼執行在完全不同的環境下,其執行環境稱為Context。不同的Context下擁有自己的全域性物件(PersistentHandle),執行程式碼時必須指定所在的Context。最典型的例子就是Chrome的標籤,每個標籤都擁有自己的Context。
Context擁有自己的全域性代理物件(global proxy object),每個Context下的全域性物件都是這個全域性代理物件的屬性。通過Context::Global ()可以得到這個全域性代理物件。新建Context時你可以手動指定它的全域性代理物件,這樣每個Context都會自動擁有一些全域性物件,比如DOM。
Context也是一種scope,通過Context::Enter ()和Context::Exit ()來進入、退出,或者使用類似於HandleScope的Context::Scope來隱式進入。

Handle

V8裡使用Handle型別來託管 JavaScript物件,與C++的std::shared_pointer類似,Handle型別間的賦值均是直接傳遞物件引用,但不同的是,V8使用自己的GC來管理物件生命週期,而不是智慧指標常用的引用計數。如果一個v8物件沒有任何Handle與之相關聯(不再被訪問),那麼這個物件很快就會被垃圾回收器回收掉。
Handle有兩種型別,Local Handle和Persistent Handle,型別分別是Local : Handle和Persistent : Handle,前者和Handle沒有區別,生存週期都在scope內。而後者的生命週期脫離scope,你需要手動呼叫Persistent::Dispose結束其生命週期。也就是說Local Handle相當於在C++在棧上分配物件,而Persistent Handle相當於C++在堆上分配物件。

HandleScope

一個函式中,可以有很多Handle,而HandleScope則相當於用來裝Handle(Local)的容器,當HandleScope生命週期結束的時候,Handle也將會被釋放,會引起Heap中物件引用的更新。HandleScope是分配在棧上,不能通過New的方式進行建立。對於同一個作用域內可以有多個HandleScope,新的HandleScope將會覆蓋上一個HandleScope,並對Local Handle進行管理。

參考資料