Wasm-bindgen: Difference between Float32Array::new().subarray and Float32Array::view()

Created on 23 Jun 2019  路  5Comments  路  Source: rustwasm/wasm-bindgen

Summary

The docs show Float32Array::view() as unsafe while the webgl example does not use this function - rather it uses a combo of Float32Array::new() and Float32Array::subarray

Is there a performance tradeoff happening with this approach, that would be gained by switching to view() ?

Additional Details

If we are always able to get a view into Float32Array, without really allocating, I wonder if we actually gain from bufferSubData() ? (e.g. https://docs.rs/web-sys/0.3.7/web_sys/struct.WebGl2RenderingContext.html#method.buffer_sub_data_with_i32_and_array_buffer)

question

Most helpful comment

@dakom (Little late, but...)

The reason for bufferSubData in WebGL2 isn't to avoid copying.

When you call view in Rust, it doesn't need to copy the memory, but it still has to allocate a new JS Float32Array object, and then use that object, and then garbage collect it.

When the bufferSubData method is called a lot, this extra allocation and garbage collection can have a significant cost.

So the point of bufferSubData is that it doesn't need to create a new Float32Array object.

In other words, with WebGL1 the Rust code looks like this (including the code for view):

unsafe fn view(slice: &[f32]) -> Float32Array {
    let buf = wasm_bindgen::memory();
    let mem = buf.unchecked_ref::<WebAssembly::Memory>();

    // Allocates a new Float32Array object in JS
    Float32Array::new_with_byte_offset_and_length(
        &mem.buffer(),
        slice.as_ptr() as u32,
        slice.len() as u32,
    )
}

gl.buffer_sub_data_with_i32_and_array_buffer_view(
    target,
    offset,
    &unsafe { view(&slice) },
);

But with WebGL2 it would look like this:

let buf = wasm_bindgen::memory();
let mem = buf.unchecked_ref::<WebAssembly::Memory>();

gl.buffer_sub_data_with_i32_and_array_buffer_view(
    target,
    offset,
    &mem.buffer(),
    slice.as_ptr() as u32,
    slice.len() as u32,
);

Note the lack of needing to create a new Float32Array object, it can just index into the wasm memory directly.

All 5 comments

Is there a performance tradeoff happening with this approach, that would be gained by switching to view() ?

Ah that was just an old example which predated the existence of view, I've sent a PR to update it since using view is more idiomatic!

If we are always able to get a view into Float32Array, without really allocating, I wonder if we actually gain from bufferSubData() ? (

I think so! I believe that's a method on WebGl2RenderingContext though while the example is currently using WebGlRenderingContext (lack of 2)

Nice!

Re: optimization w/bufferSubData...

For typical JS I think the performance win is not needing to allocate new TypedArrays each time- rather one can just keep a big ArrayBuffer around and then use bufferSubData to upload from different parts of it.

Since WASM is pretty much already doing that (e.g. we get a TypedArray precisely the same way), I'm wondering if we already get that benefit for free... in fact bufferSubData might even be a very slight loss due to calculating offsets twice!

I'm new to webgl2 though and it could be I'm just missing a totally different use case

Oh no you're right about the perf optimization, I think it's just that webgl doesn't have support for that but webgl2 does.

Closing with the example update in https://github.com/rustwasm/wasm-bindgen/pull/1618

@dakom (Little late, but...)

The reason for bufferSubData in WebGL2 isn't to avoid copying.

When you call view in Rust, it doesn't need to copy the memory, but it still has to allocate a new JS Float32Array object, and then use that object, and then garbage collect it.

When the bufferSubData method is called a lot, this extra allocation and garbage collection can have a significant cost.

So the point of bufferSubData is that it doesn't need to create a new Float32Array object.

In other words, with WebGL1 the Rust code looks like this (including the code for view):

unsafe fn view(slice: &[f32]) -> Float32Array {
    let buf = wasm_bindgen::memory();
    let mem = buf.unchecked_ref::<WebAssembly::Memory>();

    // Allocates a new Float32Array object in JS
    Float32Array::new_with_byte_offset_and_length(
        &mem.buffer(),
        slice.as_ptr() as u32,
        slice.len() as u32,
    )
}

gl.buffer_sub_data_with_i32_and_array_buffer_view(
    target,
    offset,
    &unsafe { view(&slice) },
);

But with WebGL2 it would look like this:

let buf = wasm_bindgen::memory();
let mem = buf.unchecked_ref::<WebAssembly::Memory>();

gl.buffer_sub_data_with_i32_and_array_buffer_view(
    target,
    offset,
    &mem.buffer(),
    slice.as_ptr() as u32,
    slice.len() as u32,
);

Note the lack of needing to create a new Float32Array object, it can just index into the wasm memory directly.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

arilotter picture arilotter  路  3Comments

pauldorehill picture pauldorehill  路  3Comments

bantic picture bantic  路  4Comments

gitmko0 picture gitmko0  路  3Comments

expobrain picture expobrain  路  4Comments