Skip to content

An experimental drop-in replacement for Mojo stdlib DynamicVector that demonstrates new features using References

License

Notifications You must be signed in to change notification settings

mikowals/dynamic_vector.mojo

Repository files navigation

Mojo Reference Demo - Mojo v24.1.0

Run Tests

Mojo🔥 test runner using pytest.

As of June 2024 I think most of the features this example demonstrated are now available in Mojo's nightly branch and will probably be in the next released version. The code here is now very out of date and is probably best to disappear soon to avoid any confusion about Mojo functionality or syntax.


An experimental drop in replacement for DynamicVector. It almost certainly has some bugs and it is likely that the nice things it does with References will be implemented in the Standard Library in a more reliable way shortly. But it shows why References are useful and demonstrates new features like __refitem__ and __lifetime_of(self).

The repo contains:

__setitem__ works much better with References

The current Standard Library implementation of __setitem__ often uses __getitem__ to simulate in-place updates. And since __getitem__ produces a copy this leads to a large amount of extra copies, moves, and deletes. You can see all this extra activity very clearly in a simple 2x2 DynamicVector[DynamicVector[Verbose]] where Verbose is a custom struct that logs all these events.

Running mojo verbose.mojo outputs all lifecycle events from 2x2 nested stdlib vectors when a single field is updated. Then it repeats the experiement with vectors using References internally. The Reference version has no unnecessary lifecycle events.

Calling vec[1][1].value = 1000 made of stdlib DynamicVectors produces: 5 copies, 5 deletes, and 10 moves:

update one value in std lib vector of vectors
copyinit with value: 0 hidden_id: 100
moveinit with value: 0 hidden_id 100
copyinit with value: 1 hidden_id: 101
moveinit with value: 1 hidden_id 101
copyinit with value: 1 hidden_id: 101
moveinit with value: 0 hidden_id 100
del with value: 0 hidden_id 100
moveinit with value: 1 hidden_id 101
del with value: 1 hidden_id 101
copyinit with value: 0 hidden_id: 100
moveinit with value: 0 hidden_id 100
copyinit with value: 1 hidden_id: 101
moveinit with value: 1 hidden_id 101
moveinit with value: 1 hidden_id 101
del with value: 1 hidden_id 101
moveinit with value: 1000 hidden_id 101
moveinit with value: 0 hidden_id 100
del with value: 0 hidden_id 100
moveinit with value: 1 hidden_id 101
del with value: 1 hidden_id 101
finished update in std lib vector of vectors

Calling vec[1][1].value = 2000 with Reference based DynamicVector does no intermediate copies, deletes, or moves.

update one value in reference based vector of vectors
finished update in reference based vector of vectors

Keep in mind this is a 2x2 vector of vectors but the same problem occurs for any struct that owns another struct and allows updates with __setitem__ (usually called with some_struct[index] = ). There is a lot of extra activity if updates aren't truely done in-place using References.

Time savings when updating struct elements

mojo vector_benchmark.mojo will time updating each value one by one in 256 x 256 DynamicVector[DynamicVector[Int]].

Std lib vector of vectors: 7.2681428571428572 milliseconds
Reference based vector of vectors: 0.022431906976744187 milliseconds
__setitem__ with References is  324.00913862017853 times faster than the std lib version.

This is the time savings by switching to efficient Reference usage - avoiding the extra activity in the previous demo - for a relatively small number of nested fields. While the nesting multiplies the time savings in this example, there will be some savings in any update of a struct using __setitem__ in DynamicVector.

The larger the struct the larger the savings, independent of how small field being updated is. In this example, we are only updating a reference passable ("trivial") Int and it still triggers copies and deletes of sibling values. These are avoided if instead you update in-place via a Reference.

Python-style slices - var evens = vec[0::2]

mojo slice_demo.mojo creates some slices and demonstrates in-place updates since each slice holds a Reference to the original vector.

original vec (size = 16) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
slice_1 = vec[0::2] (size = 8) [0, 2, 4, 6, 8, 10, 12, 14]
slice_2 = slice_1[0::2] (size = 4) [0, 4, 8, 12]

after slice_2[2] = 1000
slice_2 = slice_1[0::2] (size = 4) [0, 4, 1000, 12]
slice_1 = vec[0::2] (size = 8) [0, 2, 4, 6, 1000, 10, 12, 14]
original vec (size = 16) [0, 1, 2, 3, 4, 5, 6, 7, 1000, 9, 10, 11, 12, 13, 14, 15]

after vec[1:3] = [100, 200]
original vec (size = 16) [0, 100, 200, 3, 4, 5, 6, 7, 1000, 9, 10, 11, 12, 13, 14, 15]

The real world use case is something like Fast Fourier Transform Demo where the Python-style slices are readable and efficient.

About

An experimental drop-in replacement for Mojo stdlib DynamicVector that demonstrates new features using References

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages