Skip to content

Commit b5bc3f8

Browse files
addaleaxMylesBorins
authored andcommitted
timers: cross JS/C++ border less frequently
This removes the `process._needImmediateCallback` property and its semantics of having a 1/0 switch that tells C++ whether immediates are currently scheduled. Instead, a counter keeping track of all immediates is created, that can be increased on `setImmediate()` or decreased when an immediate is run or cleared. This is faster, because rather than reading/writing a C++ getter, this operation can be performed as a direct memory read/write via a typed array. The only C++ call that is left to make is activating the native handles upon creation of the first `Immediate` after the queue is empty. One other (good!) side-effect is that `immediate._destroyed` now reliably tells whether an `immediate` is still scheduled to run or not. Also, as a nice extra, this should make it easier to implement an internal variant of `setImmediate` for C++ that piggybacks off the same mechanism, which should be useful at least for async hooks and HTTP/2. Benchmark results: $ ./node benchmark/compare.js --new ./node --old ./node-master-1b093cb93df0 --runs 10 --filter immediate timers | Rscript benchmark/compare.R [00:08:53|% 100| 4/4 files | 20/20 runs | 1/1 configs]: Done improvement confidence p.value timers/immediate.js type="breadth" thousands=2000 25.61 % ** 1.432301e-03 timers/immediate.js type="breadth1" thousands=2000 7.66 % 1.320233e-01 timers/immediate.js type="breadth4" thousands=2000 4.61 % 5.669053e-01 timers/immediate.js type="clear" thousands=2000 311.40 % *** 3.896291e-07 timers/immediate.js type="depth" thousands=2000 17.54 % ** 9.755389e-03 timers/immediate.js type="depth1" thousands=2000 17.09 % *** 7.176229e-04 timers/set-immediate-breadth-args.js millions=5 10.63 % * 4.250034e-02 timers/set-immediate-breadth.js millions=10 20.62 % *** 9.150439e-07 timers/set-immediate-depth-args.js millions=10 17.97 % *** 6.819135e-10 Backport-PR-URL: #18179 PR-URL: #17064 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Minwoo Jung <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent f276cd9 commit b5bc3f8

File tree

4 files changed

+69
-73
lines changed

4 files changed

+69
-73
lines changed

lib/timers.js

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
4646
const async_id_symbol = Symbol('asyncId');
4747
const trigger_async_id_symbol = Symbol('triggerAsyncId');
4848

49+
/* This is an Uint32Array for easier sharing with C++ land. */
50+
const scheduledImmediateCount = process._scheduledImmediateCount;
51+
delete process._scheduledImmediateCount;
52+
/* Kick off setImmediate processing */
53+
const activateImmediateCheck = process._activateImmediateCheck;
54+
delete process._activateImmediateCheck;
55+
4956
// Timeout values > TIMEOUT_MAX are set to 1.
5057
const TIMEOUT_MAX = 2147483647; // 2^31-1
5158

@@ -731,15 +738,9 @@ function processImmediate() {
731738
else
732739
immediate = next;
733740
}
734-
735-
// Only round-trip to C++ land if we have to. Calling clearImmediate() on an
736-
// immediate that's in |queue| is okay. Worst case is we make a superfluous
737-
// call to NeedImmediateCallbackSetter().
738-
if (!immediateQueue.head) {
739-
process._needImmediateCallback = false;
740-
}
741741
}
742742

743+
process._immediateCallback = processImmediate;
743744

