diff --git a/benchmark/fs/bench-readdir.js b/benchmark/fs/bench-readdir.js new file mode 100644 index 00000000000000..2f0eab6a821f42 --- /dev/null +++ b/benchmark/fs/bench-readdir.js @@ -0,0 +1,22 @@ +'use strict'; + +const common = require('../common'); +const fs = require('fs'); + +const bench = common.createBenchmark(main, { + n: [1e4], +}); + + +function main(conf) { + const n = conf.n >>> 0; + + bench.start(); + (function r(cntr) { + if (--cntr <= 0) + return bench.end(n); + fs.readdir(__dirname + '/../../lib/', function() { + r(cntr); + }); + }(n)); +} diff --git a/benchmark/fs/bench-readdirSync.js b/benchmark/fs/bench-readdirSync.js new file mode 100644 index 00000000000000..9f89649138cd20 --- /dev/null +++ b/benchmark/fs/bench-readdirSync.js @@ -0,0 +1,19 @@ +'use strict'; + +const common = require('../common'); +const fs = require('fs'); + +const bench = common.createBenchmark(main, { + n: [1e4], +}); + + +function main(conf) { + const n = conf.n >>> 0; + + bench.start(); + for (var i = 0; i < n; i++) { + fs.readdirSync(__dirname + '/../../lib/'); + } + bench.end(n); +} diff --git a/benchmark/http/bench-parser.js b/benchmark/http/bench-parser.js new file mode 100644 index 00000000000000..989d9a994fa04e --- /dev/null +++ b/benchmark/http/bench-parser.js @@ -0,0 +1,55 @@ +'use strict'; + +const common = require('../common'); +const HTTPParser = process.binding('http_parser').HTTPParser; +const REQUEST = HTTPParser.REQUEST; +const kOnHeaders = HTTPParser.kOnHeaders | 0; +const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; +const kOnBody = HTTPParser.kOnBody | 0; +const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; +const CRLF = '\r\n'; + +const bench = common.createBenchmark(main, { + fields: [4, 8, 16, 32], + n: [1e5], +}); + + +function main(conf) { + const fields = conf.fields >>> 0; + const n = conf.n >>> 0; + var header = `GET /hello HTTP/1.1${CRLF}Content-Type: text/plain${CRLF}`; + + for (var i = 0; i < fields; i++) { + header += `X-Filler${i}: ${Math.random().toString(36).substr(2)}${CRLF}`; + } + header += CRLF; + + processHeader(new Buffer(header), n); +} + + +function processHeader(header, n) { + const parser = newParser(REQUEST); + + bench.start(); + for (var i = 0; i < n; i++) { + parser.execute(header, 0, header.length); + parser.reinitialize(REQUEST); + } + bench.end(n); +} + + +function newParser(type) { + const parser = new HTTPParser(type); + + parser.headers = []; + + parser[kOnHeaders] = function() { }; + parser[kOnHeadersComplete] = function() { }; + parser[kOnBody] = function() { }; + parser[kOnMessageComplete] = function() { }; + + return parser; +} diff --git a/benchmark/misc/bench-env.js b/benchmark/misc/bench-env.js new file mode 100644 index 00000000000000..66f966f587bb7f --- /dev/null +++ b/benchmark/misc/bench-env.js @@ -0,0 +1,18 @@ +'use strict'; + +const common = require('../common'); + +const bench = common.createBenchmark(main, { + n: [1e5], +}); + + +function main(conf) { + const n = conf.n >>> 0; + bench.start(); + for (var i = 0; i < n; i++) { + // Access every item in object to process values. + Object.keys(process.env); + } + bench.end(n); +} diff --git a/benchmark/misc/bench-hrtime.js b/benchmark/misc/bench-hrtime.js new file mode 100644 index 00000000000000..661dff43b0103c --- /dev/null +++ b/benchmark/misc/bench-hrtime.js @@ -0,0 +1,18 @@ +'use strict'; + +const common = require('../common'); + +const bench = common.createBenchmark(main, { + n: [1e6] +}); + + +function main(conf) { + const n = conf.n >>> 0; + + bench.start(); + for (var i = 0; i < n; i++) { + process.hrtime(); + } + bench.end(n); +} diff --git a/src/env.h b/src/env.h index 93f7c47c9919a3..8599e1495eed20 100644 --- a/src/env.h +++ b/src/env.h @@ -38,6 +38,12 @@ namespace node { #define NODE_ISOLATE_SLOT 3 #endif +// The number of items passed to push_values_to_array_function has diminishing +// returns around 8. This should be used at all call sites using said function. +#ifndef NODE_PUSH_VAL_TO_ARRAY_MAX +#define NODE_PUSH_VAL_TO_ARRAY_MAX 8 +#endif + // Strings are per-isolate primitives but Environment proxies them // for the sake of convenience. Strings should be ASCII-only. #define PER_ISOLATE_STRING_PROPERTIES(V) \ @@ -231,12 +237,11 @@ namespace node { V(zero_return_string, "ZERO_RETURN") \ #define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \ - V(add_properties_by_index_function, v8::Function) \ V(as_external, v8::External) \ + V(async_hooks_destroy_function, v8::Function) \ V(async_hooks_init_function, v8::Function) \ - V(async_hooks_pre_function, v8::Function) \ V(async_hooks_post_function, v8::Function) \ - V(async_hooks_destroy_function, v8::Function) \ + V(async_hooks_pre_function, v8::Function) \ V(binding_cache_object, v8::Object) \ V(buffer_constructor_function, v8::Function) \ V(buffer_prototype_object, v8::Object) \ @@ -249,6 +254,7 @@ namespace node { V(pipe_constructor_template, v8::FunctionTemplate) \ V(process_object, v8::Object) \ V(promise_reject_function, v8::Function) \ + V(push_values_to_array_function, v8::Function) \ V(script_context_constructor_template, v8::FunctionTemplate) \ V(script_data_constructor_function, v8::Function) \ V(secure_context_constructor_template, v8::FunctionTemplate) \ diff --git a/src/node.cc b/src/node.cc index 4e591ff5af6053..321a2857a112c4 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1027,7 +1027,7 @@ void SetupProcessObject(const FunctionCallbackInfo<Value>& args) { CHECK(args[0]->IsFunction()); - env->set_add_properties_by_index_function(args[0].As<Function>()); + env->set_push_values_to_array_function(args[0].As<Function>()); env->process_object()->Delete( FIXED_ONE_BYTE_STRING(env->isolate(), "_setupProcessObject")); } @@ -1571,28 +1571,22 @@ static void GetActiveRequests(const FunctionCallbackInfo<Value>& args) { Local<Array> ary = Array::New(args.GetIsolate()); Local<Context> ctx = env->context(); - Local<Function> fn = env->add_properties_by_index_function(); - static const size_t argc = 8; - Local<Value> argv[argc]; - size_t i = 0; + Local<Function> fn = env->push_values_to_array_function(); + Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t idx = 0; for (auto w : *env->req_wrap_queue()) { - if (w->persistent().IsEmpty() == false) { - argv[i++ % argc] = w->object(); - if ((i % argc) == 0) { - HandleScope scope(env->isolate()); - fn->Call(ctx, ary, argc, argv).ToLocalChecked(); - for (auto&& arg : argv) { - arg = Local<Value>(); - } - } + if (w->persistent().IsEmpty()) + continue; + argv[idx] = w->object(); + if (++idx >= ARRAY_SIZE(argv)) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + idx = 0; } } - const size_t remainder = i % argc; - if (remainder > 0) { - HandleScope scope(env->isolate()); - fn->Call(ctx, ary, remainder, argv).ToLocalChecked(); + if (idx > 0) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); } args.GetReturnValue().Set(ary); @@ -1605,7 +1599,10 @@ void GetActiveHandles(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); Local<Array> ary = Array::New(env->isolate()); - int i = 0; + Local<Context> ctx = env->context(); + Local<Function> fn = env->push_values_to_array_function(); + Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t idx = 0; Local<String> owner_sym = env->owner_string(); @@ -1616,7 +1613,14 @@ void GetActiveHandles(const FunctionCallbackInfo<Value>& args) { Local<Value> owner = object->Get(owner_sym); if (owner->IsUndefined()) owner = object; - ary->Set(i++, owner); + argv[idx] = owner; + if (++idx >= ARRAY_SIZE(argv)) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); + idx = 0; + } + } + if (idx > 0) { + fn->Call(ctx, ary, idx, argv).ToLocalChecked(); } args.GetReturnValue().Set(ary); @@ -2092,22 +2096,23 @@ void Hrtime(const FunctionCallbackInfo<Value>& args) { uint64_t t = uv_hrtime(); - if (args.Length() > 0) { - // return a time diff tuple - if (!args[0]->IsArray()) { + if (!args[1]->IsUndefined()) { + if (!args[1]->IsArray()) { return env->ThrowTypeError( - "process.hrtime() only accepts an Array tuple."); + "process.hrtime() only accepts an Array tuple"); } - Local<Array> inArray = Local<Array>::Cast(args[0]); - uint64_t seconds = inArray->Get(0)->Uint32Value(); - uint64_t nanos = inArray->Get(1)->Uint32Value(); - t -= (seconds * NANOS_PER_SEC) + nanos; + args.GetReturnValue().Set(true); } - Local<Array> tuple = Array::New(env->isolate(), 2); - tuple->Set(0, Integer::NewFromUnsigned(env->isolate(), t / NANOS_PER_SEC)); - tuple->Set(1, Integer::NewFromUnsigned(env->isolate(), t % NANOS_PER_SEC)); - args.GetReturnValue().Set(tuple); + Local<ArrayBuffer> ab = args[0].As<Uint32Array>()->Buffer(); + uint32_t* fields = static_cast<uint32_t*>(ab->GetContents().Data()); + + // These three indices will contain the values for the hrtime tuple. The + // seconds value is broken into the upper/lower 32 bits and stored in two + // uint32 fields to be converted back in JS. + fields[0] = (t / NANOS_PER_SEC) >> 32; + fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; + fields[2] = t % NANOS_PER_SEC; } extern "C" void node_module_register(void* m) { @@ -2519,23 +2524,35 @@ static void EnvDeleter(Local<String> property, static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { - Isolate* isolate = info.GetIsolate(); + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + Local<Context> ctx = env->context(); + Local<Function> fn = env->push_values_to_array_function(); + Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t idx = 0; + #ifdef __POSIX__ int size = 0; while (environ[size]) size++; - Local<Array> envarr = Array::New(isolate, size); + Local<Array> envarr = Array::New(isolate); for (int i = 0; i < size; ++i) { const char* var = environ[i]; const char* s = strchr(var, '='); const int length = s ? s - var : strlen(var); - Local<String> name = String::NewFromUtf8(isolate, - var, - String::kNormalString, - length); - envarr->Set(i, name); + argv[idx] = String::NewFromUtf8(isolate, + var, + String::kNormalString, + length); + if (++idx >= ARRAY_SIZE(argv)) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); + idx = 0; + } + } + if (idx > 0) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); } #else // _WIN32 WCHAR* environment = GetEnvironmentStringsW(); @@ -2543,7 +2560,6 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { return; // This should not happen. Local<Array> envarr = Array::New(isolate); WCHAR* p = environment; - int i = 0; while (*p) { WCHAR *s; if (*p == L'=') { @@ -2558,13 +2574,19 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) { } const uint16_t* two_byte_buffer = reinterpret_cast<const uint16_t*>(p); const size_t two_byte_buffer_len = s - p; - Local<String> value = String::NewFromTwoByte(isolate, - two_byte_buffer, - String::kNormalString, - two_byte_buffer_len); - envarr->Set(i++, value); + argv[idx] = String::NewFromTwoByte(isolate, + two_byte_buffer, + String::kNormalString, + two_byte_buffer_len); + if (++idx >= ARRAY_SIZE(argv)) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); + idx = 0; + } p = s + wcslen(s) + 1; } + if (idx > 0) { + fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); + } FreeEnvironmentStringsW(environment); #endif diff --git a/src/node.js b/src/node.js index 35a51c80640ab5..71b65497f4ba38 100644 --- a/src/node.js +++ b/src/node.js @@ -178,12 +178,27 @@ } startup.setupProcessObject = function() { - process._setupProcessObject(setPropByIndex); + const _hrtime = process.hrtime; + const hrValues = new Uint32Array(3); - function setPropByIndex() { + process._setupProcessObject(pushValueToArray); + + function pushValueToArray() { for (var i = 0; i < arguments.length; i++) this.push(arguments[i]); } + + process.hrtime = function hrtime(ar) { + const ret = [0, 0]; + if (_hrtime(hrValues, ar)) { + ret[0] = (hrValues[0] * 0x100000000 + hrValues[1]) - ar[0]; + ret[1] = hrValues[2] - ar[1]; + } else { + ret[0] = hrValues[0] * 0x100000000 + hrValues[1]; + ret[1] = hrValues[2]; + } + return ret; + }; }; startup.globalVariables = function() { diff --git a/src/node_file.cc b/src/node_file.cc index 2bd32d7daa2bd0..4941ab4467435b 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -214,6 +214,9 @@ static void After(uv_fs_t *req) { { int r; Local<Array> names = Array::New(env->isolate(), 0); + Local<Function> fn = env->push_values_to_array_function(); + Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t name_idx = 0; for (int i = 0; ; i++) { uv_dirent_t ent; @@ -229,9 +232,19 @@ static void After(uv_fs_t *req) { break; } - Local<String> name = String::NewFromUtf8(env->isolate(), - ent.name); - names->Set(i, name); + name_argv[name_idx++] = + String::NewFromUtf8(env->isolate(), ent.name); + + if (name_idx >= ARRAY_SIZE(name_argv)) { + fn->Call(env->context(), names, name_idx, name_argv) + .ToLocalChecked(); + name_idx = 0; + } + } + + if (name_idx > 0) { + fn->Call(env->context(), names, name_idx, name_argv) + .ToLocalChecked(); } argv[1] = names; @@ -811,6 +824,9 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) { CHECK_GE(SYNC_REQ.result, 0); int r; Local<Array> names = Array::New(env->isolate(), 0); + Local<Function> fn = env->push_values_to_array_function(); + Local<Value> name_v[NODE_PUSH_VAL_TO_ARRAY_MAX]; + size_t name_idx = 0; for (int i = 0; ; i++) { uv_dirent_t ent; @@ -821,9 +837,18 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) { if (r != 0) return env->ThrowUVException(r, "readdir", "", *path); - Local<String> name = String::NewFromUtf8(env->isolate(), - ent.name); - names->Set(i, name); + + name_v[name_idx++] = String::NewFromUtf8(env->isolate(), ent.name); + + if (name_idx >= ARRAY_SIZE(name_v)) { + fn->Call(env->context(), names, name_idx, name_v) + .ToLocalChecked(); + name_idx = 0; + } + } + + if (name_idx > 0) { + fn->Call(env->context(), names, name_idx, name_v).ToLocalChecked(); } args.GetReturnValue().Set(names); diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index ff3dfb26e529af..28322f95c40939 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -632,12 +632,23 @@ class Parser : public BaseObject { Local<Array> CreateHeaders() { // num_values_ is either -1 or the entry # of the last header // so num_values_ == 0 means there's a single header - Local<Array> headers = Array::New(env()->isolate(), 2 * num_values_); - - for (int i = 0; i < num_values_; ++i) { - headers->Set(2 * i, fields_[i].ToString(env())); - headers->Set(2 * i + 1, values_[i].ToString(env())); - } + Local<Array> headers = Array::New(env()->isolate()); + Local<Function> fn = env()->push_values_to_array_function(); + Local<Value> argv[NODE_PUSH_VAL_TO_ARRAY_MAX * 2]; + int i = 0; + + do { + size_t j = 0; + while (i < num_values_ && j < ARRAY_SIZE(argv) / 2) { + argv[j * 2] = fields_[i].ToString(env()); + argv[j * 2 + 1] = values_[i].ToString(env()); + i++; + j++; + } + if (j > 0) { + fn->Call(env()->context(), headers, j * 2, argv).ToLocalChecked(); + } + } while (i < num_values_); return headers; } diff --git a/test/parallel/test-process-getactivehandles.js b/test/parallel/test-process-getactivehandles.js new file mode 100644 index 00000000000000..96464cf3b22fcd --- /dev/null +++ b/test/parallel/test-process-getactivehandles.js @@ -0,0 +1,44 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const NUM = 8; +const connections = []; +const clients = []; +var clients_counter = 0; + +const server = net.createServer(function listener(c) { + connections.push(c); +}).listen(common.PORT, function makeConnections() { + for (var i = 0; i < NUM; i++) { + net.connect(common.PORT, function connected() { + clientConnected(this); + }); + } +}); + + +function clientConnected(client) { + clients.push(client); + if (++clients_counter >= NUM) + checkAll(); +} + + +function checkAll() { + const handles = process._getActiveHandles(); + + clients.forEach(function(item) { + assert.ok(handles.indexOf(item) > -1); + item.destroy(); + }); + + connections.forEach(function(item) { + assert.ok(handles.indexOf(item) > -1); + item.end(); + }); + + assert.ok(handles.indexOf(server) > -1); + server.close(); +}