-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]>
- Loading branch information
Showing
6 changed files
with
492 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
'targets': [ | ||
{ | ||
'target_name': 'napi_binding', | ||
'sources': [ 'napi_binding.c' ] | ||
}, | ||
{ | ||
'target_name': 'binding', | ||
'sources': [ 'binding.cc' ] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Oops, something went wrong.