Skip to content

Commit 9776f1c

Browse files
kenny-ytargos
authored andcommitted
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: #21555 Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Kyle Farnung <[email protected]>
1 parent f7aa22a commit 9776f1c

File tree

6 files changed

+492
-0
lines changed

6 files changed

+492
-0
lines changed

Makefile

+9
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,15 @@ benchmark/napi/function_call/build/Release/binding.node: all \
305305
--directory="$(shell pwd)/benchmark/napi/function_call" \
306306
--nodedir="$(shell pwd)"
307307

308+
benchmark/napi/function_args/build/Release/binding.node: all \
309+
benchmark/napi/function_args/napi_binding.c \
310+
benchmark/napi/function_args/binding.cc \
311+
benchmark/napi/function_args/binding.gyp
312+
$(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp rebuild \
313+
--python="$(PYTHON)" \
314+
--directory="$(shell pwd)/benchmark/napi/function_args" \
315+
--nodedir="$(shell pwd)"
316+
308317
# Implicitly depends on $(NODE_EXE). We don't depend on it explicitly because
309318
# it always triggers a rebuild due to it being a .PHONY rule. See the comment
310319
# near the build-addons rule for more background.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#include <v8.h>
2+
#include <node.h>
3+
#include <assert.h>
4+
5+
using v8::Isolate;
6+
using v8::Context;
7+
using v8::Local;
8+
using v8::MaybeLocal;
9+
using v8::Value;
10+
using v8::Number;
11+
using v8::String;
12+
using v8::Object;
13+
using v8::Array;
14+
using v8::ArrayBufferView;
15+
using v8::ArrayBuffer;
16+
using v8::FunctionCallbackInfo;
17+
18+
void CallWithString(const FunctionCallbackInfo<Value>& args) {
19+
assert(args.Length() == 1 && args[0]->IsString());
20+
if (args.Length() == 1 && args[0]->IsString()) {
21+
Local<String> str = args[0].As<String>();
22+
const int32_t length = str->Utf8Length() + 1;
23+
char* buf = new char[length];
24+
str->WriteUtf8(buf, length);
25+
delete [] buf;
26+
}
27+
}
28+
29+
void CallWithArray(const FunctionCallbackInfo<Value>& args) {
30+
assert(args.Length() == 1 && args[0]->IsArray());
31+
if (args.Length() == 1 && args[0]->IsArray()) {
32+
const Local<Array> array = args[0].As<Array>();
33+
uint32_t length = array->Length();
34+
for (uint32_t i = 0; i < length; ++ i) {
35+
Local<Value> v;
36+
v = array->Get(i);
37+
}
38+
}
39+
}
40+
41+
void CallWithNumber(const FunctionCallbackInfo<Value>& args) {
42+
assert(args.Length() == 1 && args[0]->IsNumber());
43+
if (args.Length() == 1 && args[0]->IsNumber()) {
44+
args[0].As<Number>()->Value();
45+
}
46+
}
47+
48+
void CallWithObject(const FunctionCallbackInfo<Value>& args) {
49+
Isolate* isolate = args.GetIsolate();
50+
Local<Context> context = isolate->GetCurrentContext();
51+
52+
assert(args.Length() == 1 && args[0]->IsObject());
53+
if (args.Length() == 1 && args[0]->IsObject()) {
54+
Local<Object> obj = args[0].As<Object>();
55+
56+
MaybeLocal<String> map_key = String::NewFromUtf8(isolate,
57+
"map", v8::NewStringType::kNormal);
58+
assert(!map_key.IsEmpty());
59+
MaybeLocal<Value> map_maybe = obj->Get(context,
60+
map_key.ToLocalChecked());
61+
assert(!map_maybe.IsEmpty());
62+
Local<Value> map;
63+
map = map_maybe.ToLocalChecked();
64+
65+
MaybeLocal<String> operand_key = String::NewFromUtf8(isolate,
66+
"operand", v8::NewStringType::kNormal);
67+
assert(!operand_key.IsEmpty());
68+
MaybeLocal<Value> operand_maybe = obj->Get(context,
69+
operand_key.ToLocalChecked());
70+
assert(!operand_maybe.IsEmpty());
71+
Local<Value> operand;
72+
operand = operand_maybe.ToLocalChecked();
73+
74+
MaybeLocal<String> data_key = String::NewFromUtf8(isolate,
75+
"data", v8::NewStringType::kNormal);
76+
assert(!data_key.IsEmpty());
77+
MaybeLocal<Value> data_maybe = obj->Get(context,
78+
data_key.ToLocalChecked());
79+
assert(!data_maybe.IsEmpty());
80+
Local<Value> data;
81+
data = data_maybe.ToLocalChecked();
82+
83+
MaybeLocal<String> reduce_key = String::NewFromUtf8(isolate,
84+
"reduce", v8::NewStringType::kNormal);
85+
assert(!reduce_key.IsEmpty());
86+
MaybeLocal<Value> reduce_maybe = obj->Get(context,
87+
reduce_key.ToLocalChecked());
88+
assert(!reduce_maybe.IsEmpty());
89+
Local<Value> reduce;
90+
reduce = reduce_maybe.ToLocalChecked();
91+
}
92+
}
93+
94+
void CallWithTypedarray(const FunctionCallbackInfo<Value>& args) {
95+
assert(args.Length() == 1 && args[0]->IsArrayBufferView());
96+
if (args.Length() == 1 && args[0]->IsArrayBufferView()) {
97+
assert(args[0]->IsArrayBufferView());
98+
Local<ArrayBufferView> view = args[0].As<ArrayBufferView>();
99+
const size_t byte_offset = view->ByteOffset();
100+
const size_t byte_length = view->ByteLength();
101+
assert(byte_length > 0);
102+
assert(view->HasBuffer());
103+
Local<ArrayBuffer> buffer;
104+
buffer = view->Buffer();
105+
ArrayBuffer::Contents contents;
106+
contents = buffer->GetContents();
107+
const uint32_t* data = reinterpret_cast<uint32_t*>(
108+
static_cast<uint8_t*>(contents.Data()) + byte_offset);
109+
assert(data);
110+
}
111+
}
112+
113+
void CallWithArguments(const FunctionCallbackInfo<Value>& args) {
114+
assert(args.Length() > 1 && args[0]->IsNumber());
115+
if (args.Length() > 1 && args[0]->IsNumber()) {
116+
int32_t loop = args[0].As<v8::Uint32>()->Value();
117+
for (int32_t i = 1; i < loop; ++i) {
118+
assert(i < args.Length());
119+
assert(args[i]->IsUint32());
120+
args[i].As<v8::Uint32>()->Value();
121+
}
122+
}
123+
}
124+
125+
void Initialize(Local<Object> target) {
126+
NODE_SET_METHOD(target, "callWithString", CallWithString);
127+
NODE_SET_METHOD(target, "callWithLongString", CallWithString);
128+
129+
NODE_SET_METHOD(target, "callWithArray", CallWithArray);
130+
NODE_SET_METHOD(target, "callWithLargeArray", CallWithArray);
131+
NODE_SET_METHOD(target, "callWithHugeArray", CallWithArray);
132+
133+
NODE_SET_METHOD(target, "callWithNumber", CallWithNumber);
134+
NODE_SET_METHOD(target, "callWithObject", CallWithObject);
135+
NODE_SET_METHOD(target, "callWithTypedarray", CallWithTypedarray);
136+
137+
NODE_SET_METHOD(target, "callWith10Numbers", CallWithArguments);
138+
NODE_SET_METHOD(target, "callWith100Numbers", CallWithArguments);
139+
NODE_SET_METHOD(target, "callWith1000Numbers", CallWithArguments);
140+
}
141+
142+
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'napi_binding',
5+
'sources': [ 'napi_binding.c' ]
6+
},
7+
{
8+
'target_name': 'binding',
9+
'sources': [ 'binding.cc' ]
10+
}
11+
]
12+
}

