Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

benchmark: add n-api function arguments benchmark suite #21555

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,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.
Expand Down
1 change: 1 addition & 0 deletions benchmark/napi/function_args/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
142 changes: 142 additions & 0 deletions benchmark/napi/function_args/binding.cc
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)
12 changes: 12 additions & 0 deletions benchmark/napi/function_args/binding.gyp
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' ]
}
]
}
99 changes: 99 additions & 0 deletions benchmark/napi/function_args/index.js
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);
}
}
Loading