Skip to content

Commit ae558af

Browse files
committed
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 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 ca3f9b7 commit ae558af

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
@@ -47,6 +47,13 @@ const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
4747
const async_id_symbol = Symbol('asyncId');
4848
const trigger_async_id_symbol = Symbol('triggerAsyncId');
4949

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

@@ -742,15 +749,9 @@ function processImmediate() {
742749
else
743750
immediate = next;
744751
}
745-
746-
// Only round-trip to C++ land if we have to. Calling clearImmediate() on an
747-
// immediate that's in |queue| is okay. Worst case is we make a superfluous
748-
// call to NeedImmediateCallbackSetter().
749-
if (!immediateQueue.head) {
750-
process._needImmediateCallback = false;
751-
}
752752
}
753753

754+
process._immediateCallback = processImmediate;
754755

755756
// An optimization so that the try/finally only de-optimizes (since at least v8
756757
// 4.7) what is in this smaller function.
@@ -762,13 +763,17 @@ function tryOnImmediate(immediate, oldTail) {
762763
runCallback(immediate);
763764
threw = false;
764765
} finally {
765-
// clearImmediate checks _onImmediate === null for kDestroy hooks.
766766
immediate._onImmediate = null;
767767
if (!threw)
768768
emitAfter(immediate[async_id_symbol]);
769-
if (async_hook_fields[kDestroy] > 0 && !immediate._destroyed) {
770-
emitDestroy(immediate[async_id_symbol]);
769+
770+
if (!immediate._destroyed) {
771771
immediate._destroyed = true;
772+
scheduledImmediateCount[0]--;
773+
774+
if (async_hook_fields[kDestroy] > 0) {
775+
emitDestroy(immediate[async_id_symbol]);
776+
}
772777
}
773778

774779
if (threw && immediate._idleNext) {
@@ -870,10 +875,9 @@ function createImmediate(args, callback) {
870875
immediate._argv = args;
871876
immediate._onImmediate = callback;
872877

873-
if (!process._needImmediateCallback) {
874-
process._needImmediateCallback = true;
875-
process._immediateCallback = processImmediate;
876-
}
878+
if (scheduledImmediateCount[0] === 0)
879+
activateImmediateCheck();
880+
scheduledImmediateCount[0]++;
877881

878882
immediateQueue.append(immediate);
879883

@@ -884,18 +888,16 @@ function createImmediate(args, callback) {
884888
exports.clearImmediate = function(immediate) {
885889
if (!immediate) return;
886890

887-
if (async_hook_fields[kDestroy] > 0 &&
888-
immediate._onImmediate !== null &&
889-
!immediate._destroyed) {
890-
emitDestroy(immediate[async_id_symbol]);
891+
if (!immediate._destroyed) {
892+
scheduledImmediateCount[0]--;
891893
immediate._destroyed = true;
894+
895+
if (async_hook_fields[kDestroy] > 0) {
896+
emitDestroy(immediate[async_id_symbol]);
897+
}
892898
}
893899

894900
immediate._onImmediate = null;
895901

896902
immediateQueue.remove(immediate);
897-
898-
if (!immediateQueue.head) {
899-
process._needImmediateCallback = false;
900-
}
901903
};

src/env-inl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ inline Environment::Environment(IsolateData* isolate_data,
283283
abort_on_uncaught_exception_(false),
284284
emit_napi_warning_(true),
285285
makecallback_cntr_(0),
286+
scheduled_immediate_count_(isolate_, 1),
286287
#if HAVE_INSPECTOR
287288
inspector_agent_(new inspector::Agent(this)),
288289
#endif
@@ -512,6 +513,11 @@ inline void Environment::set_fs_stats_field_array(double* fields) {
512513
fs_stats_field_array_ = fields;
513514
}
514515

516+
inline AliasedBuffer<uint32_t, v8::Uint32Array>&
517+
Environment::scheduled_immediate_count() {
518+
return scheduled_immediate_count_;
519+
}
520+
515521
inline performance::performance_state* Environment::performance_state() {
516522
return performance_state_;
517523
}

src/env.h

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

625+
inline AliasedBuffer<uint32_t, v8::Uint32Array>& scheduled_immediate_count();
626+
625627
inline performance::performance_state* performance_state();
626628
inline std::map<std::string, uint64_t>* performance_marks();
627629

@@ -731,6 +733,8 @@ class Environment {
731733
size_t makecallback_cntr_;
732734
std::vector<double> destroy_async_id_list_;
733735

736+
AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;
737+
734738
performance::performance_state* performance_state_ = nullptr;
735739
std::map<std::string, uint64_t> performance_marks_;
736740

src/node.cc

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -400,25 +400,6 @@ static void PrintErrorString(const char* format, ...) {
400400
va_end(ap);
401401
}
402402

403-
404-
static void CheckImmediate(uv_check_t* handle) {
405-
Environment* env = Environment::from_immediate_check_handle(handle);
406-
HandleScope scope(env->isolate());
407-
Context::Scope context_scope(env->context());
408-
MakeCallback(env->isolate(),
409-
env->process_object(),
410-
env->immediate_callback_string(),
411-
0,
412-
nullptr,
413-
{0, 0}).ToLocalChecked();
414-
}
415-
416-
417-
static void IdleImmediateDummy(uv_idle_t* handle) {
418-
// Do nothing. Only for maintaining event loop.
419-
// TODO(bnoordhuis) Maybe make libuv accept nullptr idle callbacks.
420-
}
421-
422403
const char *signo_string(int signo) {
423404
#define SIGNO_CASE(e) case e: return #e;
424405
switch (signo) {
@@ -3019,39 +3000,40 @@ static void DebugEnd(const FunctionCallbackInfo<Value>& args);
30193000

30203001
namespace {
30213002

3022-
void NeedImmediateCallbackGetter(Local<Name> property,
3023-
const PropertyCallbackInfo<Value>& info) {
3024-
Environment* env = Environment::GetCurrent(info);
3025-
const uv_check_t* immediate_check_handle = env->immediate_check_handle();
3026-
bool active = uv_is_active(
3027-
reinterpret_cast<const uv_handle_t*>(immediate_check_handle));
3028-
info.GetReturnValue().Set(active);
3003+
bool MaybeStopImmediate(Environment* env) {
3004+
if (env->scheduled_immediate_count()[0] == 0) {
3005+
uv_check_stop(env->immediate_check_handle());
3006+
uv_idle_stop(env->immediate_idle_handle());
3007+
return true;
3008+
}
3009+
return false;
30293010
}
30303011

3012+
void CheckImmediate(uv_check_t* handle) {
3013+
Environment* env = Environment::from_immediate_check_handle(handle);
3014+
HandleScope scope(env->isolate());
3015+
Context::Scope context_scope(env->context());
30313016

3032-
void NeedImmediateCallbackSetter(
3033-
Local<Name> property,
3034-
Local<Value> value,
3035-
const PropertyCallbackInfo<void>& info) {
3036-
Environment* env = Environment::GetCurrent(info);
3017+
if (MaybeStopImmediate(env))
3018+
return;
30373019

3038-
uv_check_t* immediate_check_handle = env->immediate_check_handle();
3039-
bool active = uv_is_active(
3040-
reinterpret_cast<const uv_handle_t*>(immediate_check_handle));
3020+
MakeCallback(env->isolate(),
3021+
env->process_object(),
3022+
env->immediate_callback_string(),
3023+
0,
3024+
nullptr,
3025+
{0, 0}).ToLocalChecked();
30413026

3042-
if (active == value->BooleanValue())
3043-
return;
3027+
MaybeStopImmediate(env);
3028+
}
30443029

3045-
uv_idle_t* immediate_idle_handle = env->immediate_idle_handle();
30463030

3047-
if (active) {
3048-
uv_check_stop(immediate_check_handle);
3049-
uv_idle_stop(immediate_idle_handle);
3050-
} else {
3051-
uv_check_start(immediate_check_handle, CheckImmediate);
3052-
// Idle handle is needed only to stop the event loop from blocking in poll.
3053-
uv_idle_start(immediate_idle_handle, IdleImmediateDummy);
3054-
}
3031+
void ActivateImmediateCheck(const FunctionCallbackInfo<Value>& args) {
3032+
Environment* env = Environment::GetCurrent(args);
3033+
uv_check_start(env->immediate_check_handle(), CheckImmediate);
3034+
// Idle handle is needed only to stop the event loop from blocking in poll.
3035+
uv_idle_start(env->immediate_idle_handle(),
3036+
[](uv_idle_t*){ /* do nothing, just keep the loop running */ });
30553037
}
30563038

30573039

@@ -3277,12 +3259,11 @@ void SetupProcessObject(Environment* env,
32773259
FIXED_ONE_BYTE_STRING(env->isolate(), "ppid"),
32783260
GetParentProcessId).FromJust());
32793261

3280-
auto need_immediate_callback_string =
3281-
FIXED_ONE_BYTE_STRING(env->isolate(), "_needImmediateCallback");
3282-
CHECK(process->SetAccessor(env->context(), need_immediate_callback_string,
3283-
NeedImmediateCallbackGetter,
3284-
NeedImmediateCallbackSetter,
3285-
env->as_external()).FromJust());
3262+
auto scheduled_immediate_count =
3263+
FIXED_ONE_BYTE_STRING(env->isolate(), "_scheduledImmediateCount");
3264+
CHECK(process->Set(env->context(),
3265+
scheduled_immediate_count,
3266+
env->scheduled_immediate_count().GetJSArray()).FromJust());
32863267

32873268
// -e, --eval
32883269
if (eval_string) {
@@ -3408,6 +3389,9 @@ void SetupProcessObject(Environment* env,
34083389
env->as_external()).FromJust());
34093390

34103391
// define various internal methods
3392+
env->SetMethod(process,
3393+
"_activateImmediateCheck",
3394+
ActivateImmediateCheck);
34113395
env->SetMethod(process,
34123396
"_startProfilerIdleNotifier",
34133397
StartProfilerIdleNotifier);

0 commit comments

Comments
 (0)