744745
// An optimization so that the try/finally only de-optimizes (since at least v8
745746
// 4.7) what is in this smaller function.
@@ -751,13 +752,17 @@ function tryOnImmediate(immediate, oldTail) {
751752
runCallback(immediate);
752753
threw = false;
753754
} finally {
754-
// clearImmediate checks _callback === null for kDestroy hooks.
755755
immediate._callback = null;
756756
if (!threw)
757757
emitAfter(immediate[async_id_symbol]);
758-
if (async_hook_fields[kDestroy] > 0 && !immediate._destroyed) {
759-
emitDestroy(immediate[async_id_symbol]);
758+
759+
if (!immediate._destroyed) {
760760
immediate._destroyed = true;
761+
scheduledImmediateCount[0]--;
762+
763+
if (async_hook_fields[kDestroy] > 0) {
764+
emitDestroy(immediate[async_id_symbol]);
765+
}
761766
}
762767

763768
if (threw && immediate._idleNext) {
@@ -860,10 +865,9 @@ function createImmediate(args, callback) {
860865
immediate._argv = args;
861866
immediate._onImmediate = callback;
862867

863-
if (!process._needImmediateCallback) {
864-
process._needImmediateCallback = true;
865-
process._immediateCallback = processImmediate;
866-
}
868+
if (scheduledImmediateCount[0] === 0)
869+
activateImmediateCheck();
870+
scheduledImmediateCount[0]++;
867871

868872
immediateQueue.append(immediate);
869873

@@ -874,18 +878,16 @@ function createImmediate(args, callback) {
874878
exports.clearImmediate = function(immediate) {
875879
if (!immediate) return;
876880

877-
if (async_hook_fields[kDestroy] > 0 &&
878-
immediate._callback !== null &&
879-
!immediate._destroyed) {
880-
emitDestroy(immediate[async_id_symbol]);
881+
if (!immediate._destroyed) {
882+
scheduledImmediateCount[0]--;
881883
immediate._destroyed = true;
884+
885+
if (async_hook_fields[kDestroy] > 0) {
886+
emitDestroy(immediate[async_id_symbol]);
887+
}
882888
}
883889

884890
immediate._onImmediate = null;
885891

886892
immediateQueue.remove(immediate);
887-
888-
if (!immediateQueue.head) {
889-
process._needImmediateCallback = false;
890-
}
891893
};

src/env-inl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ inline Environment::Environment(IsolateData* isolate_data,
304304
abort_on_uncaught_exception_(false),
305305
emit_napi_warning_(true),
306306
makecallback_cntr_(0),
307+
scheduled_immediate_count_(isolate_, 1),
307308
#if HAVE_INSPECTOR
308309
inspector_agent_(new inspector::Agent(this)),
309310
#endif
@@ -533,6 +534,11 @@ inline void Environment::set_fs_stats_field_array(double* fields) {
533534
fs_stats_field_array_ = fields;
534535
}
535536

537+
inline AliasedBuffer<uint32_t, v8::Uint32Array>&
538+
Environment::scheduled_immediate_count() {
539+
return scheduled_immediate_count_;
540+
}
541+
536542
inline performance::performance_state* Environment::performance_state() {
537543
return performance_state_;
538544
}

src/env.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,8 @@ class Environment {
619619
inline double* fs_stats_field_array() const;
620620
inline void set_fs_stats_field_array(double* fields);
621621

622+
inline AliasedBuffer<uint32_t, v8::Uint32Array>& scheduled_immediate_count();
623+
622624
inline performance::performance_state* performance_state();
623625
inline std::map<std::string, uint64_t>* performance_marks();
624626

@@ -715,6 +717,8 @@ class Environment {
715717
size_t makecallback_cntr_;
716718
std::vector<double> destroy_async_id_list_;
717719

720+
AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;
721+
718722
performance::performance_state* performance_state_ = nullptr;
719723
std::map<std::string, uint64_t> performance_marks_;
720724

src/node.cc

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -387,25 +387,6 @@ static void PrintErrorString(const char* format, ...) {
387387
}
388388

389389

390-
static void CheckImmediate(uv_check_t* handle) {
391-
Environment* env = Environment::from_immediate_check_handle(handle);
392-
HandleScope scope(env->isolate());
393-
Context::Scope context_scope(env->context());
394-
MakeCallback(env->isolate(),
395-
env->process_object(),
396-
env->immediate_callback_string(),
397-
0,
398-
nullptr,
399-
{0, 0}).ToLocalChecked();
400-
}
401-
402-
403-
static void IdleImmediateDummy(uv_idle_t* handle) {
404-
// Do nothing. Only for maintaining event loop.
405-
// TODO(bnoordhuis) Maybe make libuv accept nullptr idle callbacks.
406-
}
407-
408-
409390
static inline const char *errno_string(int errorno) {
410391
#define ERRNO_CASE(e) case e: return #e;
411392
switch (errorno) {
@@ -3284,39 +3265,40 @@ static void DebugEnd(const FunctionCallbackInfo<Value>& args);
32843265

32853266
namespace {
32863267

3287-
void NeedImmediateCallbackGetter(Local<Name> property,
3288-
const PropertyCallbackInfo<Value>& info) {
3289-
Environment* env = Environment::GetCurrent(info);
3290-
const uv_check_t* immediate_check_handle = env->immediate_check_handle();
3291-
bool active = uv_is_active(
3292-
reinterpret_cast<const uv_handle_t*>(immediate_check_handle));
3293-
info.GetReturnValue().Set(active);
3268+
bool MaybeStopImmediate(Environment* env) {
3269+
if (env->scheduled_immediate_count()[0] == 0) {
3270+
uv_check_stop(env->immediate_check_handle());
3271+
uv_idle_stop(env->immediate_idle_handle());
3272+
return true;
3273+
}
3274+
return false;
32943275
}
32953276

3277+
void CheckImmediate(uv_check_t* handle) {
3278+
Environment* env = Environment::from_immediate_check_handle(handle);
3279+
HandleScope scope(env->isolate());
3280+
Context::Scope context_scope(env->context());
32963281

3297-
void NeedImmediateCallbackSetter(
3298-
Local<Name> property,
3299-
Local<Value> value,
3300-
const PropertyCallbackInfo<void>& info) {
3301-
Environment* env = Environment::GetCurrent(info);
3282+
if (MaybeStopImmediate(env))
3283+
return;
33023284

3303-
uv_check_t* immediate_check_handle = env->immediate_check_handle();
3304-
bool active = uv_is_active(
3305-
reinterpret_cast<const uv_handle_t*>(immediate_check_handle));
3285+
MakeCallback(env->isolate(),
3286+
env->process_object(),
3287+
env->immediate_callback_string(),
3288+
0,
3289+
nullptr,
3290+
{0, 0}).ToLocalChecked();
33063291

3307-
if (active == value->BooleanValue())
3308-
return;
3292+
MaybeStopImmediate(env);
3293+
}
33093294

3310-
uv_idle_t* immediate_idle_handle = env->immediate_idle_handle();
33113295

3312-
if (active) {
3313-
uv_check_stop(immediate_check_handle);
3314-
uv_idle_stop(immediate_idle_handle);
3315-
} else {
3316-
uv_check_start(immediate_check_handle, CheckImmediate);
3317-
// Idle handle is needed only to stop the event loop from blocking in poll.
3318-
uv_idle_start(immediate_idle_handle, IdleImmediateDummy);
3319-
}
3296+
void ActivateImmediateCheck(const FunctionCallbackInfo<Value>& args) {
3297+
Environment* env = Environment::GetCurrent(args);
3298+
uv_check_start(env->immediate_check_handle(), CheckImmediate);
3299+
// Idle handle is needed only to stop the event loop from blocking in poll.
3300+
uv_idle_start(env->immediate_idle_handle(),
3301+
[](uv_idle_t*){ /* do nothing, just keep the loop running */ });
33203302
}
33213303

33223304

@@ -3542,12 +3524,11 @@ void SetupProcessObject(Environment* env,
35423524
process->SetAccessor(FIXED_ONE_BYTE_STRING(env->isolate(), "ppid"),
35433525
GetParentProcessId);
35443526

3545-
auto need_immediate_callback_string =
3546-
FIXED_ONE_BYTE_STRING(env->isolate(), "_needImmediateCallback");
3547-
CHECK(process->SetAccessor(env->context(), need_immediate_callback_string,
3548-
NeedImmediateCallbackGetter,
3549-
NeedImmediateCallbackSetter,
3550-
env->as_external()).FromJust());
3527+
auto scheduled_immediate_count =
3528+
FIXED_ONE_BYTE_STRING(env->isolate(), "_scheduledImmediateCount");
3529+
CHECK(process->Set(env->context(),
3530+
scheduled_immediate_count,
3531+
env->scheduled_immediate_count().GetJSArray()).FromJust());
35513532

35523533
// -e, --eval
35533534
if (eval_string) {
@@ -3673,6 +3654,9 @@ void SetupProcessObject(Environment* env,
36733654
env->as_external()).FromJust());
36743655

36753656
// define various internal methods
3657+
env->SetMethod(process,
3658+
"_activateImmediateCheck",
3659+
ActivateImmediateCheck);
36763660
env->SetMethod(process,
36773661
"_startProfilerIdleNotifier",
36783662
StartProfilerIdleNotifier);

0 commit comments

Comments
 (0)