We need to analyze this a bit deeper because the test you provided hides some of the nuances of this data structure.
Let’s look at the implementation of MultiArrayList
…
The outer portion of MultiArrayList
is the same size - it has a pointer, a length and a capacity (not unlike a slice and a capacity as in ArrayListUnmanaged
):
bytes: [*]align(@alignOf(T)) u8 = undefined,
len: usize = 0,
capacity: usize = 0,
Now, this seems well and good, but you’ll notice a bit further down that there is a helper type named Slice
that has a different set of data members.
pub const Slice = struct {
/// This array is indexed by the field index which can be obtained
/// by using @intFromEnum() on the Field enum
ptrs: [fields.len][*]u8,
len: usize,
capacity: usize,
Here, you can see that this has an array of pointers equal to the length of the number of fields. If you have a long list of fields, this is quite a bit larger.
The question is, how is this internal structure used? That could have consequences.
When you call items
to get your items, it calls slice under-the-hood:
/// Get the slice of values for a specified field.
/// If you need multiple fields, consider calling slice()
/// instead.
pub fn items(self: Self, comptime field: Field) []FieldType(field) {
return self.slice().items(field);
}
Here’s how slice is constructed:
/// Compute pointers to the start of each field of the array.
/// If you need to access multiple fields, calling this may
/// be more efficient than calling `items()` multiple times.
pub fn slice(self: Self) Slice {
var result: Slice = .{
.ptrs = undefined,
.len = self.len,
.capacity = self.capacity,
};
var ptr: [*]u8 = self.bytes;
for (sizes.bytes, sizes.fields) |field_size, i| {
result.ptrs[i] = ptr;
ptr += field_size * self.capacity;
}
return result;
}
It’s important to check the assembly. While these Slice
objects are probably transient in your case compared to the instance of the MultiArrayList
, they aren’t without a cost and they may have a size on the stack in certain conditions. More importantly, you have to calculate pointer offsets when you create them.
Long story short, a series of ArrayListUnmanaged
and this have different costs/benefits by design.