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‎

Lines changed: 9 additions & 0 deletions
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.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/
Lines changed: 142 additions & 0 deletions
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+
voidCallWithString(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+
constint32_t length = str->Utf8Length() + 1;
23+
char* buf = newchar[length];
24+
str->WriteUtf8(buf, length);
25+
delete [] buf;
26+
}
27+
}
28+
29+
voidCallWithArray(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+
voidCallWithNumber(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+
voidCallWithObject(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+
voidCallWithTypedarray(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+
constsize_t byte_offset = view->ByteOffset();
100+
constsize_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+
constuint32_t* data = reinterpret_cast<uint32_t*>(
108+
static_cast<uint8_t*>(contents.Data()) + byte_offset);
109+
assert(data);
110+
}
111+
}
112+
113+
voidCallWithArguments(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+
voidInitialize(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)
Lines changed: 12 additions & 0 deletions
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+
}
Lines changed: 99 additions & 0 deletions
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+
constcommon=require('../../common.js');
8+
9+
letv8;
10+
letnapi;
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+
constargsTypes=['String','Number','Object','Array','Typedarray',
29+
'10Numbers','100Numbers','1000Numbers'];
30+
31+
constgenerateArgs=(argType)=>{
32+
letargs=[];
33+
34+
if(argType==='String'){
35+
args.push('The quick brown fox jumps over the lazy dog');
36+
}elseif(argType==='LongString'){
37+
args.push(Buffer.alloc(32768,'42').toString());
38+
}elseif(argType==='Number'){
39+
args.push(Math.floor(314158964*Math.random()));
40+
}elseif(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+
}elseif(argType==='Array'){
48+
constarr=[];
49+
for(leti=0;i<50;++i){
50+
arr.push(Math.random()*10e9);
51+
}
52+
args.push(arr);
53+
}elseif(argType==='Typedarray'){
54+
constarr=newUint32Array(1000);
55+
for(leti=0;i<1000;++i){
56+
arr[i]=Math.random()*4294967296;
57+
}
58+
args.push(arr);
59+
}elseif(argType==='10Numbers'){
60+
args.push(10);
61+
for(leti=0;i<9;++i){
62+
args=[...args, ...generateArgs('Number')];
63+
}
64+
}elseif(argType==='100Numbers'){
65+
args.push(100);
66+
for(leti=0;i<99;++i){
67+
args=[...args, ...generateArgs('Number')];
68+
}
69+
}elseif(argType==='1000Numbers'){
70+
args.push(1000);
71+
for(leti=0;i<999;++i){
72+
args=[...args, ...generateArgs('Number')];
73+
}
74+
}
75+
76+
returnargs;
77+
};
78+
79+
constbench=common.createBenchmark(main,{
80+
type: argsTypes,
81+
engine: ['v8','napi'],
82+
n: [1,1e1,1e2,1e3,1e4,1e5],
83+
});
84+
85+
functionmain({ n, engine, type }){
86+
constbindings=engine==='v8' ? v8 : napi;
87+
constmethodName='callWith'+type;
88+
constfn=bindings[methodName];
89+
90+
if(fn){
91+
constargs=generateArgs(type);
92+
93+
bench.start();
94+
for(vari=0;i<n;i++){
95+
fn.apply(null,args);
96+
}
97+
bench.end(n);
98+
}
99+
}

0 commit comments

Comments
(0)