diff --git a/benchmark/napi/create_object_with_properties/.gitignore b/benchmark/napi/create_object_with_properties/.gitignore new file mode 100644 index 00000000000000..567609b1234a9b --- /dev/null +++ b/benchmark/napi/create_object_with_properties/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmark/napi/create_object_with_properties/binding.cc b/benchmark/napi/create_object_with_properties/binding.cc new file mode 100644 index 00000000000000..d7fae01d1659f9 --- /dev/null +++ b/benchmark/napi/create_object_with_properties/binding.cc @@ -0,0 +1,116 @@ +#include +#include +#include + +struct BenchmarkParams { + napi_value count_val; + napi_value bench_obj; + napi_value start_fn; + napi_value end_fn; + uint32_t count; +}; + +static BenchmarkParams ParseBenchmarkArgs(napi_env env, + const napi_callback_info info) { + BenchmarkParams params; + size_t argc = 4; + napi_value args[4]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + params.count_val = args[0]; + params.bench_obj = args[1]; + params.start_fn = args[2]; + params.end_fn = args[3]; + + napi_get_value_uint32(env, params.count_val, ¶ms.count); + return params; +} + +static napi_value global_names[20]; +static napi_value global_values[20]; +static bool global_properties_initialized = false; + +// Creating with many options because complains are when ~20 properties +static void InitializeTestProperties(napi_env env) { + if (global_properties_initialized) return; + + for (int i = 0; i < 20; i++) { + std::string name = "foo" + std::to_string(i); + napi_create_string_utf8( + env, name.c_str(), NAPI_AUTO_LENGTH, &global_names[i]); + napi_create_string_utf8( + env, name.c_str(), NAPI_AUTO_LENGTH, &global_values[i]); + } + global_properties_initialized = true; +} + +static napi_value CreateObjectWithPropertiesNew(napi_env env, + napi_callback_info info) { + BenchmarkParams params = ParseBenchmarkArgs(env, info); + + InitializeTestProperties(env); + + napi_value null_prototype; + napi_get_null(env, &null_prototype); + + napi_call_function( + env, params.bench_obj, params.start_fn, 0, nullptr, nullptr); + + for (uint32_t i = 0; i < params.count; i++) { + napi_value obj; + napi_create_object_with_properties( + env, null_prototype, global_names, global_values, 20, &obj); + } + + napi_call_function( + env, params.bench_obj, params.end_fn, 1, ¶ms.count_val, nullptr); + + return nullptr; +} + +static napi_value CreateObjectWithPropertiesOld(napi_env env, + napi_callback_info info) { + BenchmarkParams params = ParseBenchmarkArgs(env, info); + + InitializeTestProperties(env); + + napi_call_function( + env, params.bench_obj, params.start_fn, 0, nullptr, nullptr); + + for (uint32_t i = 0; i < params.count; i++) { + napi_value obj; + napi_create_object(env, &obj); + for (int j = 0; j < 20; j++) { + napi_set_property(env, obj, global_names[j], global_values[j]); + } + } + + napi_call_function( + env, params.bench_obj, params.end_fn, 1, ¶ms.count_val, nullptr); + + return nullptr; +} + +NAPI_MODULE_INIT() { + napi_property_descriptor desc[] = { + {"createObjectWithPropertiesNew", + 0, + CreateObjectWithPropertiesNew, + 0, + 0, + 0, + napi_default, + 0}, + {"createObjectWithPropertiesOld", + 0, + CreateObjectWithPropertiesOld, + 0, + 0, + 0, + napi_default, + 0}, + }; + + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} diff --git a/benchmark/napi/create_object_with_properties/binding.gyp b/benchmark/napi/create_object_with_properties/binding.gyp new file mode 100644 index 00000000000000..125b7d4cbf91cf --- /dev/null +++ b/benchmark/napi/create_object_with_properties/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ], + 'defines': ['NAPI_EXPERIMENTAL'] + } + ] +} diff --git a/benchmark/napi/create_object_with_properties/index.js b/benchmark/napi/create_object_with_properties/index.js new file mode 100644 index 00000000000000..b099697dd0188f --- /dev/null +++ b/benchmark/napi/create_object_with_properties/index.js @@ -0,0 +1,24 @@ +'use strict'; + +const common = require('../../common.js'); + +let binding; +try { + binding = require(`./build/${common.buildType}/binding`); +} catch { + console.error(`${__filename}: Binding failed to load`); + process.exit(0); +} + +const bench = common.createBenchmark(main, { + n: [1e2, 1e3, 1e4, 1e5, 1e6], + method: ['new', 'old'], +}); + +function main({ n, method }) { + if (method === 'new') { + binding.createObjectWithPropertiesNew(n, bench, bench.start, bench.end); + } else { + binding.createObjectWithPropertiesOld(n, bench, bench.start, bench.end); + } +} diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 0309e36b82c34a..4a8f434a3a17d8 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -2637,6 +2637,43 @@ It is the equivalent of doing `new Object()` in JavaScript. The JavaScript `Object` type is described in [Section object type][] of the ECMAScript Language Specification. +#### `napi_create_object_with_properties` + + + +> Stability: 1 - Experimental + +```cpp +napi_status napi_create_object_with_properties(napi_env env, + napi_value prototype_or_null, + const napi_value* property_names, + const napi_value* property_values, + size_t property_count, + napi_value* result) +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] prototype_or_null`: The prototype object for the new object. Can be a + `napi_value` representing a JavaScript object to use as the prototype, a + `napi_value` representing JavaScript `null`, or a `nullptr` that will be converted to `null`. +* `[in] property_names`: Array of `napi_value` representing the property names. +* `[in] property_values`: Array of `napi_value` representing the property values. +* `[in] property_count`: Number of properties in the arrays. +* `[out] result`: A `napi_value` representing a JavaScript `Object`. + +Returns `napi_ok` if the API succeeded. + +This API creates a JavaScript `Object` with the specified prototype and +properties. This is more efficient than calling `napi_create_object` followed +by multiple `napi_set_property` calls, as it can create the object with all +properties atomically, avoiding potential V8 map transitions. + +The arrays `property_names` and `property_values` must have the same length +specified by `property_count`. The properties are added to the object in the +order they appear in the arrays. + #### `napi_create_symbol`