Skip to content

Commit 0f9efef

Browse files
committed
timers: refactor timer list processing
Instead of using kOnTimeout index to track a special list processing function, just pass in a function to C++ at startup that executes all handles and determines which function to call. This change improves the performance of unpooled timeouts by roughly 20%, as well as makes the unref/ref processing easier to follow. PR-URL: #18582 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Vladimir de Turckheim <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
1 parent e5f101f commit 0f9efef

File tree

5 files changed

+67
-28
lines changed

5 files changed

+67
-28
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
4+
// The following benchmark sets up n * 1e6 unpooled timeouts,
5+
// then measures their execution on the next uv tick
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e6],
9+
});
10+
11+
function main({ n }) {
12+
let count = 0;
13+
14+
// Function tracking on the hidden class in V8 can cause misleading
15+
// results in this benchmark if only a single function is used —
16+
// alternate between two functions for a fairer benchmark
17+
18+
function cb() {
19+
count++;
20+
if (count === n)
21+
bench.end(n);
22+
}
23+
function cb2() {
24+
count++;
25+
if (count === n)
26+
bench.end(n);
27+
}
28+
29+
for (var i = 0; i < n; i++) {
30+
// unref().ref() will cause each of these timers to
31+
// allocate their own handle
32+
setTimeout(i % 2 ? cb : cb2, 1).unref().ref();
33+
}
34+
35+
bench.start();
36+
}

