summaryrefslogtreecommitdiff
path: root/benchmark
diff options
context:
space:
mode:
authorKenny Yuan <kaining.yuan@intel.com>2018-06-27 12:42:18 +0800
committerGabriel Schulhof <gabriel.schulhof@intel.com>2018-07-05 21:16:55 -0400
commit3314b3a2f5d9abcf1deee565839f779d26c8672d (patch)
tree8c65efceb9393bd455717ede9186ea7af6595472 /benchmark
parent0a78f7d622534344888013f93de5d9fed6305e6b (diff)
downloadandroid-node-v8-3314b3a2f5d9abcf1deee565839f779d26c8672d.tar.gz
android-node-v8-3314b3a2f5d9abcf1deee565839f779d26c8672d.tar.bz2
android-node-v8-3314b3a2f5d9abcf1deee565839f779d26c8672d.zip
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 <gabriel.schulhof@intel.com> Reviewed-By: Kyle Farnung <kfarnung@microsoft.com>
Diffstat (limited to 'benchmark')
-rw-r--r--benchmark/napi/function_args/.gitignore1
-rw-r--r--benchmark/napi/function_args/binding.cc142
-rw-r--r--benchmark/napi/function_args/binding.gyp12
-rw-r--r--benchmark/napi/function_args/index.js99
-rw-r--r--benchmark/napi/function_args/napi_binding.c229
5 files changed, 483 insertions, 0 deletions
diff --git a/benchmark/napi/function_args/.gitignore b/benchmark/napi/function_args/.gitignore
new file mode 100644
index 0000000000..567609b123
--- /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 0000000000..11eb394a6a
--- /dev/null
+++ b/benchmark/napi/function_args/binding.cc
@@ -0,0 +1,142 @@
+#include <v8.h>
+#include <node.h>
+#include <assert.h>
+
+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<Value>& args) {
+ assert(args.Length() == 1 && args[0]->IsString());
+ if (args.Length() == 1 && args[0]->IsString()) {
+ Local<String> str = args[0].As<String>();
+ const int32_t length = str->Utf8Length() + 1;
+ char* buf = new char[length];
+ str->WriteUtf8(buf, length);
+ delete [] buf;
+ }
+}
+
+void CallWithArray(const FunctionCallbackInfo<Value>& args) {
+ assert(args.Length() == 1 && args[0]->IsArray());
+ if (args.Length() == 1 && args[0]->IsArray()) {
+ const Local<Array> array = args[0].As<Array>();
+ uint32_t length = array->Length();
+ for (uint32_t i = 0; i < length; ++ i) {
+ Local<Value> v;
+ v = array->Get(i);
+ }
+ }
+}
+
+void CallWithNumber(const FunctionCallbackInfo<Value>& args) {
+ assert(args.Length() == 1 && args[0]->IsNumber());
+ if (args.Length() == 1 && args[0]->IsNumber()) {
+ args[0].As<Number>()->Value();
+ }
+}
+
+void CallWithObject(const FunctionCallbackInfo<Value>& args) {
+ Isolate* isolate = args.GetIsolate();
+ Local<Context> context = isolate->GetCurrentContext();
+
+ assert(args.Length() == 1 && args[0]->IsObject());
+ if (args.Length() == 1 && args[0]->IsObject()) {
+ Local<Object> obj = args[0].As<Object>();
+
+ MaybeLocal<String> map_key = String::NewFromUtf8(isolate,
+ "map", v8::NewStringType::kNormal);
+ assert(!map_key.IsEmpty());
+ MaybeLocal<Value> map_maybe = obj->Get(context,
+ map_key.ToLocalChecked());
+ assert(!map_maybe.IsEmpty());
+ Local<Value> map;
+ map = map_maybe.ToLocalChecked();
+
+ MaybeLocal<String> operand_key = String::NewFromUtf8(isolate,
+ "operand", v8::NewStringType::kNormal);
+ assert(!operand_key.IsEmpty());
+ MaybeLocal<Value> operand_maybe = obj->Get(context,
+ operand_key.ToLocalChecked());
+ assert(!operand_maybe.IsEmpty());
+ Local<Value> operand;
+ operand = operand_maybe.ToLocalChecked();
+
+ MaybeLocal<String> data_key = String::NewFromUtf8(isolate,
+ "data", v8::NewStringType::kNormal);
+ assert(!data_key.IsEmpty());
+ MaybeLocal<Value> data_maybe = obj->Get(context,
+ data_key.ToLocalChecked());
+ assert(!data_maybe.IsEmpty());
+ Local<Value> data;
+ data = data_maybe.ToLocalChecked();
+
+ MaybeLocal<String> reduce_key = String::NewFromUtf8(isolate,
+ "reduce", v8::NewStringType::kNormal);
+ assert(!reduce_key.IsEmpty());
+ MaybeLocal<Value> reduce_maybe = obj->Get(context,
+ reduce_key.ToLocalChecked());
+ assert(!reduce_maybe.IsEmpty());
+ Local<Value> reduce;
+ reduce = reduce_maybe.ToLocalChecked();
+ }
+}
+
+void CallWithTypedarray(const FunctionCallbackInfo<Value>& args) {
+ assert(args.Length() == 1 && args[0]->IsArrayBufferView());
+ if (args.Length() == 1 && args[0]->IsArrayBufferView()) {
+ assert(args[0]->IsArrayBufferView());
+ Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
+ const size_t byte_offset = view->ByteOffset();
+ const size_t byte_length = view->ByteLength();
+ assert(byte_length > 0);
+ assert(view->HasBuffer());
+ Local<ArrayBuffer> buffer;
+ buffer = view->Buffer();
+ ArrayBuffer::Contents contents;
+ contents = buffer->GetContents();
+ const uint32_t* data = reinterpret_cast<uint32_t*>(
+ static_cast<uint8_t*>(contents.Data()) + byte_offset);
+ assert(data);
+ }
+}
+
+void CallWithArguments(const FunctionCallbackInfo<Value>& args) {
+ assert(args.Length() > 1 && args[0]->IsNumber());
+ if (args.Length() > 1 && args[0]->IsNumber()) {
+ int32_t loop = args[0].As<v8::Uint32>()->Value();
+ for (int32_t i = 1; i < loop; ++i) {
+ assert(i < args.Length());
+ assert(args[i]->IsUint32());
+ args[i].As<v8::Uint32>()->Value();
+ }
+ }
+}
+
+void Initialize(Local<Object> 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 0000000000..ac122ed1a0
--- /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 0000000000..c8f281a342
--- /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 0000000000..b697644ca4
--- /dev/null
+++ b/benchmark/napi/function_args/napi_binding.c
@@ -0,0 +1,229 @@
+#include <node_api.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+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;
+}