Poor Buffer (and possibly bigint?) performance
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 7.9k
- Forks
- 412
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 61
Description
It looks like passing buffer using napi-rs introduces a lot of computing overhead.
I discovered this while working on a new library hoping to replace the current native C binding. I wanted to benchmark the new code and to my surprise the rust version was a lot slower. The benchmark is basically a for loop to 40000000 in nodejs calling every time the associated fn.
The library I was working on should deal with both BigInts and Buffers so i created a simple .rs lib containing several empty functions whose signature can be seen in the results below:
pub fn bigint_u8refarr(n: BigInt, out: &[u8]) -> (): 129ns/op
pub fn bigint_buffer(n: BigInt, out: Buffer) -> (): 134ns/op
pub fn bigint_only(n: BigInt) -> (): 46ns/op
pub fn buffer_only(out: Buffer) -> (): 98ns/op
pub fn u8refarr_only(out: &[u8]) -> (): 97ns/op
pub fn nothing() -> (): 14ns/op
I then created a .c file, compiled with node-gyp and here the results:
c.noop: 12ns/op
bufferOnly_c: 26ns/op
bufferOnlyWithRef_c: 56ns/op
noop is self-explanatory and is on-par with nothing() in rust.
bufferOnly_c and bufferOnlyWithRef_c are basically the same with the exception that "WithRef" also handles napi_create_reference and napi_delete_reference which i saw is being used in napi-rs in the FromNapiValue trait implementation for Buffer.
anyway. here the code:
napi_value bufferOnly (napi_env env, napi_callback_info info) {
napi_value argv[1];
napi_status status;
size_t argc = 1;
status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
assert(status == napi_ok);
size_t byte_width;
uint8_t* raw_buffer;
status = napi_get_buffer_info(env, argv[0], (void**) &raw_buffer, &byte_width);
assert(status == napi_ok);
napi_value result;
napi_get_undefined(env, &result);
return result;
}
napi_value bufferOnlyWithRef (napi_env env, napi_callback_info info) {
napi_value argv[1];
napi_status status;
size_t argc = 1;
status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
assert(status == napi_ok);
napi_ref ref;
status = napi_create_reference(env, argv[0], 1, &ref);
assert(status == napi_ok);
size_t byte_width;
uint8_t* raw_buffer;
status = napi_get_buffer_info(env, argv[0], (void**) &raw_buffer, &byte_width);
assert(status == napi_ok);
// delete ref
status = napi_delete_reference(env, ref);
assert(status == napi_ok);
// return argv[0];
napi_value result;
napi_get_undefined(env, &result);
return result;
}
There is obviously some other overhead in rust implementation that might justify this gap:
bufferOnlyWithRef_c: 56ns/op
pub fn buffer_only(out: Buffer) -> (): 98ns/op
I'm out of ideas on how to optimize this and to be honest i'm not even sure why we want to use napi_create/delete_reference.
Any other insight is more than welcome. If needed i can provide a github repo with some messy code i was playing with.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Buffer FromNapiValue implementation and compare its napi_create_reference and napi_delete_reference behavior with the C bufferOnlyWithRef entry point and the reported benchmark results. Done means identifying the source of the overhead and demonstrating a measured improvement for Buffer and possibly BigInt arguments.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nodejs, rust
- Domain
- performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100