napi-rs / napi-rs/napi-rs

Poor Buffer (and possibly bigint?) performance

Open
#1,973 5 comments 0 reactions 0 assignees View on GitHub

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.