Skip to content

feat(perf): Remove unnecessary construction of native ArrayBuffer#1015

Merged
boorad merged 1 commit into
margelo:mainfrom
wh201906:wh201906/no-copy
May 3, 2026
Merged

feat(perf): Remove unnecessary construction of native ArrayBuffer#1015
boorad merged 1 commit into
margelo:mainfrom
wh201906:wh201906/no-copy

Conversation

@wh201906
Copy link
Copy Markdown
Contributor

@wh201906 wh201906 commented May 2, 2026

This PR reduces unnecessary data copying from ToNativeArrayBuffer()
As documented, it's safe to use the non-owning ArrayBuffer before the sync function returns. I checked such cases and removed ToNativeArrayBuffer() in them.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 2, 2026

@wh201906 is attempting to deploy a commit to the Margelo Team on Vercel.

A member of the Team first needs to authorize it.

@boorad
Copy link
Copy Markdown
Collaborator

boorad commented May 3, 2026

@wh201906 thanks for this! Claude review agrees:

Verdict: safe to merge

The PR is correctly scoped. ToNativeArrayBuffer is a deep memcpy from a JS-owned buffer into a C++-owned one (QuickCryptoUtils.hpp:41-46). Removing it is safe iff the data is consumed before the sync call returns — and that's exactly the discipline this PR follows.

Why ownership is fine here

I verified every modified call site against three things:

  1. The Nitro spec returns are all synccipher.nitro.ts, sign.nitro.ts, rsaCipher.nitro.ts. Zero Promise<T> on any touched method. Nitro guarantees non-owning ArrayBuffers are valid for the entire sync call → JS GC can't run, can't relocate or free the backing store mid-call.
  2. OpenSSL consumes synchronously in every modified site — EVP_CipherUpdate, EVP_DigestUpdate, EVP_PKEY_encrypt/decrypt/sign, EVP_DigestVerify, EVP_CIPHER_CTX_ctrl(...SET_TAG/SET_IVLEN). None of these stash the input pointer for later use.
  3. The shared_ptr lifetime brackets the useupdate()/setAAD()/setAuthTag() take const std::shared_ptr<ArrayBuffer>&, init() takes by value. Either way, the strong ref lives in the local frame; data->data() is valid until return.

The two subtle cases I scrutinized — both fine

  • HybridRsaCipher OAEP label (HybridRsaCipher.cpp:102+): EVP_PKEY_CTX_set0_rsa_oaep_label takes ownership of the buffer (note the set0_), so it has to outlive the call. The PR correctly keeps the OPENSSL_malloc + memcpy and just removes the redundant prior copy into native_label. We were copying twice; now we copy once. ✓
  • HybridSignHandle::update / HybridVerifyHandle::update: these accumulate data into a std::vector data_buffer for one-shot Ed25519/Ed448/ML-DSA paths. The accumulation is data_buffer.insert(end, ptr, ptr+size) — that's already a copy into owned storage, so the JS buffer can vanish after the call. ✓

Things worth verifying before merging

  • No threads/coroutines spawned inside any of these sync entry points that capture data->data() and outlive the call. I didn't see any, but the cipher hierarchy has subclasses — worth a fresh pair of eyes on HybridCipher::update's overrides.
  • No mutation of the borrowed buffer. PR only reads — confirmed by inspection of every diff hunk.
  • Run the cipher + sign/verify test vectors in the example app (NIST/RFC vectors). Behavior is unchanged so they should pass; if they don't, that's a real bug.
  • The PR leaves ToNativeArrayBuffer callers in HybridScrypt, HybridPbkdf2, HybridArgon2, HybridHkdf, HybridRandom, etc. untouched — likely because those have async/threaded paths. That conservatism is correct; same trick can't be applied blindly there.

Perf upside

Real and measurable. For cipher.update() with multi-MB plaintexts, this drops one full-buffer memcpy + heap alloc per call. On the hot path (chunked encryption of large payloads) that's roughly halving the per-update memory bandwidth. Worth taking.

@boorad boorad changed the title Remove unnecessary construction of native ArrayBuffer feat(perf): Remove unnecessary construction of native ArrayBuffer May 3, 2026
@boorad boorad merged commit c614fe5 into margelo:main May 3, 2026
5 of 6 checks passed
@boorad
Copy link
Copy Markdown
Collaborator

boorad commented May 3, 2026

@wh201906 also, can you start using the prefixes for PR titles: fix, feat, chore, docs etc...

@wh201906 wh201906 deleted the wh201906/no-copy branch May 3, 2026 15:06
@wh201906
Copy link
Copy Markdown
Contributor Author

wh201906 commented May 3, 2026

can you start using the prefixes for PR titles

Sure!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants