Returning a Buffer without memory copy #1171
-
I am trying to create a The type I start with is an Here's my initial code: #[napi]
pub fn parse(src: String) -> napi::Result<Buffer> {
let program = parseImpl(src);
let aligned_vec: rkyv::AlignedVec = rkyv::to_bytes::<_, 512>(&program).unwrap();
let vec: Vec<u8> = aligned_vec.into_vec();
let buffer = Buffer::from(vec);
Ok(buffer)
} If I comment out the lines from I also tried using a pointer: let ptr: *mut u8 = aligned_vec.as_mut_ptr();
let len: usize = aligned_vec.len();
let capacity: usize = aligned_vec.capacity();
mem::forget(aligned_vec);
let vec = unsafe { Vec::from_raw_parts(ptr, len, capacity) };
let buffer = Buffer::from(vec);
Ok(buffer) This performs the same. However, if I comment out Is there any way to avoid memory copy when creating a At present, I'm only converting to As you can probably tell from the way I'm talking, I'm new to Rust, so apologies if what I'm asking is stupid. Would really appreciate any help you can give. Background on what I'm trying to achieve: swc-project/swc#2175 |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
You can use use napi::{bindgen_prelude::*, JsBuffer};
use napi_derive::napi;
#[napi]
pub fn parse(env: Env, src: String) -> napi::Result<JsBuffer> {
let program = parseImpl(src);
let aligned_vec: rkyv::AlignedVec = rkyv::to_bytes::<_, 512>(&program).unwrap();
let aligned_data_len = aligned_vec.len();
unsafe {
env.create_buffer_with_borrowed_data(aligned_vec.as_ptr(), aligned_vec_len, aligned_vec, |aligned_vec, _| {
drop(aligned_vec);
})
}
} |
Beta Was this translation helpful? Give feedback.
-
For anyone else who encounters this and is interested: It turned out most of the slowdown I was experiencing was actually due to the JS benchmarking framework I used, which it turns out runs the entire benchmark synchronously with no breaks (ticks) between runs. This meant V8 had no opportunity to garbage collect, so buffers returned to JS ate up more and more memory as the benchmark progressed, whereas buffers that stayed on Rust side got freed promptly. It was the memory pressure which slowed down JS, more than the cost of conversion to a buffer, and once I inserted some ticks to allow buffers to be freed on JS side, the performance gap I was asking about above narrowed dramatically. That said, I did also find a way to avoid a memory copy on Rust side which also yielded a speed gain: let aligned_vec: rkyv::AlignedVec = rkyv::to_bytes::<_, 512>(&program).convert_err()?;
let buffer = unsafe {
Uint8Array::with_external_data(
aligned_vec.as_mut_ptr(),
aligned_vec.len(),
move |_ptr, _len| drop(aligned_vec),
)
}; NB My understanding is that it's important to drop types with same layout they're created with. In this case |
Beta Was this translation helpful? Give feedback.
For anyone else who encounters this and is interested:
It turned out most of the slowdown I was experiencing was actually due to the JS benchmarking framework I used, which it turns out runs the entire benchmark synchronously with no breaks (ticks) between runs. This meant V8 had no opportunity to garbage collect, so buffers returned to JS ate up more and more memory as the benchmark progressed, whereas buffers that stayed on Rust side got freed promptly. It was the memory pressure which slowed down JS, more than the cost of conversion to a buffer, and once I inserted some ticks to allow buffers to be freed on JS side, the performance gap I was asking about above narrowed dramatically.
Th…