From 9776f1cbef855cbc6bb70c57e7747f931f64062d Mon Sep 17 00:00:00 2001 From: Kenny Yuan Date: Wed, 27 Jun 2018 12:42:18 +0800 Subject: [PATCH] benchmark: add n-api function args benchmark This benchmark suite is added to measure the performance of n-api function call with various type/number of arguments. The cases in this suite are carefully selected to efficiently show the performance trend. PR-URL: https://github.com/nodejs/node/pull/21555 Reviewed-By: Gabriel Schulhof Reviewed-By: Kyle Farnung --- Makefile | 9 + benchmark/napi/function_args/.gitignore | 1 + benchmark/napi/function_args/binding.cc | 142 ++++++++++++ benchmark/napi/function_args/binding.gyp | 12 + benchmark/napi/function_args/index.js | 99 +++++++++ benchmark/napi/function_args/napi_binding.c | 229 ++++++++++++++++++++ 6 files changed, 492 insertions(+) create mode 100644 benchmark/napi/function_args/.gitignore create mode 100644 benchmark/napi/function_args/binding.cc create mode 100644 benchmark/napi/function_args/binding.gyp create mode 100644 benchmark/napi/function_args/index.js create mode 100644 benchmark/napi/function_args/napi_binding.c diff --git a/Makefile b/Makefile index c75194dbaa1566..6bba4c5052574e 100644 --- a/Makefile +++ b/Makefile @@ -305,6 +305,15 @@ benchmark/napi/function_call/build/Release/binding.node: all \ --directory="$(shell pwd)/benchmark/napi/function_call" \ --nodedir="$(shell pwd)" +benchmark/napi/function_args/build/Release/binding.node: all \ + benchmark/napi/function_args/napi_binding.c \ + benchmark/napi/function_args/binding.cc \ + benchmark/napi/function_args/binding.gyp + $(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp rebuild \ + --python="$(PYTHON)" \ + --directory="$(shell pwd)/benchmark/napi/function_args" \ + --nodedir="$(shell pwd)" + # Implicitly depends on $(NODE_EXE). We don't depend on it explicitly because # it always triggers a rebuild due to it being a .PHONY rule. See the comment # near the build-addons rule for more background. diff --git a/benchmark/napi/function_args/.gitignore b/benchmark/napi/function_args/.gitignore new file mode 100644 index 00000000000000..567609b1234a9b --- /dev/null +++ b/benchmark/napi/function_args/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmark/napi/function_args/binding.cc b/benchmark/napi/function_args/binding.cc new file mode 100644 index 00000000000000..11eb394a6a98f5 --- /dev/null +++ b/benchmark/napi/function_args/binding.cc @@ -0,0 +1,142 @@ +#include +#include +#include + +using v8::Isolate; +using v8::Context; +using v8::Local; +using v8::MaybeLocal; +using v8::Value; +using v8::Number; +using v8::String; +using v8::Object; +using v8::Array; +using v8::ArrayBufferView; +using v8::ArrayBuffer; +using v8::FunctionCallbackInfo; + +void CallWithString(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsString()); + if (args.Length() == 1 && args[0]->IsString()) { + Local str = args[0].As(); + const int32_t length = str->Utf8Length() + 1; + char* buf = new char[length]; + str->WriteUtf8(buf, length); + delete [] buf; + } +} + +void CallWithArray(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsArray()); + if (args.Length() == 1 && args[0]->IsArray()) { + const Local array = args[0].As(); + uint32_t length = array->Length(); + for (uint32_t i = 0; i < length; ++ i) { + Local v; + v = array->Get(i); + } + } +} + +void CallWithNumber(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsNumber()); + if (args.Length() == 1 && args[0]->IsNumber()) { + args[0].As()->Value(); + } +} + +void CallWithObject(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + assert(args.Length() == 1 && args[0]->IsObject()); + if (args.Length() == 1 && args[0]->IsObject()) { + Local obj = args[0].As(); + + MaybeLocal map_key = String::NewFromUtf8(isolate, + "map", v8::NewStringType::kNormal); + assert(!map_key.IsEmpty()); + MaybeLocal map_maybe = obj->Get(context, + map_key.ToLocalChecked()); + assert(!map_maybe.IsEmpty()); + Local map; + map = map_maybe.ToLocalChecked(); + + MaybeLocal operand_key = String::NewFromUtf8(isolate, + "operand", v8::NewStringType::kNormal); + assert(!operand_key.IsEmpty()); + MaybeLocal operand_maybe = obj->Get(context, + operand_key.ToLocalChecked()); + assert(!operand_maybe.IsEmpty()); + Local operand; + operand = operand_maybe.ToLocalChecked(); + + MaybeLocal data_key = String::NewFromUtf8(isolate, + "data", v8::NewStringType::kNormal); + assert(!data_key.IsEmpty()); + MaybeLocal data_maybe = obj->Get(context, + data_key.ToLocalChecked()); + assert(!data_maybe.IsEmpty()); + Local data; + data = data_maybe.ToLocalChecked(); + + MaybeLocal reduce_key = String::NewFromUtf8(isolate, + "reduce", v8::NewStringType::kNormal); + assert(!reduce_key.IsEmpty()); + MaybeLocal reduce_maybe = obj->Get(context, + reduce_key.ToLocalChecked()); + assert(!reduce_maybe.IsEmpty()); + Local reduce; + reduce = reduce_maybe.ToLocalChecked(); + } +} + +void CallWithTypedarray(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsArrayBufferView()); + if (args.Length() == 1 && args[0]->IsArrayBufferView()) { + assert(args[0]->IsArrayBufferView()); + Local view = args[0].As(); + const size_t byte_offset = view->ByteOffset(); + const size_t byte_length = view->ByteLength(); + assert(byte_length > 0); + assert(view->HasBuffer()); + Local buffer; + buffer = view->Buffer(); + ArrayBuffer::Contents contents; + contents = buffer->GetContents(); + const uint32_t* data = reinterpret_cast( + static_cast(contents.Data()) + byte_offset); + assert(data); + } +} + +void CallWithArguments(const FunctionCallbackInfo& args) { + assert(args.Length() > 1 && args[0]->IsNumber()); + if (args.Length() > 1 && args[0]->IsNumber()) { + int32_t loop = args[0].As()->Value(); + for (int32_t i = 1; i < loop; ++i) { + assert(i < args.Length()); + assert(args[i]->IsUint32()); + args[i].As()->Value(); + } + } +} + +void Initialize(Local target) { + NODE_SET_METHOD(target, "callWithString", CallWithString); + NODE_SET_METHOD(target, "callWithLongString", CallWithString); + + NODE_SET_METHOD(target, "callWithArray", CallWithArray); + NODE_SET_METHOD(target, "callWithLargeArray", CallWithArray); + NODE_SET_METHOD(target, "callWithHugeArray", CallWithArray); + + NODE_SET_METHOD(target, "callWithNumber", CallWithNumber); + NODE_SET_METHOD(target, "callWithObject", CallWithObject); + NODE_SET_METHOD(target, "callWithTypedarray", CallWithTypedarray); + + NODE_SET_METHOD(target, "callWith10Numbers", CallWithArguments); + NODE_SET_METHOD(target, "callWith100Numbers", CallWithArguments); + NODE_SET_METHOD(target, "callWith1000Numbers", CallWithArguments); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) diff --git a/benchmark/napi/function_args/binding.gyp b/benchmark/napi/function_args/binding.gyp new file mode 100644 index 00000000000000..ac122ed1a07962 --- /dev/null +++ b/benchmark/napi/function_args/binding.gyp @@ -0,0 +1,12 @@ +{ + 'targets': [ + { + 'target_name': 'napi_binding', + 'sources': [ 'napi_binding.c' ] + }, + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/benchmark/napi/function_args/index.js b/benchmark/napi/function_args/index.js new file mode 100644 index 00000000000000..c8f281a3429fde --- /dev/null +++ b/benchmark/napi/function_args/index.js @@ -0,0 +1,99 @@ +// show the difference between calling a V8 binding C++ function +// relative to a comparable N-API C++ function, +// in various types/numbers of arguments. +// Reports n of calls per second. +'use strict'; + +const common = require('../../common.js'); + +let v8; +let napi; + +try { + v8 = require('./build/Release/binding'); +} catch (err) { + // eslint-disable-next-line no-path-concat + console.error(__filename + ': V8 Binding failed to load'); + process.exit(0); +} + +try { + napi = require('./build/Release/napi_binding'); +} catch (err) { + // eslint-disable-next-line no-path-concat + console.error(__filename + ': NAPI-Binding failed to load'); + process.exit(0); +} + +const argsTypes = ['String', 'Number', 'Object', 'Array', 'Typedarray', + '10Numbers', '100Numbers', '1000Numbers']; + +const generateArgs = (argType) => { + let args = []; + + if (argType === 'String') { + args.push('The quick brown fox jumps over the lazy dog'); + } else if (argType === 'LongString') { + args.push(Buffer.alloc(32768, '42').toString()); + } else if (argType === 'Number') { + args.push(Math.floor(314158964 * Math.random())); + } else if (argType === 'Object') { + args.push({ + map: 'add', + operand: 10, + data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + reduce: 'add', + }); + } else if (argType === 'Array') { + const arr = []; + for (let i = 0; i < 50; ++i) { + arr.push(Math.random() * 10e9); + } + args.push(arr); + } else if (argType === 'Typedarray') { + const arr = new Uint32Array(1000); + for (let i = 0; i < 1000; ++i) { + arr[i] = Math.random() * 4294967296; + } + args.push(arr); + } else if (argType === '10Numbers') { + args.push(10); + for (let i = 0; i < 9; ++i) { + args = [...args, ...generateArgs('Number')]; + } + } else if (argType === '100Numbers') { + args.push(100); + for (let i = 0; i < 99; ++i) { + args = [...args, ...generateArgs('Number')]; + } + } else if (argType === '1000Numbers') { + args.push(1000); + for (let i = 0; i < 999; ++i) { + args = [...args, ...generateArgs('Number')]; + } + } + + return args; +}; + +const bench = common.createBenchmark(main, { + type: argsTypes, + engine: ['v8', 'napi'], + n: [1, 1e1, 1e2, 1e3, 1e4, 1e5], +}); + +function main({ n, engine, type }) { + const bindings = engine === 'v8' ? v8 : napi; + const methodName = 'callWith' + type; + const fn = bindings[methodName]; + + if (fn) { + const args = generateArgs(type); + + bench.start(); + for (var i = 0; i < n; i++) { + fn.apply(null, args); + } + bench.end(n); + } +} diff --git a/benchmark/napi/function_args/napi_binding.c b/benchmark/napi/function_args/napi_binding.c new file mode 100644 index 00000000000000..b697644ca441e9 --- /dev/null +++ b/benchmark/napi/function_args/napi_binding.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include + +static napi_value CallWithString(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(types[0] == napi_string); + if (types[0] == napi_string) { + size_t len = 0; + // Get the length + status = napi_get_value_string_utf8(env, args[0], NULL, 0, &len); + assert(status == napi_ok); + char* buf = (char*)malloc(len + 1); + status = napi_get_value_string_utf8(env, args[0], buf, len + 1, &len); + assert(status == napi_ok); + free(buf); + } + + return NULL; +} + +static napi_value CallWithArray(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_value array = args[0]; + bool is_array = false; + status = napi_is_array(env, array, &is_array); + assert(status == napi_ok); + + assert(is_array); + if (is_array) { + uint32_t length; + status = napi_get_array_length(env, array, &length); + assert(status == napi_ok); + + for (uint32_t i = 0; i < length; ++i) { + napi_value v; + status = napi_get_element(env, array, i, &v); + assert(status == napi_ok); + } + } + + return NULL; +} + +static napi_value CallWithNumber(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(types[0] == napi_number); + if (types[0] == napi_number) { + double value = 0.0; + status = napi_get_value_double(env, args[0], &value); + assert(status == napi_ok); + } + + return NULL; +} + +static napi_value CallWithObject(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(argc == 1 && types[0] == napi_object); + if (argc == 1 && types[0] == napi_object) { + napi_value value; + + status = napi_get_named_property(env, args[0], "map", &value); + assert(status == napi_ok); + + status = napi_get_named_property(env, args[0], "operand", &value); + assert(status == napi_ok); + + status = napi_get_named_property(env, args[0], "data", &value); + assert(status == napi_ok); + + status = napi_get_named_property(env, args[0], "reduce", &value); + assert(status == napi_ok); + } + + return NULL; +} + +static napi_value CallWithTypedarray(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + bool is_typedarray = false; + status = napi_is_typedarray(env, args[0], &is_typedarray); + assert(status == napi_ok); + + assert(is_typedarray); + if (is_typedarray) { + napi_typedarray_type type; + napi_value input_buffer; + size_t byte_offset = 0; + size_t length = 0; + status = napi_get_typedarray_info(env, args[0], &type, &length, + NULL, &input_buffer, &byte_offset); + assert(status == napi_ok); + assert(length > 0); + + void* data = NULL; + size_t byte_length = 0; + status = napi_get_arraybuffer_info(env, + input_buffer, &data, &byte_length); + assert(status == napi_ok); + + uint32_t* input_integers = (uint32_t*)((uint8_t*)(data) + byte_offset); + assert(input_integers); + } + + return NULL; +} + +static napi_value CallWithArguments(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1000]; + // Get the length + status = napi_get_cb_info(env, info, &argc, NULL, NULL, NULL); + assert(status == napi_ok); + + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + assert(argc <= 1000); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(argc > 1 && types[0] == napi_number); + if (argc > 1 && types[0] == napi_number) { + uint32_t loop = 0; + status = napi_get_value_uint32(env, args[0], &loop); + assert(status == napi_ok); + + for (uint32_t i = 1; i < loop; ++i) { + assert(i < argc); + status = napi_typeof(env, args[i], types); + assert(status == napi_ok); + assert(types[0] == napi_number); + + uint32_t value = 0; + status = napi_get_value_uint32(env, args[i], &value); + assert(status == napi_ok); + } + } + + return NULL; +} + + +#define EXPORT_FUNC(env, exports, name, func) \ + do { \ + napi_status status; \ + napi_value js_func; \ + status = napi_create_function((env), \ + (name), \ + NAPI_AUTO_LENGTH, \ + (func), \ + NULL, \ + &js_func); \ + assert(status == napi_ok); \ + status = napi_set_named_property((env), \ + (exports), \ + (name), \ + js_func); \ + assert(status == napi_ok); \ + } while (0); + + +NAPI_MODULE_INIT() { + EXPORT_FUNC(env, exports, "callWithString", CallWithString); + EXPORT_FUNC(env, exports, "callWithLongString", CallWithString); + + EXPORT_FUNC(env, exports, "callWithArray", CallWithArray); + EXPORT_FUNC(env, exports, "callWithLargeArray", CallWithArray); + EXPORT_FUNC(env, exports, "callWithHugeArray", CallWithArray); + + EXPORT_FUNC(env, exports, "callWithNumber", CallWithNumber); + + EXPORT_FUNC(env, exports, "callWithObject", CallWithObject); + EXPORT_FUNC(env, exports, "callWithTypedarray", CallWithTypedarray); + + EXPORT_FUNC(env, exports, "callWith10Numbers", CallWithArguments); + EXPORT_FUNC(env, exports, "callWith100Numbers", CallWithArguments); + EXPORT_FUNC(env, exports, "callWith1000Numbers", CallWithArguments); + + return exports; +}