For those who aren’t aware, Swift doesn’t round the sizes of types up to a multiple of the alignment, instead allowing other data to occupy that space. You still need to include the trailing padding when stepping between elements in an array, and so Swift also has the idea of “stride”, which is what most languages call size.
An example in case I’m not making sense
Maybe it’d be clearer with an example:const Thing = struct {
big: u16,
small: u8,
};
With how Zig currently works, this is laid out in memory as
offset field
====== =====
0 big
1 big
2 small
3 _padding
@sizeOf(Thing)
is 4 bytes and @alignOf(Thing)
is 2 bytes. If you have an array of them your memory is laid out like this:
+---+---+---+---+---+---+---+---+---+---+---+---+
| B | B | S | _ | B | B | S | _ | B | B | S | _ |
+---+---+---+---+---+---+---+---+---+---+---+---+
This is necessary so each Thing
instance has an alignment of 2.
Now, imagine we have this:
const BigThing = struct {
t: Thing,
extra: u8,
};
It is laid out in memory as
offset field
====== =====
0 t.big
1 t.big
2 t.small
3 t._padding
4 extra
5 _padding
We end up with extra padding at the end of the t
field, just so that if we have an array of Thing
each instance is aligned – but we don’t have an array of Thing
s here!
Swift fixes this by making @sizeOf(Thing)
equal to 3 and @strideOf(Thing)
equal to 4, i.e. the size doesn’t include the padding at the end. This is the resulting memory layout of BigThing
:
offset field
====== =====
0 t.big
1 t.big
2 t.small
3 extra
@sizeOf(BigThing)
and @strideOf(BigThing)
are equal to 4.
Have people working on Zig considered this approach? Intuitively it feels like the sort of thing that Zig would do.
As far as I can tell this is a pure win in terms of cache utilization, memory consumption, etc, with the only downside being unfamiliarity. I suspect that this is the sort of thing that’s easy to learn once and not forget, though.
I’d love to hear everyone’s thoughts!