diff --git a/README.md b/README.md index d09b600a6..22c8eb4c7 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,16 @@ Take a look and get inspired by our **[test suite](https://github.com/nodejs/nod +### **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**. diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 000000000..f6e7c27d9 --- /dev/null +++ b/benchmark/README.md @@ -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. diff --git a/benchmark/binding.gyp b/benchmark/binding.gyp new file mode 100644 index 000000000..72f68a13e --- /dev/null +++ b/benchmark/binding.gyp @@ -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' ], + }, + ] +} diff --git a/benchmark/function_args.cc b/benchmark/function_args.cc new file mode 100644 index 000000000..15c6de6fc --- /dev/null +++ b/benchmark/function_args.cc @@ -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) diff --git a/benchmark/function_args.js b/benchmark/function_args.js new file mode 100644 index 000000000..3dee09a68 --- /dev/null +++ b/benchmark/function_args.js @@ -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(); + }); diff --git a/benchmark/index.js b/benchmark/index.js new file mode 100644 index 000000000..e4c7391b1 --- /dev/null +++ b/benchmark/index.js @@ -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); + } + }); diff --git a/benchmark/property_descriptor.cc b/benchmark/property_descriptor.cc new file mode 100644 index 000000000..e4e26e7c9 --- /dev/null +++ b/benchmark/property_descriptor.cc @@ -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("templated", + napi_enumerable)); + + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/benchmark/property_descriptor.js b/benchmark/property_descriptor.js new file mode 100644 index 000000000..cab510601 --- /dev/null +++ b/benchmark/property_descriptor.js @@ -0,0 +1,29 @@ +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 getters = new Benchmark.Suite; + const setters = new Benchmark.Suite; + + console.log(`${addonName}: `); + + Object.keys(rootAddon).forEach((key) => { + getters.add(`${key} getter`, () => { + const x = rootAddon[key]; + }); + setters.add(`${key} setter`, () => { + rootAddon[key] = 5; + }) + }); + + getters + .on('cycle', (event) => console.log(String(event.target))) + .run(); + + setters + .on('cycle', (event) => console.log(String(event.target))) + .run(); + }); diff --git a/common.gypi b/common.gypi new file mode 100644 index 000000000..812198aea --- /dev/null +++ b/common.gypi @@ -0,0 +1,24 @@ +{ + 'variables': { + 'NAPI_VERSION%': "