lib/timers.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
const async_wrap = process.binding('async_wrap');
2525
const {
2626
Timer: TimerWrap,
27-
setImmediateCallback,
27+
setupTimers,
2828
} = process.binding('timer_wrap');
2929
const L = require('internal/linkedlist');
3030
const timerInternals = require('internal/timers');
@@ -34,7 +34,6 @@ const assert = require('assert');
3434
const util = require('util');
3535
const errors = require('internal/errors');
3636
const debug = util.debuglog('timer');
37-
const kOnTimeout = TimerWrap.kOnTimeout | 0;
3837
// Two arrays that share state between C++ and JS.
3938
const { async_hook_fields, async_id_fields } = async_wrap;
4039
const {
@@ -57,7 +56,7 @@ const kRefCount = 1;
5756
const kHasOutstanding = 2;
5857

5958
const [immediateInfo, toggleImmediateRef] =
60-
setImmediateCallback(processImmediate);
59+
setupTimers(processImmediate, processTimers);
6160

6261
const kRefed = Symbol('refed');
6362

@@ -221,10 +220,14 @@ function TimersList(msecs, unrefed) {
221220
timer.start(msecs);
222221
}
223222

224-
// adds listOnTimeout to the C++ object prototype, as
225-
// V8 would not inline it otherwise.
226-
TimerWrap.prototype[kOnTimeout] = function listOnTimeout(now) {
227-
const list = this._list;
223+
function processTimers(now) {
224+
if (this.owner)
225+
return unrefdHandle(this.owner, now);
226+
return listOnTimeout(this, now);
227+
}
228+
229+
function listOnTimeout(handle, now) {
230+
const list = handle._list;
228231
const msecs = list.msecs;
229232

230233
debug('timeout callback %d', msecs);
@@ -241,7 +244,7 @@ TimerWrap.prototype[kOnTimeout] = function listOnTimeout(now) {
241244
if (timeRemaining <= 0) {
242245
timeRemaining = 1;
243246
}
244-
this.start(timeRemaining);
247+
handle.start(timeRemaining);
245248
debug('%d list wait because diff is %d', msecs, diff);
246249
return true;
247250
}
@@ -280,11 +283,11 @@ TimerWrap.prototype[kOnTimeout] = function listOnTimeout(now) {
280283

281284
// Do not close the underlying handle if its ownership has changed
282285
// (e.g it was unrefed in its callback).
283-
if (!this.owner)
284-
this.close();
286+
if (!handle.owner)
287+
handle.close();
285288

286289
return true;
287-
};
290+
}
288291

289292

290293
// An optimization so that the try/finally only de-optimizes (since at least v8
@@ -516,18 +519,17 @@ exports.clearInterval = function(timer) {
516519
};
517520

518521

519-
function unrefdHandle(now) {
522+
function unrefdHandle(timer, now) {
520523
try {
521524
// Don't attempt to call the callback if it is not a function.
522-
if (typeof this.owner._onTimeout === 'function') {
523-
tryOnTimeout(this.owner, now);
525+
if (typeof timer._onTimeout === 'function') {
526+
tryOnTimeout(timer, now);
524527
}
525528
} finally {
526529
// Make sure we clean up if the callback is no longer a function
527530
// even if the timer is an interval.
528-
if (!this.owner._repeat ||
529-
typeof this.owner._onTimeout !== 'function') {
530-
this.owner.close();
531+
if (!timer._repeat || typeof timer._onTimeout !== 'function') {
532+
timer.close();
531533
}
532534
}
533535

@@ -557,7 +559,6 @@ Timeout.prototype.unref = function() {
557559

558560
this._handle = handle || new TimerWrap();
559561
this._handle.owner = this;
560-
this._handle[kOnTimeout] = unrefdHandle;
561562
this._handle.start(delay);
562563
this._handle.unref();
563564
}
@@ -581,7 +582,6 @@ Timeout.prototype.close = function() {
581582
}
582583

583584
this._idleTimeout = -1;
584-
this._handle[kOnTimeout] = null;
585585
this._handle.close();
586586
} else {
587587
unenroll(this);

src/env.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ class ModuleWrap;
308308
V(secure_context_constructor_template, v8::FunctionTemplate) \
309309
V(tcp_constructor_template, v8::FunctionTemplate) \
310310
V(tick_callback_function, v8::Function) \
311+
V(timers_callback_function, v8::Function) \
311312
V(tls_wrap_constructor_function, v8::Function) \
312313
V(tty_constructor_template, v8::FunctionTemplate) \
313314
V(udp_constructor_function, v8::Function) \

src/timer_wrap.cc

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ using v8::Object;
4141
using v8::String;
4242
using v8::Value;
4343

44-
const uint32_t kOnTimeout = 0;
45-
4644
class TimerWrap : public HandleWrap {
4745
public:
4846
static void Initialize(Local<Object> target,
@@ -53,8 +51,6 @@ class TimerWrap : public HandleWrap {
5351
Local<String> timerString = FIXED_ONE_BYTE_STRING(env->isolate(), "Timer");
5452
constructor->InstanceTemplate()->SetInternalFieldCount(1);
5553
constructor->SetClassName(timerString);
56-
constructor->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnTimeout"),
57-
Integer::New(env->isolate(), kOnTimeout));
5854

5955
env->SetTemplateMethod(constructor, "now", Now);
6056

@@ -71,18 +67,22 @@ class TimerWrap : public HandleWrap {
7167
target->Set(timerString, constructor->GetFunction());
7268

7369
target->Set(env->context(),
74-
FIXED_ONE_BYTE_STRING(env->isolate(), "setImmediateCallback"),
75-
env->NewFunctionTemplate(SetImmediateCallback)
70+
FIXED_ONE_BYTE_STRING(env->isolate(), "setupTimers"),
71+
env->NewFunctionTemplate(SetupTimers)
7672
->GetFunction(env->context()).ToLocalChecked()).FromJust();
7773
}
7874

7975
size_t self_size() const override { return sizeof(*this); }
8076

8177
private:
82-
static void SetImmediateCallback(const FunctionCallbackInfo<Value>& args) {
78+
static void SetupTimers(const FunctionCallbackInfo<Value>& args) {
8379
CHECK(args[0]->IsFunction());
80+
CHECK(args[1]->IsFunction());
8481
auto env = Environment::GetCurrent(args);
82+
8583
env->set_immediate_callback_function(args[0].As<Function>());
84+
env->set_timers_callback_function(args[1].As<Function>());
85+
8686
auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
8787
Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
8888
};
@@ -142,7 +142,8 @@ class TimerWrap : public HandleWrap {
142142
Local<Value> args[1];
143143
do {
144144
args[0] = env->GetNow();
145-
ret = wrap->MakeCallback(kOnTimeout, 1, args).ToLocalChecked();
145+
ret = wrap->MakeCallback(env->timers_callback_function(), 1, args)
146+
.ToLocalChecked();
146147
} while (ret->IsUndefined() &&
147148
!env->tick_info()->has_thrown() &&
148149
wrap->object()->Get(env->context(),

test/message/timeout_throw.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ ReferenceError: undefined_reference_error_maker is not defined
55
at Timeout._onTimeout (*test*message*timeout_throw.js:*:*)
66
at ontimeout (timers.js:*:*)
77
at tryOnTimeout (timers.js:*:*)
8-
at Timer.listOnTimeout (timers.js:*:*)
8+
at listOnTimeout (timers.js:*:*)
9+
at Timer.processTimers (timers.js:*:*)

0 commit comments

Comments
 (0)