Просмотр исходного кода

perf: 堆页插入改为链尾分配, 消除 O(n^2) 线性扫描

heapAllocBlock 原先每次插入都从 FirstHeap 沿链线性扫描'第一个有空位的页'; 但堆链只往尾部追加, 均匀定长插入下前面每页都会被扫到且都放不下, 单次插入退化为 O(堆页数)、整体 O(n^2). 实测 3000 行 5.4s(约 560 行/秒), 到 3.2 万行退化到约 53 行/秒.

改为直接使用链尾页 LastHeap, 放不下才追加新页, 单次插入 O(1). 实测 2 万行 3000B 动态字段插入 1.37s(约 14600 行/秒), 4000 个溢出页. 代价: 旧尾页不足一个块的零头不再被后续小块回填, 每页最多浪费一个块大小以内.
yl32940 1 месяц назад
Родитель
Сommit
367d600566
1 измененных файлов с 19 добавлено и 23 удалено
  1. 19 23
      DB/core/Table.go

+ 19 - 23
DB/core/Table.go

@@ -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)