-
-
Notifications
You must be signed in to change notification settings - Fork 402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
More efficient conversion of Vec to JsArray #2058
Comments
The thing is that a JS Array is an array of values of any type, which means that every value in the Rust array must be converted to a JS value. I don't think there is much room for improvement here (maybe pre-allocation, which we might be already doing) But, the ideal JS type for a |
Awesome, I will experiment with that next, thanks! |
I'm ready to tackle this, can you show me some code for what is currently possible converting a |
Most of this work would be done here: https://github.com/boa-dev/boa/blob/main/boa_engine/src/object/jstypedarray.rs The idea is to add another This let my_vector: Vec<u8> = vec![];
let array_buffer = boa_engine::builtins::array_buffer::ArrayBuffer {
array_buffer_data: Some(my_vector),
array_buffer_byte_length: 8,
array_buffer_detach_key: JsValue::undefined()
};
let array_buffer_js_value: JsValue = array_buffer.into(); It might be useful to have a Then, you can create the internal The last part, of converting a You can use I think the best approach here is to have a 3-method interface: |
@Razican Can you really implement a |
I've attempted to follow your guidance here but run into a major problem early on. Here's what I'm trying to add to https://github.com/boa-dev/boa/blob/main/boa_engine/src/object/jstypedarray.rs: impl From<Vec<$element>> for $name {
#[inline]
fn from(o: Vec<$element>) -> Self {
let o_len = o.len();
let array_buffer = crate::builtins::array_buffer::ArrayBuffer {
array_buffer_data: Some(o),
array_buffer_byte_length: o_len,
array_buffer_detach_key: JsValue::undefined()
};
let uint8_array_js_object = crate::object::JsObject::from_proto_and_data(
Context::default().intrinsics().constructors().$constructor_object().prototype(),
crate::object::ObjectData {
kind: crate::object::ObjectKind::ArrayBuffer(array_buffer),
internal_methods: &crate::object::internal_methods::ORDINARY_INTERNAL_METHODS
}
);
// TODO not finished here
}
} The problem is that Here's some of the conversation from Discord: Person 1:
Person 2:
|
This PR implements an optimization done by V8 and spidermonkey. Which stores indexed properties in two class storage methods dense and sparse. The dense method stores the property in a contiguous array ( `Vec<T>` ) where the index is the property key. This storage method is the default. While on the other hand we have sparse storage method which stores them in a hash map with key `u32` (like we currently do). This storage method is a backup and is slower to access because we have to do a hash map lookup, instead of an array access. In the dense array we can store only data descriptors that have a value field and are `writable`, `configurable` and `enumerable` (which by default array elements are). Since all the fields of the property descriptor are the same except value field, we can omit them an just store the `JsValue`s in `Vec<JsValue>` this decreases the memory consumption and because it is smaller we are less likely to have cache misses. There are cases where we have to convert from dense to sparse (the slow case): - If we insert index elements in a non-incremental way, like `array[1000] = 1000` (inserting an element to an index that is already occupied only replaces it does not make it sparse) - If we delete from the middle of the array (making a hole), like `delete array[10]` (only converts if there is actualy an element there, so calling delete on a non-existent index property will do nothing) Once it becomes sparse is *stays* sparse there is no way to convert it again. (the computation needed to check whether it can be converted outweighs the benefits of this optimization) I did some local benchmarks and on array creation/pop and push there is ~45% speed up and the impact _should_ be bigger the more elements we have. For example the code below on `main` takes `~21.5s` while with this optimization is `~3.5s` (both on release build). ```js let array = []; for (let i = 0; i < 50000; ++i) { array[i] = i; } ``` In addition I also made `Array::create_array_from_list` do a direct setting of the properties (small deviation from spec but it should have the same behaviour), with this #2058 should be fixed, conversion from `Vec` to `JsArray`, not `JsTypedArray` for that I will work on next :)
This PR adds the `ArrayBuffer` rust wrapper. It also provides a capability to construct a `JsArrayBuffer` from a user defined blob of data ( `Vec<u8>` ) and it is not cloned, it is directly used as the internal buffer. This allows us to replace the inifficent `Vec<u8>` to `JsArray` then to `TypedArray` (in typed arrays `from_iter`), with a `JsArrayBuffer` created from user data to `TypedArray`. With this `Vec<u8>` to `JsTypedArray` should be fully fixed as discussed in #2058.
I haven't tested it yet, our solution is still a bit hacky. But this looks like it would probably solve the problem. I plan to incorporate this eventually. |
I will close this, since the solution seems to be there. If anyone finds that this is still a problem, feel free to comment here, and we can re-open it. |
<!--- Thank you for contributing to Boa! Please fill out the template below, and remove or add any information as you feel necessary. ---> This Pull Request is related to the #2058 and the discussion in the discord chat. It changes the following: - Adds a `take` method to `JsArrayBuffer` - Builds out `JsArrayBuffer` docs - Adds a `JsArrayBuffer::take()` example to `jsarraybuffer.rs` in `boa_examples`
I just want to bring this up and get some preliminary feedback. My project Azle is a TS/JS environment for the Internet Computer, a Wasm environment. It is relatively resource-constrained compared with other cloud solutions at the moment, and all computation is metered with a unit called cycles. Each RPC call into a canister has a cycle limit.
Users will need to deal with large arrays of
Vec<u8>
to deal with raw binary data. Right now it seems like the conversion betweenVec<u8>
andJsArray
(to get the user's raw binary data into the canister in the RPC call) is extremely inefficient, insomuch that the cycles limit on the computation is reached and the call fails. I am reaching the limit on arrays of length between 10,000 and 25,000 (~10-20kb).What can be done to make the conversion more efficient? And btw I am not 100% it's Boa's conversion that is causing the problem, I think my code can be optimized to some extent. But I fear the BigO of the JsArray code (like
from_iter
) will just be insufficient. What can be done if this is the case? Is there a possibility of just sharing a memory location with a boa context so that we do not need to iterate through entire arrays to convert them over?The text was updated successfully, but these errors were encountered: