Skip to content

Commit 6a06463

Browse files
author
Gabriel Schulhof
committed
add benchmarking framework
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>
1 parent ffc71ed commit 6a06463

13 files changed

+465
-48
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,16 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod
162162

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

165+
### **Benchmarks**
166+
167+
You can run the available benchmarks using the following command:
168+
169+
```
170+
npm run-script benchmark
171+
```
172+
173+
See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks.
174+
165175
## **Contributing**
166176

167177
We love contributions from the community to **node-addon-api**.

benchmark/README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Benchmarks
2+
3+
## Running the benchmarks
4+
5+
From the parent directory, run
6+
7+
```bash
8+
npm run-script benchmark
9+
```
10+
11+
The above script supports the following arguments:
12+
13+
* `--benchmarks=...`: A semicolon-separated list of benchmark names. These names
14+
will be mapped to file names in this directory by appending `.js`.
15+
16+
## Adding benchmarks
17+
18+
The steps below should be followed when adding new benchmarks.
19+
20+
0. Decide on a name for the benchmark. This name will be used in several places.
21+
This example will use the name `new_benchmark`.
22+
23+
0. Create files `new_benchmark.cc` and `new_benchmark.js` in this directory.
24+
25+
0. Copy an existing benchmark in `binding.gyp` and change the target name prefix
26+
and the source file name to `new_benchmark`. This should result in two new
27+
targets which look like this:
28+
29+
```gyp
30+
{
31+
'target_name': 'new_benchmark',
32+
'sources': [ 'new_benchmark.cc' ],
33+
'includes': [ '../except.gypi' ],
34+
},
35+
{
36+
'target_name': 'new_benchmark_noexcept',
37+
'sources': [ 'new_benchmark.cc' ],
38+
'includes': [ '../noexcept.gypi' ],
39+
},
40+
```
41+
42+
There should always be a pair of targets: one bearing the name of the
43+
benchmark and configured with C++ exceptions enabled, and one bearing the
44+
same name followed by the suffix `_noexcept` and configured with C++
45+
exceptions disabled. This will ensure that the benchmark can be written to
46+
cover both the case where C++ exceptions are enabled and the case where they
47+
are disabled.

benchmark/binding.gyp

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
'target_defaults': { 'includes': ['../common.gypi'] },
3+
'targets': [
4+
{
5+
'target_name': 'function_args',
6+
'sources': [ 'function_args.cc' ],
7+
'includes': [ '../except.gypi' ],
8+
},
9+
{
10+
'target_name': 'function_args_noexcept',
11+
'sources': [ 'function_args.cc' ],
12+
'includes': [ '../noexcept.gypi' ],
13+
},
14+
{
15+
'target_name': 'property_descriptor',
16+
'sources': [ 'property_descriptor.cc' ],
17+
'includes': [ '../except.gypi' ],
18+
},
19+
{
20+
'target_name': 'property_descriptor_noexcept',
21+
'sources': [ 'property_descriptor.cc' ],
22+
'includes': [ '../noexcept.gypi' ],
23+
},
24+
]
25+
}

