diff --git a/hbrdd/ntx/build.go b/hbrdd/ntx/build.go index cc581ab..760ebdc 100644 --- a/hbrdd/ntx/build.go +++ b/hbrdd/ntx/build.go @@ -551,7 +551,7 @@ func (idx *Index) insertKeyBTree(key []byte, recNo uint32) error { itemSize := int(idx.header.ItemSize) dataStart := 2 + (maxItem+1)*2 - newRoot := &Page{data: [BlockSize]byte{}, keyCount: 0} + newRoot := &Page{data: make([]byte, BlockSize), keyCount: 0} for i := 0; i <= maxItem; i++ { binary.LittleEndian.PutUint16(newRoot.data[2+i*2:4+i*2], uint16(dataStart+i*itemSize)) } @@ -635,7 +635,7 @@ func (idx *Index) pageSplit(page *Page, iKey int, key []byte, recNo uint32, chil // Allocate new page (will be the LEFT / lower half) newPageOff := int64(idx.header.NextPage) idx.header.NextPage += uint32(BlockSize) - newPage := &Page{data: [BlockSize]byte{}} + newPage := &Page{data: make([]byte, BlockSize)} // Initialize offset table for i := 0; i <= maxItem; i++ { binary.LittleEndian.PutUint16(newPage.data[2+i*2:4+i*2], uint16(dataStart+i*itemSize)) diff --git a/hbrdd/ntx/ntx.go b/hbrdd/ntx/ntx.go index 6fc4b43..a4b5af6 100644 --- a/hbrdd/ntx/ntx.go +++ b/hbrdd/ntx/ntx.go @@ -102,36 +102,52 @@ func (h *Header) GetTagName() string { return trimNull(h.TagName[:]) } // [recNo: 4 bytes LE] — record number // [keyValue: keyLen bytes] — key data type Page struct { - offset int64 // file offset of this page - data [BlockSize]byte + offset int64 + data []byte // BoltDB-style: slice into mmap (zero-copy) or owned buffer keyCount uint16 changed bool } -// LoadPage reads a page from the file. -// LoadPage reads a page from file (no cache — used by tests and one-off reads). +// pagePool reuses Page structs to reduce GC pressure. +var pagePool [8]Page +var pagePoolIdx int + +func acquirePage() *Page { + p := &pagePool[pagePoolIdx%len(pagePool)] + pagePoolIdx++ + return p +} + +// LoadPage reads a page from the file (used by tests and one-off reads). func LoadPage(f *os.File, offset int64) (*Page, error) { - p := &Page{offset: offset} - if _, err := f.ReadAt(p.data[:], offset); err != nil { + p := &Page{offset: offset, data: make([]byte, BlockSize)} + if _, err := f.ReadAt(p.data, offset); err != nil { return nil, fmt.Errorf("read NTX page at %d: %w", offset, err) } p.keyCount = binary.LittleEndian.Uint16(p.data[0:2]) return p, nil } -// cachedLoadPage loads a page — zero-copy from mmap, or file read fallback. +// cachedLoadPage — BoltDB-style zero-copy: returns slice into mmap. +// No 1024-byte copy. Page.data points directly into mmap memory. func (idx *Index) cachedLoadPage(offset int64) (*Page, error) { - p := &Page{offset: offset} + p := acquirePage() + p.offset = offset if idx.mmapData != nil && offset >= 0 && int(offset)+BlockSize <= len(idx.mmapData) { - // Zero-copy: slice directly into mmap'd memory - copy(p.data[:], idx.mmapData[offset:offset+BlockSize]) + // Zero-copy: data is a slice into mmap (no copy!) + p.data = idx.mmapData[offset : offset+BlockSize] p.keyCount = binary.LittleEndian.Uint16(p.data[0:2]) return p, nil } - // Fallback: read from file - if _, err := idx.file.ReadAt(p.data[:], offset); err != nil { + // Fallback: allocate and read from file + if p.data == nil || cap(p.data) < BlockSize { + p.data = make([]byte, BlockSize) + } else { + p.data = p.data[:BlockSize] + } + if _, err := idx.file.ReadAt(p.data, offset); err != nil { return nil, fmt.Errorf("read NTX page at %d: %w", offset, err) } p.keyCount = binary.LittleEndian.Uint16(p.data[0:2])