日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
通過快照加速Node.js的啟動

前言:隨著 Node.js 的越來越強大,代碼量也變得越來越多,不可避免地拖慢了 Node.js 的啟動速度,針對這個問題,Node.js 社區(qū)通過 V8 的 snapshot 技術(shù)對 Node.js 的啟動做了優(yōu)化,在 github 有很多關(guān)于此的 issue 討論,大家有興趣也可以去看一下。通過快照加速啟動是一個非常復(fù)雜的過程,這需要對 V8 有深入的理解。本文介紹一下如何在 Node.js 中使用快照加速 Node.js 的啟動。以 v16.13.1 為例,社區(qū)一直在優(yōu)化這里面的速度,不同的版本的速度可能不一樣。

Node.js 默認開啟了快照功能。編譯后會生成一個 node_snapshot.cc 文件。里面定義了幾個方法和保存了快照的數(shù)據(jù),在 Node.js 啟動的時候會用到。我們也可以在編譯的時候關(guān)閉這個功能,具體執(zhí)行 ./configure --without-node-snapshot。除了控制編譯時是否生成快照,還可以控制啟動時是否使用快照,默認是使用,可以通過 --no-node-snapshot 關(guān)閉。我們看看效果。

const { performance } = require('perf_hooks');
console.log(performance.nodeTiming.bootstrapComplete - performance.nodeTiming.nodeStart);

可以通過 perf_hooks 模塊拿到 Node.js 在啟動過程的時間,這里我們首先在不使用快照的情況下看看時間。具體執(zhí)行 node --no-node-snapshot test.js,我的電腦上是 42.165621001273394 毫秒。然后再看使用快照時的時間。具體執(zhí)行 node test.js,我電腦是 24.800417000427842 毫秒,我們看到速度有了很大的提升。接下來我們看看 Node.js 關(guān)于這部分的大致實現(xiàn)。首先看編譯配置。

['node_use_node_snapshot=="true"', {
'dependencies': [
'node_mksnapshot',
],
'actions': [
{
'action_name': 'node_mksnapshot',
'process_outputs_as_sources': 1,
'inputs': [
'<(node_mksnapshot_exec)',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc',
],
'action': [
'<@(_inputs)',
'<@(_outputs)',
],
},
],}, {
'sources': [
'src/node_snapshot_stub.cc'
],

}],

我們看到這里根據(jù) node_use_node_snapshot 判斷要不要生成快照,這個變量在 configure.py 中設(shè)置,也就是前面說的 --without-node-snapshot。

如果 node_use_node_snapshot 為 false,則編譯 node_snapshot_stub.cc。

node_snapshot_stub.cc 提供了一個默認的實現(xiàn),因為 Node.js 的 C++ 代碼里會用到這幾個函數(shù)。如果 node_use_node_snapshot 為 true 則執(zhí)行 node_mksnapshot.cc 并且把快照寫入到文件 node_snapshot.cc。接下來看一下 node_mksnapshot.cc 是如何生成快照的。

int main(int argc, char* argv[]) {
argv = uv_setup_args(argc, argv);
v8::V8::SetFlagsFromString("--random_seed=42");
std::ofstream out;
out.open(argv[1], std::ios::out | std::ios::binary);
node::InitializationResult result = node::InitializeOncePerProcess(argc, argv);
{
std::string snapshot =
node::SnapshotBuilder::Generate(result.args, result.exec_args);
out << snapshot;
out.close();
}
node::TearDownOncePerProcess();
return 0;
}

InitializeOncePerProcess 做了一些初始化操作,重點是 Generate(直接看底層的 Generate)。

void SnapshotBuilder::Generate(SnapshotData* out,
const std::vector args,
const std::vector exec_args) {
Isolate* isolate = Isolate::Allocate();
isolate->SetCaptureStackTraceForUncaughtExceptions(
true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
per_process::v8_platform.Platform()->RegisterIsolate(isolate,
uv_default_loop());
std::unique_ptr main_instance;
std::string result;
{
const std::vector& external_references =
NodeMainInstance::CollectExternalReferences();
SnapshotCreator creator(isolate, external_references.data());
Environment* env;
{
main_instance =
NodeMainInstance::Create(isolate,
uv_default_loop(),
per_process::v8_platform.Platform(),
args,
exec_args);
HandleScope scope(isolate);
creator.SetDefaultContext(Context::New(isolate));
out->isolate_data_indices = main_instance->isolate_data()->Serialize(&creator);
Local context;
{
TryCatch bootstrapCatch(isolate);
context = NewContext(isolate);
}
Context::Scope context_scope(context);
// Create the environment
env = new Environment(main_instance->isolate_data(),
context,
args,
exec_args,
nullptr,
node::EnvironmentFlags::kDefaultFlags,
{});
// Run scripts in lib/internal/bootstrap/
{
TryCatch bootstrapCatch(isolate);
v8::MaybeLocal result = env->RunBootstrapping();
result.ToLocalChecked();
}
// Serialize the native states
out->env_info = env->Serialize(&creator);
// Serialize the context
size_t index = creator.AddContext(context, {SerializeNodeContextInternalFields, env});
}
// Must be out of HandleScope
out->blob =
creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear);
}
per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
}