benchmark/function_args.cc

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include "napi.h"
2+
3+
static napi_value NoArgFunction_Core(napi_env env, napi_callback_info info) {
4+
(void) env;
5+
(void) info;
6+
return nullptr;
7+
}
8+
9+
static napi_value OneArgFunction_Core(napi_env env, napi_callback_info info) {
10+
size_t argc = 1;
11+
napi_value argv;
12+
if (napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr) != napi_ok) {
13+
return nullptr;
14+
}
15+
(void) argv;
16+
return nullptr;
17+
}
18+
19+
static napi_value TwoArgFunction_Core(napi_env env, napi_callback_info info) {
20+
size_t argc = 2;
21+
napi_value argv[2];
22+
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) {
23+
return nullptr;
24+
}
25+
(void) argv[0];
26+
(void) argv[1];
27+
return nullptr;
28+
}
29+
30+
static napi_value ThreeArgFunction_Core(napi_env env, napi_callback_info info) {
31+
size_t argc = 3;
32+
napi_value argv[3];
33+
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) {
34+
return nullptr;
35+
}
36+
(void) argv[0];
37+
(void) argv[1];
38+
(void) argv[2];
39+
return nullptr;
40+
}
41+
42+
static napi_value FourArgFunction_Core(napi_env env, napi_callback_info info) {
43+
size_t argc = 4;
44+
napi_value argv[4];
45+
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok) {
46+
return nullptr;
47+
}
48+
(void) argv[0];
49+
(void) argv[1];
50+
(void) argv[2];
51+
(void) argv[3];
52+
return nullptr;
53+
}
54+
55+
static void NoArgFunction(const Napi::CallbackInfo& info) {
56+
(void) info;
57+
}
58+
59+
static void OneArgFunction(const Napi::CallbackInfo& info) {
60+
Napi::Value argv0 = info[0]; (void) argv0;
61+
}
62+
63+
static void TwoArgFunction(const Napi::CallbackInfo& info) {
64+
Napi::Value argv0 = info[0]; (void) argv0;
65+
Napi::Value argv1 = info[1]; (void) argv1;
66+
}
67+
68+
static void ThreeArgFunction(const Napi::CallbackInfo& info) {
69+
Napi::Value argv0 = info[0]; (void) argv0;
70+
Napi::Value argv1 = info[1]; (void) argv1;
71+
Napi::Value argv2 = info[2]; (void) argv2;
72+
}
73+
74+
static void FourArgFunction(const Napi::CallbackInfo& info) {
75+
Napi::Value argv0 = info[0]; (void) argv0;
76+
Napi::Value argv1 = info[1]; (void) argv1;
77+
Napi::Value argv2 = info[2]; (void) argv2;
78+
Napi::Value argv3 = info[3]; (void) argv3;
79+
}
80+
81+
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
82+
napi_value no_arg_function, one_arg_function, two_arg_function,
83+
three_arg_function, four_arg_function;
84+
napi_status status;
85+
86+
status = napi_create_function(env,
87+
"noArgFunction",
88+
NAPI_AUTO_LENGTH,
89+
NoArgFunction_Core,
90+
nullptr,
91+
&no_arg_function);
92+
NAPI_THROW_IF_FAILED(env, status, Napi::Object());
93+
94+
status = napi_create_function(env,
95+
"oneArgFunction",
96+
NAPI_AUTO_LENGTH,
97+
OneArgFunction_Core,
98+
nullptr,
99+
&one_arg_function);
100+
NAPI_THROW_IF_FAILED(env, status, Napi::Object());
101+
102+
status = napi_create_function(env,
103+
"twoArgFunction",
104+
NAPI_AUTO_LENGTH,
105+
TwoArgFunction_Core,
106+
nullptr,
107+
&two_arg_function);
108+
NAPI_THROW_IF_FAILED(env, status, Napi::Object());
109+
110+
status = napi_create_function(env,
111+
"threeArgFunction",
112+
NAPI_AUTO_LENGTH,
113+
ThreeArgFunction_Core,
114+
nullptr,
115+
&three_arg_function);
116+
NAPI_THROW_IF_FAILED(env, status, Napi::Object());
117+
118+
status = napi_create_function(env,
119+
"fourArgFunction",
120+
NAPI_AUTO_LENGTH,
121+
FourArgFunction_Core,
122+
nullptr,
123+
&four_arg_function);
124+
NAPI_THROW_IF_FAILED(env, status, Napi::Object());
125+
126+
Napi::Object core = Napi::Object::New(env);
127+
core["noArgFunction"] = Napi::Value(env, no_arg_function);
128+
core["oneArgFunction"] = Napi::Value(env, one_arg_function);
129+
core["twoArgFunction"] = Napi::Value(env, two_arg_function);
130+
core["threeArgFunction"] = Napi::Value(env, three_arg_function);
131+
core["fourArgFunction"] = Napi::Value(env, four_arg_function);
132+
exports["core"] = core;
133+
134+
Napi::Object cplusplus = Napi::Object::New(env);
135+
cplusplus["noArgFunction"] = Napi::Function::New(env, NoArgFunction);
136+
cplusplus["oneArgFunction"] = Napi::Function::New(env, OneArgFunction);
137+
cplusplus["twoArgFunction"] = Napi::Function::New(env, TwoArgFunction);
138+
cplusplus["threeArgFunction"] = Napi::Function::New(env, ThreeArgFunction);
139+
cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction);
140+
exports["cplusplus"] = cplusplus;
141+
142+
return exports;
143+
}
144+
145+
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

