diff --git a/hbrt/value.go b/hbrt/value.go index 3c5e332..d8464d6 100644 --- a/hbrt/value.go +++ b/hbrt/value.go @@ -279,6 +279,52 @@ func MakeArrayFrom(items []Value) Value { } } +// ArraySlab is a pre-allocated pool of HbArray headers. SQL scan loops +// create one array per matching row; allocating them one at a time +// generates O(n) heap traffic. This lets the caller allocate all the +// headers in a single backing slice and hand out stable pointers. +// +// Usage: +// slab := hbrt.NewArraySlab(estRows) +// for each row: +// items := flat[off:end:end] +// rows = append(rows, slab.WrapNext(items)) +// +// Each WrapNext advances the slab cursor. If the slab overflows, a new +// backing slice is allocated transparently; pointers already handed out +// remain valid because they reference fixed addresses in the old slice. +type ArraySlab struct { + buf []HbArray + idx int +} + +// NewArraySlab returns an ArraySlab pre-sized for n rows. +func NewArraySlab(n int) *ArraySlab { + if n < 16 { + n = 16 + } + return &ArraySlab{buf: make([]HbArray, n)} +} + +// WrapNext stores items into the next free HbArray slot and returns +// a Value wrapping it. Grows the slab if exhausted. +func (s *ArraySlab) WrapNext(items []Value) Value { + if s.idx >= len(s.buf) { + // Exhausted — allocate a fresh slab. Old slab stays alive because + // previously handed-out pointers reference elements inside it. + newSize := len(s.buf) * 2 + s.buf = make([]HbArray, newSize) + s.idx = 0 + } + ha := &s.buf[s.idx] + ha.Items = items + s.idx++ + return Value{ + info: makeInfo(tArray, 0, 0), + ptr: unsafe.Pointer(ha), + } +} + // MakeObject creates an object Value (array with class). func MakeObject(classID uint16, fieldCount int) Value { ha := &HbArray{Items: make([]Value, fieldCount), Class: classID}