I have written a relatively small msgpack implementation in C++, with performance that is not too bad, so I decided to write a NAPI wrapper around it, but the performance is just horrible. I am guessing this is because I'm having to find the type of everything before being able to pack it, and the performance of Napi::Array
and Napi::Object
.
#include <napi.h>
#include <cstdint>
#include <string>
#include <vector>
#include "msgpack.hpp"
namespace node_msgpack {
using namespace std;
using namespace Napi;
bool is_int(Env& env, Value& num) {
return env.Global().Get("Number").ToObject().Get("isInteger").As<Function>().Call({ num }).ToBoolean().Value();
}
void pack(Env& env, vector<Value>& src, msgpack_byte::container& dest, bool initial = true);
void pack(Env& env, Value& src, msgpack_byte::container& dest, bool initial = false) {
switch (src.Type()) {
case napi_boolean: {
msgpack::pack(src.ToBoolean().Value(), dest);
break;
}
case napi_number: {
if (is_int(env, src)) {
int64_t num = src.ToNumber().Int64Value();
if (num >= 0) {
msgpack::pack_uint(num, dest);
}
else {
msgpack::pack_int(num, dest);
}
}
else {
msgpack::pack(src.ToNumber().DoubleValue(), dest);
}
break;
}
case napi_string: {
msgpack::pack(src.ToString().Utf8Value(), dest);
break;
}
case napi_null:
case napi_undefined: {
msgpack::pack(nullptr, dest);
break;
}
case napi_object: {
if (src.IsArray()) {
Array input_js_arr = src.As<Array>();
const uint32_t n = input_js_arr.Length();
vector<Value> input_vec;
input_vec.resize(n);
for (uint32_t i = 0; i < n; i++) {
input_vec[i] = (input_js_arr.Get(i));
}
pack(env, input_vec, dest);
}
else {
Object input_js_obj = src.As<Object>();
Array obj_props = input_js_obj.GetPropertyNames();
const uint32_t n = obj_props.Length();
if (initial) {
dest.check_resize(size_t(msgpack::LengthOf(src) * compression_percent));
}
if (n <= 15) {
dest.push_back(fixmap_t(n));
}
else if (n <= umax16) {
dest.push_back(uint8_t(map16));
dest.push_back(uint16_t(n));
}
else if (n <= umax32) {
dest.push_back(uint8_t(map32));
dest.push_back(uint32_t(n));
}
for (uint32_t i = 0; i < n; i++) {
Value key = obj_props.Get(i);
Value val = input_js_obj.Get(key);
pack(env, key, dest, false);
pack(env, val, dest, false);
}
if (initial) {
dest.shrink_to_fit();
}
}
break;
}
}
}
void pack(Env& env, vector<Value>& src, msgpack_byte::container& dest, bool initial) {
size_t n = src.size();
if (initial) {
dest.check_resize(size_t(msgpack::LengthOf(src) * compression_percent));
}
if (n <= 15) {
dest.push_back(fixarray_t(n));
}
else if (n <= umax16) {
dest.push_back(uint8_t(arr16));
dest.push_back(uint16_t(n));
}
else if (n <= umax32) {
dest.push_back(uint8_t(arr32));
dest.push_back(uint32_t(n));
}
for (uint32_t i = 0; i < n; i++) {
pack(env, src[i], dest, false);
}
if (initial) {
dest.shrink_to_fit();
}
}
String stringify(const CallbackInfo& info) {
Env env = info.Env();
msgpack_byte::container dest;
if (info.Length() == 0) {
Error::New(env, "Expected an argument").ThrowAsJavaScriptException();
return String::New(env, "");
}
Value input = info[0];
pack(env, input, dest, true);
return String::New(env, msgpack_byte::to_string(dest));
}
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "stringify"), Napi::Function::New(env, node_msgpack::stringify));
return exports;
}
NODE_API_MODULE(addon, Init)
The msgpack::pack
, msgpack::pack_uint
and msgpack::pack_int
calls the pack
function to the plain C++ msgpack library I wrote https://github.com/t348575/msgpack
Am I using the most efficient method to check types? I tried using visual benchmarking like shown here, but I could not get anything useful out of it. To compare, the napi wrapper takes around 20 seconds to stringify ~400MB of data whereas the plain C++ takes only 0.4s to do the same. The entire code can be found here