wasm-bindgen icon indicating copy to clipboard operation
wasm-bindgen copied to clipboard

Copy free method to convert js TypedArrays<T> to Vec<T>

Open UsernameN0tAvailable opened this issue 3 years ago • 5 comments

Motivation

With the heavy usage of Sharedarraybuffers/Arra/Buffers, to share data between workers, you find yourself only using the js typedarrays to operate with the data within the structure.

One way to solve this, is to write a wrapper around such as "SharedVec" or something similar, but you'll always end up havin to create a view first to work with the data, if you dont want to copy -> to_vec(), which copies the whole buffer.

Proposed Solution

Isn't there a way to use the SharedArrayBuffer/ArrayBuffer as the underlying data of the rust Vec<T> , so that we can leverage the Vec api without copying the js Buffer everytime?

UsernameN0tAvailable avatar Jun 16 '22 04:06 UsernameN0tAvailable

Unfortunately, that isn't possible. Vecs can only reference data inside of the WebAssembly module's linear memory, which ArrayBuffers aren't.

If you're the one creating the buffers, you can sort of accomplish this with Uint8Array::view and co., which unsafely create views into Rust's memory that can be used by JS.

Liamolucko avatar Jun 16 '22 11:06 Liamolucko

The problem is, I'm not using js at all. I use on one worker a Vec<T> and a I convert it to a SharedArrayBuffer and on the other context I build my own wrapper SharedVec<T> that implements similar functions as a Vec<T> using the underlying SharedArrayBuffer and the corresponding View for T. So to create the buffer in order to share it, I can use TypedArray::view no problem (to avoid one copy operation), but on the receiving end I have the clunky Wrapper, which has quite an impact on performance, when compared to a normal Vec<T>.

Is there maybe a way to share the whole wasm memory between the context and then reserving the space before another context can fill the vectors, were there any attempts at implementing this at all?

UsernameN0tAvailable avatar Jun 16 '22 13:06 UsernameN0tAvailable

Is there maybe a way to share the whole wasm memory between the context and then reserving the space before another context can fill the vectors, were there any attempts at implementing this at all?

That's pretty much exactly how WebAssembly multithreading works, actually. It requires nightly and a bit of setup, but there's an example here: https://github.com/rustwasm/wasm-bindgen/tree/main/examples/raytrace-parallel

Once it's set up, you can send the Vec's ptr, length and capacity to the other worker and reconstruct it on the other end with Vec::from_raw_parts. Another option would be to create a channel between the workers using the same technique, and then send the Vecs through there.

Hopefully I understood your problem properly this time 😅

Liamolucko avatar Jun 16 '22 21:06 Liamolucko

Yes, you got it right. I'd like my workers to still store a state, so I would avoid rayon, plus would it still be possible to "grow()" the memory once it's shared? Even if you could still share the buffer once it's grown wouldn't the state on one of the workers get somehow corrupted?

UsernameN0tAvailable avatar Jun 16 '22 21:06 UsernameN0tAvailable

I'd like my workers to still store a state, so I would avoid rayon, plus would it still be possible to "grow()" the memory once it's shared? Even if you could still share the buffer once it's grown wouldn't the state on one of the workers get somehow corrupted?

I'm not quite sure what you mean.

If you're asking whether it's possible to grow the whole of the WebAssembly module's memory, then yes, SharedArrayBuffer is specifically designed to allow that.

If you're asking whether you can grow the Vec, it depends what you're doing.

I assumed that you were sending the Vec from one worker to another, and creating a new owned Vec on the other side; in that case, you can grow it just fine, since the other worker doesn't care about it anymore (and should be using mem::forget or ManuallyDrop to avoid deallocating it).

If you're actually sharing it between threads, though, then no; you have to follow regular Rust borrowing rules. So, what you should be sending is a &[T] (or &Vec<T>), which can't be grown.

If you need to have both threads mutate it, then you'll need to wrap it in a Mutex or something, and send a reference to that.

Liamolucko avatar Jun 18 '22 09:06 Liamolucko