|
|
@@ -855,37 +855,33 @@ func (table *Table) heapAllocBlock(need uint32) (*HeapPage, uint16, error) {
|
|
|
}
|
|
|
total := need + dynamicBlockHdr
|
|
|
|
|
|
- // 1. 沿链查找第一个剩余空间足够的堆页
|
|
|
- pid := table.info.FirstHeap
|
|
|
+ // 1. 尾页是当前唯一可写页: 堆链只往尾部追加, 新块一律落在链尾。
|
|
|
+ //
|
|
|
+ // 为什么不再从 FirstHeap 逐个扫"第一个有空间的页": 均匀定长插入下, 前面每一
|
|
|
+ // 页都会因剩余空间不足而被扫到且都放不下, 单次插入退化成 O(堆页数), 整体
|
|
|
+ // O(n²) —— 实测插 10 万行要十几分钟且内存顶不住。尾插把单次插入降到 O(1)。
|
|
|
+ //
|
|
|
+ // 代价: 旧尾页里不足一个块的零头不再被后续小块回填, 每页最多浪费「放不下的
|
|
|
+ // 那个块大小」以内。若尾页恰好是 heapUnref 清空复用的空页, 则它必然放得下
|
|
|
+ // 任何块(空页可用空间 >= 单块上限), 不会因此多开一张页。
|
|
|
var tail *HeapPage
|
|
|
- for pid != 0 {
|
|
|
- hp, err := LoadHeapPage(table.handle, pid)
|
|
|
+ if table.info.LastHeap != 0 {
|
|
|
+ hp, err := LoadHeapPage(table.handle, table.info.LastHeap)
|
|
|
if err != nil {
|
|
|
return nil, 0, err
|
|
|
}
|
|
|
- tail = hp
|
|
|
if hp.Info.Hdr.RefCount < maxHeapRefCount && PageSize-hp.Info.Hdr.UseSize >= total {
|
|
|
- _, off, err := hp.Alloc(total)
|
|
|
- if err != nil {
|
|
|
+ tail = hp
|
|
|
+ } else {
|
|
|
+ // 尾页放不下 → 在它后面接一张新页作为新尾
|
|
|
+ nhp := NewHeapPage(table.handle, page.HeapIndex)
|
|
|
+ hp.Info.Hdr.NextPage = nhp.Info.Hdr.PageID
|
|
|
+ nhp.Info.Hdr.PrePage = hp.Info.Hdr.PageID
|
|
|
+ if err := hp.Save(); err != nil {
|
|
|
return nil, 0, err
|
|
|
}
|
|
|
- if off > 0xFFFF {
|
|
|
- return nil, 0, ErrHeapPageTooLarge
|
|
|
- }
|
|
|
- return hp, uint16(off), nil
|
|
|
- }
|
|
|
- pid = hp.Info.Hdr.NextPage
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 链上无可用空间 → 新建堆页
|
|
|
- if tail != nil {
|
|
|
- nhp := NewHeapPage(table.handle, page.HeapIndex)
|
|
|
- tail.Info.Hdr.NextPage = nhp.Info.Hdr.PageID
|
|
|
- nhp.Info.Hdr.PrePage = tail.Info.Hdr.PageID
|
|
|
- if err := tail.Save(); err != nil {
|
|
|
- return nil, 0, err
|
|
|
+ tail = nhp
|
|
|
}
|
|
|
- tail = nhp
|
|
|
} else {
|
|
|
// 链为空 (旧库或已被清空): 新建首页
|
|
|
tail = NewHeapPage(table.handle, page.HeapIndex)
|