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%': "