benchmark/function_args.js

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const path = require('path');
2+
const Benchmark = require('benchmark');
3+
const addonName = path.basename(__filename, '.js');
4+
5+
[ addonName, addonName + '_noexcept' ]
6+
.forEach((addonName) => {
7+
const rootAddon = require(`./build/Release/${addonName}`);
8+
const implems = Object.keys(rootAddon);
9+
const anObject = {};
10+
11+
console.log(`${addonName}: `);
12+
13+
console.log('no arguments:');
14+
implems.reduce((suite, implem) => {
15+
const fn = rootAddon[implem].noArgFunction;
16+
return suite.add(implem, () => fn());
17+
}, new Benchmark.Suite)
18+
.on('cycle', (event) => console.log(String(event.target)))
19+
.run();
20+
21+
console.log('one argument:');
22+
implems.reduce((suite, implem) => {
23+
const fn = rootAddon[implem].oneArgFunction;
24+
return suite.add(implem, () => fn('x'));
25+
}, new Benchmark.Suite)
26+
.on('cycle', (event) => console.log(String(event.target)))
27+
.run();
28+
29+
console.log('two arguments:');
30+
implems.reduce((suite, implem) => {
31+
const fn = rootAddon[implem].twoArgFunction;
32+
return suite.add(implem, () => fn('x', 12));
33+
}, new Benchmark.Suite)
34+
.on('cycle', (event) => console.log(String(event.target)))
35+
.run();
36+
37+
console.log('three arguments:');
38+
implems.reduce((suite, implem) => {
39+
const fn = rootAddon[implem].threeArgFunction;
40+
return suite.add(implem, () => fn('x', 12, true));
41+
}, new Benchmark.Suite)
42+
.on('cycle', (event) => console.log(String(event.target)))
43+
.run();
44+
45+
console.log('four arguments:');
46+
implems.reduce((suite, implem) => {
47+
const fn = rootAddon[implem].fourArgFunction;
48+
return suite.add(implem, () => fn('x', 12, true, anObject));
49+
}, new Benchmark.Suite)
50+
.on('cycle', (event) => console.log(String(event.target)))
51+
.run();
52+
});

benchmark/index.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
const { readdirSync } = require('fs');
4+
const { spawnSync } = require('child_process');
5+
const path = require('path');
6+
7+
let benchmarks = [];
8+
9+
if (!!process.env.npm_config_benchmarks) {
10+
benchmarks = process.env.npm_config_benchmarks
11+
.split(';')
12+
.map((item) => (item + '.js'));
13+
}
14+
15+
// Run each file in this directory or the list given on the command line except
16+
// index.js as a Node.js process.
17+
(benchmarks.length > 0 ? benchmarks : readdirSync(__dirname))
18+
.filter((item) => (item !== 'index.js' && item.match(/\.js$/)))
19+
.map((item) => path.join(__dirname, item))
20+
.forEach((item) => {
21+
const child = spawnSync(process.execPath, [
22+
'--expose-gc',
23+
item
24+
], { stdio: 'inherit' });
25+
if (child.signal) {
26+
console.error(`Tests aborted with ${child.signal}`);
27+
process.exitCode = 1;
28+
} else {
29+
process.exitCode = child.status;
30+
}
31+
if (child.status !== 0) {
32+
process.exit(process.exitCode);
33+
}
34+
});

benchmark/property_descriptor.cc

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "napi.h"
2+
3+
static napi_value Getter_Core(napi_env env, napi_callback_info info) {
4+
(void) info;
5+
napi_value result;
6+
napi_status status = napi_create_uint32(env, 42, &result);
7+
NAPI_THROW_IF_FAILED(env, status, nullptr);
8+
return result;
9+
}
10+
11+
static napi_value Setter_Core(napi_env env, napi_callback_info info) {
12+
size_t argc = 1;
13+
napi_value argv;
14+
napi_status status =
15+
napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
16+
NAPI_THROW_IF_FAILED(env, status, nullptr);
17+
(void) argv;
18+
return nullptr;
19+
}
20+
21+
static Napi::Value Getter(const Napi::CallbackInfo& info) {
22+
return Napi::Number::New(info.Env(), 42);
23+
}
24+
25+
static void Setter(const Napi::CallbackInfo& info) {
26+
(void) info[0];
27+
}
28+
29+
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
30+
napi_status status;
31+
napi_property_descriptor core_prop = {
32+
"core",
33+
nullptr,
34+
nullptr,
35+
Getter_Core,
36+
Setter_Core,
37+
nullptr,
38+
napi_enumerable,
39+
nullptr
40+
};
41+
42+
status = napi_define_properties(env, exports, 1, &core_prop);
43+
NAPI_THROW_IF_FAILED(env, status, Napi::Object());
44+
45+
exports.DefineProperty(
46+
Napi::PropertyDescriptor::Accessor(env,
47+
exports,
48+
"cplusplus",
49+
Getter,
50+
Setter,
51+
napi_enumerable));
52+
53+
exports.DefineProperty(
54+
Napi::PropertyDescriptor::Accessor<Getter, Setter>("templated",
55+
napi_enumerable));
56+
57+
return exports;
58+
}
59+
60+
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

0 commit comments

Comments
 (0)