benchmark/napi/function_args/index.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// show the difference between calling a V8 binding C++ function
2+
// relative to a comparable N-API C++ function,
3+
// in various types/numbers of arguments.
4+
// Reports n of calls per second.
5+
'use strict';
6+
7+
const common = require('../../common.js');
8+
9+
let v8;
10+
let napi;
11+
12+
try {
13+
v8 = require('./build/Release/binding');
14+
} catch (err) {
15+
// eslint-disable-next-line no-path-concat
16+
console.error(__filename + ': V8 Binding failed to load');
17+
process.exit(0);
18+
}
19+
20+
try {
21+
napi = require('./build/Release/napi_binding');
22+
} catch (err) {
23+
// eslint-disable-next-line no-path-concat
24+
console.error(__filename + ': NAPI-Binding failed to load');
25+
process.exit(0);
26+
}
27+
28+
const argsTypes = ['String', 'Number', 'Object', 'Array', 'Typedarray',
29+
'10Numbers', '100Numbers', '1000Numbers'];
30+
31+
const generateArgs = (argType) => {
32+
let args = [];
33+
34+
if (argType === 'String') {
35+
args.push('The quick brown fox jumps over the lazy dog');
36+
} else if (argType === 'LongString') {
37+
args.push(Buffer.alloc(32768, '42').toString());
38+
} else if (argType === 'Number') {
39+
args.push(Math.floor(314158964 * Math.random()));
40+
} else if (argType === 'Object') {
41+
args.push({
42+
map: 'add',
43+
operand: 10,
44+
data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
45+
reduce: 'add',
46+
});
47+
} else if (argType === 'Array') {
48+
const arr = [];
49+
for (let i = 0; i < 50; ++i) {
50+
arr.push(Math.random() * 10e9);
51+
}
52+
args.push(arr);
53+
} else if (argType === 'Typedarray') {
54+
const arr = new Uint32Array(1000);
55+
for (let i = 0; i < 1000; ++i) {
56+
arr[i] = Math.random() * 4294967296;
57+
}
58+
args.push(arr);
59+
} else if (argType === '10Numbers') {
60+
args.push(10);
61+
for (let i = 0; i < 9; ++i) {
62+
args = [...args, ...generateArgs('Number')];
63+
}
64+
} else if (argType === '100Numbers') {
65+
args.push(100);
66+
for (let i = 0; i < 99; ++i) {
67+
args = [...args, ...generateArgs('Number')];
68+
}
69+
} else if (argType === '1000Numbers') {
70+
args.push(1000);
71+
for (let i = 0; i < 999; ++i) {
72+
args = [...args, ...generateArgs('Number')];
73+
}
74+
}
75+
76+
return args;
77+
};
78+
79+
const bench = common.createBenchmark(main, {
80+
type: argsTypes,
81+
engine: ['v8', 'napi'],
82+
n: [1, 1e1, 1e2, 1e3, 1e4, 1e5],
83+
});
84+
85+
function main({ n, engine, type }) {
86+
const bindings = engine === 'v8' ? v8 : napi;
87+
const methodName = 'callWith' + type;
88+
const fn = bindings[methodName];
89+
90+
if (fn) {
91+
const args = generateArgs(type);
92+
93+
bench.start();
94+
for (var i = 0; i < n; i++) {
95+
fn.apply(null, args);
96+
}
97+
bench.end(n);
98+
}
99+
}

0 commit comments

Comments
 (0)