Skip to content

Commit b729030

Browse files
committed
node-api: find the Environment once
Without an async context, napi_make_callback went through the public node::MakeCallback(), which looks up the Environment from the creation context of the callback on every call. The napi_env already knows it, so call into it directly, as the path with an async context does. node_napi_env__::node_env() also looked it up from the context on every call: find it once, a napi_env lives as long as its Environment. Add a benchmark, napi/make_callback_napi. Refs: nodejs/performance#24 Signed-off-by: Nigro Simone <nigro.simone@gmail.com>
1 parent f4d2742 commit b729030

7 files changed

Lines changed: 129 additions & 8 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include <node_api.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
#include <uv.h>
5+
6+
typedef struct {
7+
uv_timer_t timer;
8+
napi_env env;
9+
int64_t n;
10+
napi_ref fn;
11+
napi_ref done;
12+
} State;
13+
14+
static void OnClose(uv_handle_t* handle) {
15+
free(handle->data);
16+
}
17+
18+
// napi_make_callback without async context, n times from a libuv timer, then
19+
// done: every call opens a top-level callback scope, like an I/O callback.
20+
static void OnTimer(uv_timer_t* handle) {
21+
State* state = (State*) handle->data;
22+
napi_env env = state->env;
23+
napi_handle_scope scope;
24+
napi_value fn, done, recv;
25+
napi_open_handle_scope(env, &scope);
26+
napi_get_reference_value(env, state->fn, &fn);
27+
napi_get_reference_value(env, state->done, &done);
28+
napi_get_global(env, &recv);
29+
for (int64_t i = 0; i < state->n; i++) {
30+
napi_handle_scope inner;
31+
napi_open_handle_scope(env, &inner);
32+
napi_make_callback(env, NULL, recv, fn, 0, NULL, NULL);
33+
napi_close_handle_scope(env, inner);
34+
}
35+
napi_make_callback(env, NULL, recv, done, 0, NULL, NULL);
36+
napi_delete_reference(env, state->fn);
37+
napi_delete_reference(env, state->done);
38+
napi_close_handle_scope(env, scope);
39+
uv_close((uv_handle_t*) &state->timer, OnClose);
40+
}
41+
42+
// run(n, fn, done)
43+
static napi_value Run(napi_env env, napi_callback_info info) {
44+
size_t argc = 3;
45+
napi_value argv[3];
46+
napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
47+
State* state = (State*) calloc(1, sizeof(State));
48+
state->env = env;
49+
napi_get_value_int64(env, argv[0], &state->n);
50+
napi_create_reference(env, argv[1], 1, &state->fn);
51+
napi_create_reference(env, argv[2], 1, &state->done);
52+
uv_loop_t* loop;
53+
napi_get_uv_event_loop(env, &loop);
54+
state->timer.data = state;
55+
uv_timer_init(loop, &state->timer);
56+
uv_timer_start(&state->timer, OnTimer, 0, 0);
57+
return NULL;
58+
}
59+
60+
NAPI_MODULE_INIT() {
61+
napi_value run;
62+
napi_create_function(env, "run", NAPI_AUTO_LENGTH, Run, NULL, &run);
63+
napi_set_named_property(env, exports, "run", run);
64+
return exports;
65+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.c' ]
6+
}
7+
]
8+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
const common = require('../../common.js');
4+
5+
// napi_make_callback without async context from a libuv timer, so every call
6+
// opens a top-level callback scope, like an I/O callback does.
7+
8+
let binding;
9+
try {
10+
binding = require(`./build/${common.buildType}/binding`);
11+
} catch {
12+
console.error('napi/make_callback_napi/index.js Binding failed to load');
13+
process.exit(0);
14+
}
15+
16+
const bench = common.createBenchmark(main, {
17+
n: [1e6, 1e7],
18+
});
19+
20+
function main({ n }) {
21+
bench.start();
22+
binding.run(n, () => {}, () => bench.end(n));
23+
}

