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: https://github.com/nodejs/node/pull/18582
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Vladimir de Turckheim <vlad2t@hotmail.com>
Reviewed-By: Evan Lucas <evanlucas@me.com>
This commit is contained in:
Anatoli Papirovski 2018-02-05 14:29:32 -05:00
parent e5f101fe7b
commit 0f9efef05d
No known key found for this signature in database
GPG Key ID: 614E2E1ABEB4B2C0
5 changed files with 67 additions and 28 deletions

View File

@ -0,0 +1,36 @@
'use strict';
const common = require('../common.js');
// The following benchmark sets up n * 1e6 unpooled timeouts,
// then measures their execution on the next uv tick
const bench = common.createBenchmark(main, {
n: [1e6],
});
function main({ n }) {
let count = 0;
// Function tracking on the hidden class in V8 can cause misleading
// results in this benchmark if only a single function is used —
// alternate between two functions for a fairer benchmark
function cb() {
count++;
if (count === n)
bench.end(n);
}
function cb2() {
count++;
if (count === n)
bench.end(n);
}
for (var i = 0; i < n; i++) {
// unref().ref() will cause each of these timers to
// allocate their own handle
setTimeout(i % 2 ? cb : cb2, 1).unref().ref();
}
bench.start();
}

View File

@ -24,7 +24,7 @@
const async_wrap = process.binding('async_wrap'); const async_wrap = process.binding('async_wrap');
const { const {
Timer: TimerWrap, Timer: TimerWrap,
setImmediateCallback, setupTimers,
} = process.binding('timer_wrap'); } = process.binding('timer_wrap');
const L = require('internal/linkedlist'); const L = require('internal/linkedlist');
const timerInternals = require('internal/timers'); const timerInternals = require('internal/timers');
@ -34,7 +34,6 @@ const assert = require('assert');
const util = require('util'); const util = require('util');
const errors = require('internal/errors'); const errors = require('internal/errors');
const debug = util.debuglog('timer'); const debug = util.debuglog('timer');
const kOnTimeout = TimerWrap.kOnTimeout | 0;
// Two arrays that share state between C++ and JS. // Two arrays that share state between C++ and JS.
const { async_hook_fields, async_id_fields } = async_wrap; const { async_hook_fields, async_id_fields } = async_wrap;
const { const {
@ -57,7 +56,7 @@ const kRefCount = 1;
const kHasOutstanding = 2; const kHasOutstanding = 2;
const [immediateInfo, toggleImmediateRef] = const [immediateInfo, toggleImmediateRef] =
setImmediateCallback(processImmediate); setupTimers(processImmediate, processTimers);
const kRefed = Symbol('refed'); const kRefed = Symbol('refed');
@ -221,10 +220,14 @@ function TimersList(msecs, unrefed) {
timer.start(msecs); timer.start(msecs);
} }
// adds listOnTimeout to the C++ object prototype, as function processTimers(now) {
// V8 would not inline it otherwise. if (this.owner)
TimerWrap.prototype[kOnTimeout] = function listOnTimeout(now) { return unrefdHandle(this.owner, now);
const list = this._list; return listOnTimeout(this, now);
}
function listOnTimeout(handle, now) {
const list = handle._list;
const msecs = list.msecs; const msecs = list.msecs;
debug('timeout callback %d', msecs); debug('timeout callback %d', msecs);
@ -241,7 +244,7 @@ TimerWrap.prototype[kOnTimeout] = function listOnTimeout(now) {
if (timeRemaining <= 0) { if (timeRemaining <= 0) {
timeRemaining = 1; timeRemaining = 1;
} }
this.start(timeRemaining); handle.start(timeRemaining);
debug('%d list wait because diff is %d', msecs, diff); debug('%d list wait because diff is %d', msecs, diff);
return true; return true;
} }
@ -280,11 +283,11 @@ TimerWrap.prototype[kOnTimeout] = function listOnTimeout(now) {
// Do not close the underlying handle if its ownership has changed // Do not close the underlying handle if its ownership has changed
// (e.g it was unrefed in its callback). // (e.g it was unrefed in its callback).
if (!this.owner) if (!handle.owner)
this.close(); handle.close();
return true; return true;
}; }
// An optimization so that the try/finally only de-optimizes (since at least v8 // An optimization so that the try/finally only de-optimizes (since at least v8
@ -516,18 +519,17 @@ exports.clearInterval = function(timer) {
}; };
function unrefdHandle(now) { function unrefdHandle(timer, now) {
try { try {
// Don't attempt to call the callback if it is not a function. // Don't attempt to call the callback if it is not a function.
if (typeof this.owner._onTimeout === 'function') { if (typeof timer._onTimeout === 'function') {
tryOnTimeout(this.owner, now); tryOnTimeout(timer, now);
} }
} finally { } finally {
// Make sure we clean up if the callback is no longer a function // Make sure we clean up if the callback is no longer a function
// even if the timer is an interval. // even if the timer is an interval.
if (!this.owner._repeat || if (!timer._repeat || typeof timer._onTimeout !== 'function') {
typeof this.owner._onTimeout !== 'function') { timer.close();
this.owner.close();
} }
} }
@ -557,7 +559,6 @@ Timeout.prototype.unref = function() {
this._handle = handle || new TimerWrap(); this._handle = handle || new TimerWrap();
this._handle.owner = this; this._handle.owner = this;
this._handle[kOnTimeout] = unrefdHandle;
this._handle.start(delay); this._handle.start(delay);
this._handle.unref(); this._handle.unref();
} }
@ -581,7 +582,6 @@ Timeout.prototype.close = function() {
} }
this._idleTimeout = -1; this._idleTimeout = -1;
this._handle[kOnTimeout] = null;
this._handle.close(); this._handle.close();
} else { } else {
unenroll(this); unenroll(this);

View File

@ -308,6 +308,7 @@ class ModuleWrap;
V(secure_context_constructor_template, v8::FunctionTemplate) \ V(secure_context_constructor_template, v8::FunctionTemplate) \
V(tcp_constructor_template, v8::FunctionTemplate) \ V(tcp_constructor_template, v8::FunctionTemplate) \
V(tick_callback_function, v8::Function) \ V(tick_callback_function, v8::Function) \
V(timers_callback_function, v8::Function) \
V(tls_wrap_constructor_function, v8::Function) \ V(tls_wrap_constructor_function, v8::Function) \
V(tty_constructor_template, v8::FunctionTemplate) \ V(tty_constructor_template, v8::FunctionTemplate) \
V(udp_constructor_function, v8::Function) \ V(udp_constructor_function, v8::Function) \

View File

@ -41,8 +41,6 @@ using v8::Object;
using v8::String; using v8::String;
using v8::Value; using v8::Value;
const uint32_t kOnTimeout = 0;
class TimerWrap : public HandleWrap { class TimerWrap : public HandleWrap {
public: public:
static void Initialize(Local<Object> target, static void Initialize(Local<Object> target,
@ -53,8 +51,6 @@ class TimerWrap : public HandleWrap {
Local<String> timerString = FIXED_ONE_BYTE_STRING(env->isolate(), "Timer"); Local<String> timerString = FIXED_ONE_BYTE_STRING(env->isolate(), "Timer");
constructor->InstanceTemplate()->SetInternalFieldCount(1); constructor->InstanceTemplate()->SetInternalFieldCount(1);
constructor->SetClassName(timerString); constructor->SetClassName(timerString);
constructor->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnTimeout"),
Integer::New(env->isolate(), kOnTimeout));
env->SetTemplateMethod(constructor, "now", Now); env->SetTemplateMethod(constructor, "now", Now);
@ -71,18 +67,22 @@ class TimerWrap : public HandleWrap {
target->Set(timerString, constructor->GetFunction()); target->Set(timerString, constructor->GetFunction());
target->Set(env->context(), target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "setImmediateCallback"), FIXED_ONE_BYTE_STRING(env->isolate(), "setupTimers"),
env->NewFunctionTemplate(SetImmediateCallback) env->NewFunctionTemplate(SetupTimers)
->GetFunction(env->context()).ToLocalChecked()).FromJust(); ->GetFunction(env->context()).ToLocalChecked()).FromJust();
} }
size_t self_size() const override { return sizeof(*this); } size_t self_size() const override { return sizeof(*this); }
private: private:
static void SetImmediateCallback(const FunctionCallbackInfo<Value>& args) { static void SetupTimers(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsFunction()); CHECK(args[0]->IsFunction());
CHECK(args[1]->IsFunction());
auto env = Environment::GetCurrent(args); auto env = Environment::GetCurrent(args);
env->set_immediate_callback_function(args[0].As<Function>()); env->set_immediate_callback_function(args[0].As<Function>());
env->set_timers_callback_function(args[1].As<Function>());
auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) { auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue()); Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
}; };
@ -142,7 +142,8 @@ class TimerWrap : public HandleWrap {
Local<Value> args[1]; Local<Value> args[1];
do { do {
args[0] = env->GetNow(); args[0] = env->GetNow();
ret = wrap->MakeCallback(kOnTimeout, 1, args).ToLocalChecked(); ret = wrap->MakeCallback(env->timers_callback_function(), 1, args)
.ToLocalChecked();
} while (ret->IsUndefined() && } while (ret->IsUndefined() &&
!env->tick_info()->has_thrown() && !env->tick_info()->has_thrown() &&
wrap->object()->Get(env->context(), wrap->object()->Get(env->context(),

View File

@ -5,4 +5,5 @@ ReferenceError: undefined_reference_error_maker is not defined
at Timeout._onTimeout (*test*message*timeout_throw.js:*:*) at Timeout._onTimeout (*test*message*timeout_throw.js:*:*)
at ontimeout (timers.js:*:*) at ontimeout (timers.js:*:*)
at tryOnTimeout (timers.js:*:*) at tryOnTimeout (timers.js:*:*)
at Timer.listOnTimeout (timers.js:*:*) at listOnTimeout (timers.js:*:*)
at Timer.processTimers (timers.js:*:*)