可以看到就是模擬了 Node.js 的啟動過程,然后把相關(guān)的數(shù)據(jù)寫入到快照中。最終生成了一個文件。

這個文件和前面默認的 node_snapshot_stub.cc 文件類似,多了快照數(shù)據(jù)。有了快照再來看一下怎么使用。

int Start(int argc, char** argv) {
InitializationResult result = InitializeOncePerProcess(argc, argv);
if (result.early_return) {
return result.exit_code;
}
{
Isolate::CreateParams params;
const std::vector* indices = nullptr;
const EnvSerializeInfo* env_info = nullptr;
// 是否使用快照
bool use_node_snapshot =
per_process::cli_options->per_isolate->node_snapshot;
if (use_node_snapshot) {
// 獲取快照信息
v8::StartupData* blob = NodeMainInstance::GetEmbeddedSnapshotBlob();
if (blob != nullptr) {
params.snapshot_blob = blob;
indices = NodeMainInstance::GetIsolateDataIndices();
env_info = NodeMainInstance::GetEnvSerializeInfo();
}
}
uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);
// 通過快照初始化
NodeMainInstance main_instance(¶ms,
uv_default_loop(),
per_process::v8_platform.Platform(),
result.args,
result.exec_args,
indices);
result.exit_code = main_instance.Run(env_info);
}
TearDownOncePerProcess();
return result.exit_code;

}

Start 是 Node.js 啟動時執(zhí)行的函數(shù),在上面代碼中可以看到如果開啟了快照并且生成了快照,那么就通過快照進行初始化,否則走正常初始化流程,下面是 IsolateData 的初始化邏輯。

if (indexes == nullptr) {
CreateProperties();} else {
DeserializeProperties(indexes);

}

我們可以看到有快照則反序列化后進行初始化就行,否則需要進行創(chuàng)建。我們對比一下使用和沒有使用快照進行初始化的代碼的對比,以 async_wrap_providers_ 為例。下面是沒有使用快照。

void IsolateData::CreateProperties() {
async_wrap_providers_[AsyncWrap::PROVIDER_ ## Provider].Set( \
isolate_, \
String::NewFromOneByte( \
isolate_, \
reinterpret_cast(#Provider), \
NewStringType::kInternalized, \
sizeof(#Provider) - 1).ToLocalChecked());
NODE_ASYNC_PROVIDER_TYPES(V)

}

上面是通過宏初始化 async_wrap_providers_ 數(shù)組的邏輯,可以看到使用 V8 的 API 創(chuàng)建字符串然后設(shè)置到 async_wrap_providers_ 中。接下來看使用了快照的初始化邏輯。

void IsolateData::DeserializeProperties(const std::vector* indexes) {
size_t i = 0;
HandleScope handle_scope(isolate_);
for (size_t j = 0; j < AsyncWrap::PROVIDERS_LENGTH; j++) {
MaybeLocal maybe_field = isolate_->GetDataFromSnapshotOnce((*indexes)[i++]);
Local field;
async_wrap_providers_[j].Set(isolate_, field);
}
}

可以看到直接通過快照信息調(diào)用 GetDataFromSnapshotOnce 獲取對應(yīng)的數(shù)據(jù),然后設(shè)置到 async_wrap_providers_。

總結(jié):可以看到通過快照極大加速了 Node.js 的啟動過程,而快照技術(shù)的思想很簡單,就是保存副本避免每次重新創(chuàng)建一樣的數(shù)據(jù),但是實現(xiàn)上是非常復(fù)雜的。甚至有同學(xué)提出是否可以在任意時刻給進程當前狀態(tài)打一個快照,這樣進程掛掉后就可以直接恢復(fù)到之前的狀態(tài),這聽起來很美好,但是實現(xiàn)起來可能會非常復(fù)雜。


網(wǎng)站標題:通過快照加速Node.js的啟動
標題URL:http://www.dlmjj.cn/article/cdidiog.html