新技能:通過程式碼快取加速 Node.js 的啟動
導讀 | 本文介紹在 Node.js 裡如何利用程式碼快取技術加速 Node.js 的啟動。 |
前言:之前的文章介紹了通過快照的方式加速 Node.js 的啟動,除了快照,V8 還提供了另一種技術加速程式碼的執行,那就是程式碼快取。通過 V8 第一次執行 JS 的時候,V8 需要即時進行解析和編譯 JS程式碼,這個是需要一定時間的,程式碼快取可以把這個過程的一些資訊儲存下來,下次執行的時候,通過這個快取的資訊就可以加速 JS 程式碼的執行。本文介紹在 Node.js 裡如何利用程式碼快取技術加速 Node.js 的啟動。
首先看一下 Node.js 的編譯配置。
'actions': [ { 'action_name': 'node_js2c', 'process_outputs_as_sources': 1, 'inputs': [ 'tools/js2c.py', '<@(library_files)', '<@(deps_files)', 'config.gypi' ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/node_javascript.cc', ], 'action': [ '<(python)', 'tools/js2c.py', '--directory', 'lib', '--target', '<@(_outputs)', 'config.gypi', '<@(deps_files)', ], }, ],
通過這個配置,在編譯 Node.js 的時候,會執行 js2c.py,並且把輸入寫到 node_javascript.cc 檔案。我們看一下生成的內容。
裡面定義了一個函式,這個函式裡面往 source_ 欄位裡不斷追加一系列的內容,其中 key 是 Node.js 中的原生 JS 模組資訊,值是模組的內容,我們隨便看一個模組 assert/strict。
const data = [39,117,115,101, 32,115,116,114,105, 99,116, 39, 59, 10, 10,109,111,100,117,108,101, 46,101,120,112,111,114,116,115, 32,61, 32,114,101,113,117,105,114,101, 40, 39, 97,115,115,101,114,116, 39, 41, 46,115,116,114,105, 99,116, 59, 10]; console.log(Buffer.from(data).toString('utf-8'))
輸出如下。
'use strict'; module.exports = require('assert').strict;
通過 js2c.py ,Node.js 把原生 JS 模組的內容寫到了檔案中,並且編譯進 Node.js 的可執行檔案裡,這樣在 Node.js 啟動時就不需要從硬碟裡讀取對應的檔案,否則無論是啟動還是執行時動態載入原生 JS 模組,都需要更多的耗時,因為記憶體的速度遠快於硬碟。這是 Node.js 做的第一個優化,接下來看程式碼快取,因為程式碼快取是在這個基礎上實現的。首先看一下編譯配置。
['node_use_node_code_cache=="true"', { 'dependencies': [ 'mkcodecache', ], 'actions': [ { 'action_name': 'run_mkcodecache', 'process_outputs_as_sources': 1, 'inputs': [ '<(mkcodecache_exec)', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/node_code_cache.cc', ], 'action': [ '<@(_inputs)', '<@(_outputs)', ], }, ],}, { 'sources': [ 'src/node_code_cache_stub.cc' ], }],
如果編譯 Node.js 時 node_use_node_code_cache 為 true 則生成程式碼快取。如果我們不需要可以關掉,具體執行 ./configure --without-node-code-cache。如果我們關閉程式碼快取, Node.js 關於這部分的實現是空,具體在 node_code_cache_stub.cc。
const bool has_code_cache = false; void NativeModuleEnv::InitializeCodeCache() {}
也就是什麼都不做。如果我們開啟了程式碼快取,就會執行 mkcodecache.cc 生成程式碼快取。
int main(int argc, char* argv[]) { argv = uv_setup_args(argc, argv); std::ofstream out; out.open(argv[1], std::ios::out | std::ios::binary); node::per_process::enabled_debug_list.Parse(nullptr); std::unique_ptrplatform = v8::platform::NewDefaultPlatform(); v8::V8::InitializePlatform(platform.get()); v8::V8::Initialize(); Isolate::CreateParams create_params; create_params.array_buffer_allocator_shared.reset( ArrayBuffer::Allocator::NewDefaultAllocator()); Isolate* isolate = Isolate::New(create_params); { Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); v8::Localcontext = v8::Context::New(isolate); v8::Context::Scope context_scope(context); std::string cache = CodeCacheBuilder::Generate(context); out << cache; out.close(); } isolate->Dispose(); v8::V8::ShutdownPlatform(); return 0; }
首先開啟檔案,然後是 V8 的常用初始化邏輯,最後通過 Generate 生成程式碼快取。
std::string CodeCacheBuilder::Generate(Localcontext) { NativeModuleLoader* loader = NativeModuleLoader::GetInstance(); std::vectorids = loader->GetModuleIds(); std::mapdata; for (const auto& id : ids) { if (loader->CanBeRequired(id.c_str())) { NativeModuleLoader::Result result; USE(loader->CompileAsModule(context, id.c_str(), &result)); ScriptCompiler::CachedData* cached_data = loader->GetCodeCache(id.c_str()); data.emplace(id, cached_data); } } return GenerateCodeCache(data); }
首先新建一個 NativeModuleLoader。
NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) { LoadJavaScriptSource(); }
NativeModuleLoader 初始化時會執行 LoadJavaScriptSource,這個函式就是通過 python 生成的 node_javascript.cc 檔案裡的函式,初始化完成後 NativeModuleLoader 物件的 source_ 欄位就儲存了原生 JS 模組的程式碼。接著遍歷這些原生 JS 模組,通過 CompileAsModule 進行編譯。
MaybeLocalNativeModuleLoader::CompileAsModule( Localcontext, const char* id, NativeModuleLoader::Result* result) { Isolate* isolate = context->GetIsolate(); std::vector<1local> parameters = { FIXED_ONE_BYTE_STRING(isolate, "exports"), FIXED_ONE_BYTE_STRING(isolate, "require"), FIXED_ONE_BYTE_STRING(isolate, "module"), FIXED_ONE_BYTE_STRING(isolate, "process"), FIXED_ONE_BYTE_STRING(isolate, "internalBinding"), FIXED_ONE_BYTE_STRING(isolate, "primordials")}; return LookupAndCompile(context, id, ¶meters, result); }
接著看 LookupAndCompile
MaybeLocalNativeModuleLoader::LookupAndCompile( Localcontext, const char* id, std::vector<1local>* parameters, NativeModuleLoader::Result* result) { Isolate* isolate = context->GetIsolate(); EscapableHandleScope scope(isolate); Localsource; // 根據 key 從 source_ 欄位找到模組內容 if (!LoadBuiltinModuleSource(isolate, id).ToLocal(&source)) { return {}; } std::string filename_s = std::string("node:") + id; Localfilename = OneByteString(isolate, filename_s.c_str(), filename_s.size()); ScriptOrigin origin(isolate, filename, 0, 0, true); ScriptCompiler::CachedData* cached_data = nullptr; { Mutex::ScopedLock lock(code_cache_mutex_); // 判斷是否有程式碼快取 auto cache_it = code_cache_.find(id); if (cache_it != code_cache_.end()) { cached_data = cache_it->second.release(); code_cache_.erase(cache_it); } } const bool has_cache = cached_data != nullptr; ScriptCompiler::CompileOptions options = has_cache ? ScriptCompiler::kConsumeCodeCache : ScriptCompiler::kEagerCompile; // 如果有程式碼快取則傳入 ScriptCompiler::Source script_source(source, origin, cached_data); // 進行編譯 MaybeLocalmaybe_fun = ScriptCompiler::CompileFunctionInContext(context, &script_source, parameters->size(), parameters->data(), 0, nullptr, options); Localfun; if (!maybe_fun.ToLocal(&fun)) { return MaybeLocal(); } *result = (has_cache && !script_source.GetCachedData()->rejected) ? Result::kWithCache : Result::kWithoutCache; // 生成程式碼快取儲存下來,最後寫入檔案,下次使用 std::unique_ptrnew_cached_data( ScriptCompiler::CreateCodeCacheForFunction(fun)); { Mutex::ScopedLock lock(code_cache_mutex_); code_cache_.emplace(id, std::move(new_cached_data)); } return scope.Escape(fun); }
第一次執行的時候,也就是編譯 Node.js 時,LookupAndCompile 會生成程式碼快取寫到檔案 node_code_cache.cc 中,並編譯進可執行檔案,內容大致如下。
除了這個函式還有一系列的程式碼快取資料,這裡就不貼出來了。在 Node.js 第一次執行的初始化階段,就會執行上面的函式,在 code_cache 欄位裡儲存了每個模組和對應的程式碼快取。初始化完畢後,後面載入原生 JS 模組時,Node.js 再次執行 LookupAndCompile,就個時候就有程式碼快取了。當開啟程式碼快取時,我的電腦上 Node.js 啟動時間大概為 40 毫秒,當去掉程式碼快取的邏輯重新編譯後,Node.js 的啟動時間大概是 60 毫秒,速度有了很大的提升。
總結:Node.js 在編譯時首先把原生 JS 模組的程式碼寫入到檔案並,接著執行 mkcodecache.cc 把原生 JS 模組進行編譯和獲取對應的程式碼快取,然後寫到檔案中,同時編譯進 Node.js 的可執行檔案中,在 Node.js 初始化時會把他們收集起來,這樣後續載入原生 JS 模組時就可以使用這些程式碼快取加速程式碼的執行。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2892937/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 新技能:透過程式碼快取加速 Node.js 的啟動快取Node.js
- 通過HTTP Header控制快取HTTPHeader快取
- .net 程式通過 crontab 無法啟動,手動執行指令碼可以啟動指令碼
- Java記憶體快取-通過Google Guava建立快取Java記憶體快取GoGuava
- Node.js快取Node.js快取
- python3 通過 pybind11 使用Eigen加速程式碼Python
- 使用IndexedDB快取給WebGL三維程式加速Index快取Web
- Java記憶體快取-通過Map定製簡單快取Java記憶體快取
- 通過 gitlab 介面獲取程式碼提交記錄和改動行數Gitlab
- Webpack Plugin 自動生成版本號等配置並通過前端伺服器獲取更新微信快取舊程式碼問題WebPlugin前端伺服器快取
- [05] 通過P/Invoke加速C#程式C#
- GaussDB(for MySQL) :Partial Result Cache,通過快取中間結果對運算元進行加速MySql快取
- CDN加速快取的定義與作用快取
- PySpark 通過Arrow加速Spark
- 程式碼解決快取穿透和快取雪崩問題快取穿透
- [譯]通過超市買牛奶來學習快取快取
- Node.js原始碼解析-啟動-js部分Node.js原始碼
- 探索前端黑科技——通過 png 圖的 rgba 值快取資料前端快取
- 用程式碼來實踐Web快取Web快取
- Mybatis的快取——一級快取和原始碼分析MyBatis快取原始碼
- 無人機get新技能,惹黑熊心跳加速無人機
- 從原始碼看微信小程式啟動過程原始碼微信小程式
- 通過 Grub 來引導啟動 UBUNTUUbuntu
- 不廢話,程式碼實踐帶你掌握 強快取、協商快取!快取
- 如何手動重啟 Node.js 和用守護程式自動重啟Node.js
- GO語言————6.12 通過記憶體快取來提升效能Go記憶體快取
- 走近原始碼:Redis的啟動過程原始碼Redis
- Linux的啟動過程及init程式Linux
- php 快取使用監控測試程式碼PHP快取
- 通過程式碼掛上物理頁
- 魚塘翻了,記Node中通過redis快取session資訊遇到的坑Redis快取Session
- 通義靈碼:體驗 AI 程式設計新技能-@workspace 和 @terminal 為你的程式設計插上一雙翅膀AI程式設計
- pbootcms模板自動清理runtime快取,自動清理快取boot快取
- windows通過python指令碼重啟本地redisWindowsPython指令碼Redis
- Android 8.0 原始碼分析 (三) 應用程式程式建立到應用程式啟動的過程Android原始碼
- React通過redux快取列表資料以及滑動位置,回退時恢復頁面狀態ReactRedux快取
- CloudIDE:為開發者寫程式碼開啟“加速”模式CloudIDE模式
- 為什麼SAP GUI裡的傳統事務碼能通過Fiori Launchpad啟動GUI