Commit d97041fff91 for nodejs
commit d97041fff91a4574660d07adc3d0a6cf289a3991
Author: Nigro Simone <nigro.simone@gmail.com>
Date: Sat Sep 26 14:59:55 2026 +0200
src: reduce InternalCallbackScope overhead
InternalCallbackScope looks up the Environment from the isolate two
times per call, inside async_context_frame::exchange, and it keeps the
prior async context frame in a v8::Global also when there is no frame,
that is the common case. Every call from native code into JS pays this:
MakeCallback, CallbackScope, AsyncWrap, Node-API.
Now the scope passes the Environment it already has, the option is read
with an inline accessor instead of copying the shared_ptr, and the
global handle is created only when the prior frame is not undefined.
benchmark/napi/make_callback, Node 26.3.0 built with and without this
change, Linux x64, 30 runs: from 202-208 ns to 155-159 ns per call.
Refs: https://github.com/nodejs/performance/issues/24
Signed-off-by: Nigro Simone <nigro.simone@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66316
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
diff --git a/src/api/callback.cc b/src/api/callback.cc
index c3850fa4afe..217aebae9e1 100644
--- a/src/api/callback.cc
+++ b/src/api/callback.cc
@@ -112,8 +112,12 @@ InternalCallbackScope::InternalCallbackScope(
isolate->SetIdle(false);
- prior_context_frame_.Reset(
- isolate, async_context_frame::exchange(isolate, context_frame));
+ // The prior frame is usually undefined: no global handle then.
+ Local<Value> prior_context_frame =
+ async_context_frame::exchange(env, context_frame);
+ if (!prior_context_frame->IsUndefined()) {
+ prior_context_frame_.Reset(isolate, prior_context_frame);
+ }
env->async_hooks()->push_async_context(
async_context_.async_id, async_context_.trigger_async_id, object);
@@ -159,7 +163,7 @@ void InternalCallbackScope::Close() {
if (pushed_ids_) {
env_->async_hooks()->pop_async_context(async_context_.async_id);
- async_context_frame::exchange(isolate, prior_context_frame_.Get(isolate));
+ async_context_frame::set(env_, prior_context_frame_.Get(isolate));
}
if (failed_) return;
diff --git a/src/async_context_frame.cc b/src/async_context_frame.cc
index 8e5fb3dff95..2e637904573 100644
--- a/src/async_context_frame.cc
+++ b/src/async_context_frame.cc
@@ -37,23 +37,30 @@ Local<Value> current(Isolate* isolate) {
return isolate->GetContinuationPreservedEmbedderDataV2().As<Value>();
}
-void set(Isolate* isolate, Local<Value> value) {
- auto env = Environment::GetCurrent(isolate);
- if (!env->options()->async_context_frame) {
+void set(Environment* env, Local<Value> value) {
+ if (!env->async_context_frame_enabled()) {
return;
}
- isolate->SetContinuationPreservedEmbedderDataV2(value);
+ env->isolate()->SetContinuationPreservedEmbedderDataV2(value);
+}
+
+void set(Isolate* isolate, Local<Value> value) {
+ set(Environment::GetCurrent(isolate), value);
}
// NOTE: It's generally recommended to use async_context_frame::Scope
// but sometimes (such as enterWith) a direct exchange is needed.
-Local<Value> exchange(Isolate* isolate, Local<Value> value) {
- auto prior = current(isolate);
- set(isolate, value);
+Local<Value> exchange(Environment* env, Local<Value> value) {
+ auto prior = current(env->isolate());
+ set(env, value);
return prior;
}
+Local<Value> exchange(Isolate* isolate, Local<Value> value) {
+ return exchange(Environment::GetCurrent(isolate), value);
+}
+
void CreatePerContextProperties(Local<Object> target,
Local<Value> unused,
Local<Context> context,
diff --git a/src/async_context_frame.h b/src/async_context_frame.h
index 389f740d643..8ec59ebbd96 100644
--- a/src/async_context_frame.h
+++ b/src/async_context_frame.h
@@ -23,7 +23,9 @@ class Scope {
v8::Local<v8::Value> current(v8::Isolate* isolate);
void set(v8::Isolate* isolate, v8::Local<v8::Value> value);
+void set(Environment* env, v8::Local<v8::Value> value);
v8::Local<v8::Value> exchange(v8::Isolate* isolate, v8::Local<v8::Value> value);
+v8::Local<v8::Value> exchange(Environment* env, v8::Local<v8::Value> value);
} // namespace async_context_frame
} // namespace node
diff --git a/src/env-inl.h b/src/env-inl.h
index c2abf1c29a8..12ffdb48a0b 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -460,6 +460,10 @@ inline std::shared_ptr<EnvironmentOptions> Environment::options() {
return options_;
}
+inline bool Environment::async_context_frame_enabled() const {
+ return options_->async_context_frame;
+}
+
inline const std::vector<std::string>& Environment::argv() {
return argv_;
}
diff --git a/src/env.h b/src/env.h
index a5ce5ee0231..bbe417852e9 100644
--- a/src/env.h
+++ b/src/env.h
@@ -1113,6 +1113,7 @@ class Environment final : public MemoryRetainer {
void* data);
inline std::shared_ptr<EnvironmentOptions> options();
+ inline bool async_context_frame_enabled() const;
inline std::shared_ptr<ExclusiveAccess<HostPort>> inspector_host_port();
inline int64_t stack_trace_limit() const;