Skip to content

Commit

Permalink
add benchmarking framework
Browse files Browse the repository at this point in the history
Adds the framework for writing benchmarks and two basic benchmarks.

PR-URL: #623
Reviewed-By: Nicola Del Gobbo <nicoladelgobbo@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
  • Loading branch information
Gabriel Schulhof committed Dec 14, 2019
1 parent ffc71ed commit 6a06463
Show file tree
Hide file tree
Showing 13 changed files with 465 additions and 48 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,16 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod

<a name="resources"></a>

### **Benchmarks**

You can run the available benchmarks using the following command:

```
npm run-script benchmark
```

See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks.

## **Contributing**

We love contributions from the community to **node-addon-api**.
Expand Down
47 changes: 47 additions & 0 deletions benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Benchmarks

## Running the benchmarks

From the parent directory, run

```bash
npm run-script benchmark
```

The above script supports the following arguments:

* `--benchmarks=...`: A semicolon-separated list of benchmark names. These names
will be mapped to file names in this directory by appending `.js`.

## Adding benchmarks

The steps below should be followed when adding new benchmarks.

0. Decide on a name for the benchmark. This name will be used in several places.
This example will use the name `new_benchmark`.

0. Create files `new_benchmark.cc` and `new_benchmark.js` in this directory.

0. Copy an existing benchmark in `binding.gyp` and change the target name prefix
and the source file name to `new_benchmark`. This should result in two new
targets which look like this:

```gyp
{
'target_name': 'new_benchmark',
'sources': [ 'new_benchmark.cc' ],
'includes': [ '../except.gypi' ],
},
{
'target_name': 'new_benchmark_noexcept',
'sources': [ 'new_benchmark.cc' ],
'includes': [ '../noexcept.gypi' ],
},
```

There should always be a pair of targets: one bearing the name of the
benchmark and configured with C++ exceptions enabled, and one bearing the
same name followed by the suffix `_noexcept` and configured with C++
exceptions disabled. This will ensure that the benchmark can be written to
cover both the case where C++ exceptions are enabled and the case where they
are disabled.
25 changes: 25 additions & 0 deletions benchmark/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
'target_defaults': { 'includes': ['../common.gypi'] },
'targets': [
{
'target_name': 'function_args',
'sources': [ 'function_args.cc' ],
'includes': [ '../except.gypi' ],
},
{
'target_name': 'function_args_noexcept',
'sources': [ 'function_args.cc' ],
'includes': [ '../noexcept.gypi' ],
},
{
'target_name': 'property_descriptor',
'sources': [ 'property_descriptor.cc' ],
'includes': [ '../except.gypi' ],
},
{
'target_name': 'property_descriptor_noexcept',
'sources': [ 'property_descriptor.cc' ],
'includes': [ '../noexcept.gypi' ],
},
]
}
145 changes: 145 additions & 0 deletions benchmark/function_args.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include "napi.h"

static napi_value NoArgFunction_Core(napi_env env, napi_callback_info info) {
(void) env;
(void) info;
return nullptr;
}

static napi_value OneArgFunction_Core(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value argv;
if (napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr) != napi_ok) {
return nullptr;
}
(void) argv;
return nullptr;
}

static napi_value TwoArgFunction_Core(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[2];
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) {
return nullptr;
}
(void) argv[0];
(void) argv[1];
return nullptr;
}

static napi_value ThreeArgFunction_Core(napi_env env, napi_callback_info info) {
size_t argc = 3;
napi_value argv[3];
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) {
return nullptr;
}
(void) argv[0];
(void) argv[1];
(void) argv[2];
return nullptr;
}

static napi_value FourArgFunction_Core(napi_env env, napi_callback_info info) {
size_t argc = 4;
napi_value argv[4];
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) {
return nullptr;
}
(void) argv[0];
(void) argv[1];
(void) argv[2];
(void) argv[3];
return nullptr;
}

static void NoArgFunction(const Napi::CallbackInfo& info) {
(void) info;
}

static void OneArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
}

static void TwoArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
Napi::Value argv1 = info[1]; (void) argv1;
}

static void ThreeArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
Napi::Value argv1 = info[1]; (void) argv1;
Napi::Value argv2 = info[2]; (void) argv2;
}

static void FourArgFunction(const Napi::CallbackInfo& info) {
Napi::Value argv0 = info[0]; (void) argv0;
Napi::Value argv1 = info[1]; (void) argv1;
Napi::Value argv2 = info[2]; (void) argv2;
Napi::Value argv3 = info[3]; (void) argv3;
}

