-
Notifications
You must be signed in to change notification settings - Fork 20
Description
Summary
This proposal introduces a lightweight and extensible mechanism to create views of NDArray and Matrix objects in NuMojo, similar to the approach discussed in #169 , but without requiring Mojo support for trait parameters.
The goal is to enable array views as a first-class feature in NuMojo, allowing data sharing and view-based operations while minimizing unnecessary array copies. This lays the groundwork for improved memory efficiency as we continue to rework getter/setter methods and optimize internal data handling.
Motivation
In libraries like NumPy, array slicing and subsetting operations often return views instead of copies, enabling efficient reuse of existing memory. This significantly reduces overhead for large numerical computations.
With Mojo’s strong copy semantics now in place, NuMojo is well-positioned to adopt a similar design. Introducing views represents a key step toward zero-copy operations, allowing users to manipulate subarrays that share the same underlying data buffer.
notes
-
The example above illustrates one possible approach for constructing views of Matrix and NDArray. The specific names of the trait and struct types are open for discussion, they can be refined to better convey intent and improve clarity for future users and contributors.
-
Because this approach is non-intrusive and integrates cleanly with the current data model, it can be gradually incorporated into the existing NDArray and Matrix implementations in subsequent pull requests. Future updates to
__getitem__and__setitem__will align these behaviors more closely with NumPy’s view semantics, ensuring consistency and intuitive usage.
Detailed Explanation
The proposed design adds a simple abstraction layer around view handling through a new Viewable trait and two supporting structs: ViewData and NonViewData that represent arrays views and original arrays. You can find the full example code here.
Trait definition
trait Viewable(ImplicitlyCopyable, Movable):
fn __init__(out self):
...
fn get(self) -> Bool:
...ViewData and NonViewData
struct NonViewData(Viewable, ImplicitlyCopyable, Movable):
alias view: Bool = False
fn __init__(out self):
pass
fn get(self) -> Bool:
return self.view
struct ViewData[is_mutable: Bool, //, origin: Origin[is_mutable]](Viewable, ImplicitlyCopyable, Movable):
alias view: Bool = True
fn __init__(out self):
pass
fn get(self) -> Bool:
return self.viewMatrix
The Matrix type is extended with a View parameter that defaults to NonViewData.
struct Matrix[dtype: DType = DType.float64, View: Viewable = NonViewData](
Copyable, Movable, Sized, Stringable, Writable
):
alias width: Int = simd_width_of[dtype]() #
"""Vector size of the data type."""
var _buf: OwnData[dtype]
"""Data buffer of the items in the NDArray."""
var view: View
"""View information of the NDArray."""
var shape: Tuple[Int, Int]
"""Shape of Tensor."""
var size: Int
"""Size of Tensor."""
var strides: Tuple[Int, Int]
"""Strides of Tensor."""
var flags: Flags
"Information about the memory layout of the array."This design enables both standard (owning) matrices and non-owning view matrices to coexist cleanly, distinguished by their View type parameter.
Constructing a View
A specialized constructor initializes a non-owning Matrix that references data from another array.
@always_inline("nodebug")
fn __init__(
out self,
shape: Tuple[Int, Int],
strides: Tuple[Int, Int],
offset: Int,
ptr: UnsafePointer[Scalar[dtype]],
):
self.shape = shape
self.strides = strides
self.size = shape[0] * shape[1]
self._buf = OwnData(ptr=ptr.offset(offset))
self.view = View()
self.flags = Flags(
self.shape, self.strides, owndata=False, writeable=False
)Example usage
from numojo.core.matrix_view import Matrix
fn main() raises:
var a = Tensor(Tuple[Int, Int](2, 2))
for i in range(4):
a._store(i, val=i)
print("a: ", a)
var b = a[1] # a[1] returns the view of row of array `a`.
print("b :", b)
b._store(0, val=42) # modifies the data of array `a`
print("b_modified: ", b)
print("a_modified: ", a)Possible naming conventions:
trait: Viewable, StorageKind, OwnerShip.
structs: Owning/NonOwning, Owning/View.
field: var ownership, var view.
Impact
Who benefits:
- Library users: Gain the ability to slice, reshape, and manipulate subarrays without memory duplication.
- NuMojo developers: Gain a clear and type-safe foundation for future view-based optimizations (e.g., lazy evaluation, broadcasting).
- Performance-critical applications: Reduced memory footprint and improved computational efficiency.
What changes:
- Addition of Viewable, ViewData, and NonViewData types.
- Extension of Matrix to support a generic view parameter.
- New constructors for creating non-owning view matrices.
References
- Derived and simplified from the earlier [proposal][core] an approach to construct views of array #169
- Inspired by NumPy’s view semantics for arrays and subarray slicing.