‎src/api/callback.cc‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,13 +348,24 @@ MaybeLocal<Value> InternalMakeCallback(Isolate* isolate,
348348
}
349349
Environment* env = Environment::GetCurrent(context);
350350
CHECK_NOT_NULL(env);
351+
return MakeCallbackInEnvironment(
352+
env, recv, callback, argc, argv, asyncContext, context_frame);
353+
}
354+
355+
MaybeLocal<Value> MakeCallbackInEnvironment(Environment* env,
356+
Local<Object> recv,
357+
const Local<Function> callback,
358+
int argc,
359+
Local<Value> argv[],
360+
async_context asyncContext,
361+
Local<Value> context_frame) {
351362
Context::Scope context_scope(env->context());
352363
MaybeLocal<Value> ret = InternalMakeCallback(
353364
env, recv, recv, callback, argc, argv, asyncContext, context_frame);
354365
if (ret.IsEmpty() && env->async_callback_scope_depth() == 0) {
355366
// This is only for legacy compatibility and we may want to look into
356367
// removing/adjusting it.
357-
return Undefined(isolate);
368+
return Undefined(env->isolate());
358369
}
359370
return ret;
360371
}

‎src/node_api.cc‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ static void ThrowNodeApiVersionError(node::Environment* node_env,
7171
node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
7272
const std::string& module_filename,
7373
int32_t module_api_version)
74-
: napi_env__(context, module_api_version), filename(module_filename) {
74+
: napi_env__(context, module_api_version),
75+
filename(module_filename),
76+
node_env_(node::Environment::GetCurrent(context)) {
7577
CHECK_NOT_NULL(node_env());
7678
}
7779

@@ -1026,13 +1028,15 @@ napi_status NAPI_CDECL napi_make_callback(napi_env env,
10261028
v8::MaybeLocal<v8::Value> callback_result;
10271029

10281030
if (async_context == nullptr) {
1029-
callback_result = node::MakeCallback(
1030-
env->isolate,
1031+
// The Environment of the napi_env, not looked up from the callback again.
1032+
callback_result = node::MakeCallbackInEnvironment(
1033+
reinterpret_cast<node_napi_env>(env)->node_env(),
10311034
v8recv,
10321035
v8func,
10331036
argc,
10341037
reinterpret_cast<v8::Local<v8::Value>*>(const_cast<napi_value*>(argv)),
1035-
{0, 0});
1038+
{0, 0},
1039+
v8::Undefined(env->isolate));
10361040
} else {
10371041
v8impl::AsyncContext* node_async_context =
10381042
reinterpret_cast<v8impl::AsyncContext*>(async_context);

‎src/node_api_internals.h‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ struct node_napi_env__ : public napi_env__ {
3333

3434
void DeleteMe() override;
3535

36-
inline node::Environment* node_env() const {
37-
return node::Environment::GetCurrent(context());
38-
}
36+
// Found once: a napi_env lives as long as its Environment.
37+
inline node::Environment* node_env() const { return node_env_; }
3938
inline const char* GetFilename() const { return filename.c_str(); }
4039

4140
std::string filename;
41+
node::Environment* node_env_;
4242
bool destructing = false;
4343
bool finalization_scheduled = false;
4444
};

‎src/node_internals.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@ v8::MaybeLocal<v8::Value> InternalMakeCallback(
230230
async_context asyncContext,
231231
v8::Local<v8::Value> context_frame);
232232

233+
// The same, for a caller that already knows the Environment of `callback`.
234+
v8::MaybeLocal<v8::Value> MakeCallbackInEnvironment(
235+
Environment* env,
236+
v8::Local<v8::Object> recv,
237+
const v8::Local<v8::Function> callback,
238+
int argc,
239+
v8::Local<v8::Value> argv[],
240+
async_context asyncContext,
241+
v8::Local<v8::Value> context_frame);
242+
233243
v8::MaybeLocal<v8::Value> MakeSyncCallback(v8::Isolate* isolate,
234244
v8::Local<v8::Object> recv,
235245
v8::Local<v8::Function> callback,

0 commit comments

Comments
 (0)