static Napi::Object Init(Napi::Env env, Napi::Object exports) {
napi_value no_arg_function, one_arg_function, two_arg_function,
three_arg_function, four_arg_function;
napi_status status;

status = napi_create_function(env,
"noArgFunction",
NAPI_AUTO_LENGTH,
NoArgFunction_Core,
nullptr,
&no_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"oneArgFunction",
NAPI_AUTO_LENGTH,
OneArgFunction_Core,
nullptr,
&one_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"twoArgFunction",
NAPI_AUTO_LENGTH,
TwoArgFunction_Core,
nullptr,
&two_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"threeArgFunction",
NAPI_AUTO_LENGTH,
ThreeArgFunction_Core,
nullptr,
&three_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

status = napi_create_function(env,
"fourArgFunction",
NAPI_AUTO_LENGTH,
FourArgFunction_Core,
nullptr,
&four_arg_function);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

Napi::Object core = Napi::Object::New(env);
core["noArgFunction"] = Napi::Value(env, no_arg_function);
core["oneArgFunction"] = Napi::Value(env, one_arg_function);
core["twoArgFunction"] = Napi::Value(env, two_arg_function);
core["threeArgFunction"] = Napi::Value(env, three_arg_function);
core["fourArgFunction"] = Napi::Value(env, four_arg_function);
exports["core"] = core;

Napi::Object cplusplus = Napi::Object::New(env);
cplusplus["noArgFunction"] = Napi::Function::New(env, NoArgFunction);
cplusplus["oneArgFunction"] = Napi::Function::New(env, OneArgFunction);
cplusplus["twoArgFunction"] = Napi::Function::New(env, TwoArgFunction);
cplusplus["threeArgFunction"] = Napi::Function::New(env, ThreeArgFunction);
cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction);
exports["cplusplus"] = cplusplus;

return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
52 changes: 52 additions & 0 deletions benchmark/function_args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const path = require('path');
const Benchmark = require('benchmark');
const addonName = path.basename(__filename, '.js');

[ addonName, addonName + '_noexcept' ]
.forEach((addonName) => {
const rootAddon = require(`./build/Release/${addonName}`);
const implems = Object.keys(rootAddon);
const anObject = {};

console.log(`${addonName}: `);

console.log('no arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].noArgFunction;
return suite.add(implem, () => fn());
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('one argument:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].oneArgFunction;
return suite.add(implem, () => fn('x'));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('two arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].twoArgFunction;
return suite.add(implem, () => fn('x', 12));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('three arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].threeArgFunction;
return suite.add(implem, () => fn('x', 12, true));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();

console.log('four arguments:');
implems.reduce((suite, implem) => {
const fn = rootAddon[implem].fourArgFunction;
return suite.add(implem, () => fn('x', 12, true, anObject));
}, new Benchmark.Suite)
.on('cycle', (event) => console.log(String(event.target)))
.run();
});
34 changes: 34 additions & 0 deletions benchmark/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

const { readdirSync } = require('fs');
const { spawnSync } = require('child_process');
const path = require('path');

let benchmarks = [];

if (!!process.env.npm_config_benchmarks) {
benchmarks = process.env.npm_config_benchmarks
.split(';')
.map((item) => (item + '.js'));
}

// Run each file in this directory or the list given on the command line except
// index.js as a Node.js process.
(benchmarks.length > 0 ? benchmarks : readdirSync(__dirname))
.filter((item) => (item !== 'index.js' && item.match(/\.js$/)))
.map((item) => path.join(__dirname, item))
.forEach((item) => {
const child = spawnSync(process.execPath, [
'--expose-gc',
item
], { stdio: 'inherit' });
if (child.signal) {
console.error(`Tests aborted with ${child.signal}`);
process.exitCode = 1;
} else {
process.exitCode = child.status;
}
if (child.status !== 0) {
process.exit(process.exitCode);
}
});
60 changes: 60 additions & 0 deletions benchmark/property_descriptor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "napi.h"

static napi_value Getter_Core(napi_env env, napi_callback_info info) {
(void) info;
napi_value result;
napi_status status = napi_create_uint32(env, 42, &result);
NAPI_THROW_IF_FAILED(env, status, nullptr);
return result;
}

static napi_value Setter_Core(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value argv;
napi_status status =
napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
NAPI_THROW_IF_FAILED(env, status, nullptr);
(void) argv;
return nullptr;
}

static Napi::Value Getter(const Napi::CallbackInfo& info) {
return Napi::Number::New(info.Env(), 42);
}

static void Setter(const Napi::CallbackInfo& info) {
(void) info[0];
}

static Napi::Object Init(Napi::Env env, Napi::Object exports) {
napi_status status;
napi_property_descriptor core_prop = {
"core",
nullptr,
nullptr,
Getter_Core,
Setter_Core,
nullptr,
napi_enumerable,
nullptr
};

status = napi_define_properties(env, exports, 1, &core_prop);
NAPI_THROW_IF_FAILED(env, status, Napi::Object());

exports.DefineProperty(
Napi::PropertyDescriptor::Accessor(env,
exports,
"cplusplus",
Getter,
Setter,
napi_enumerable));

exports.DefineProperty(
Napi::PropertyDescriptor::Accessor<Getter, Setter>("templated",
napi_enumerable));

return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
Loading

0 comments on commit 6a06463

Please sign in to comment.