fix: CDX mmap + internal node format (BE key-first) — 50K works

CDX internal node format fix:
- Was: [child LE][recNo LE][key] (NTX-style)
- Now: [key][recNo BE][child BE] (correct CDX format)
- Fixes GoTop/Seek/Scan for large CDX files (50K+ records)

CDX mmap:
- syscall.Mmap on OpenIndex for zero-copy reads
- idx.readAt() helper: mmap slice or file fallback
- All ReadAt calls in Tag navigation replaced
- Close: munmap

CDX 50K benchmark (all counts correct):
  SEEK NAME 50K: 362ms (f=50000)
  SCAN 50K: 276ms (c=50000)
  SCOPE 35K: 238ms (c=35000)
  SEEK ID 50K: 320ms (f=50000)

CDX is slower than NTX due to bit-packed leaf decompression per page.
Cross-read test: 18/18 still PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 13:32:06 +09:00
parent a9600ad45c
commit 1b41384675
3 changed files with 153 additions and 24 deletions

View File

@@ -0,0 +1,66 @@
// CDX Read Benchmark — opens Harbour-created CDX, tests seek/scan/scope
PROCEDURE Main()
LOCAL i, nStart, nEnd, nCount
// Open Harbour-created files
USE "cdx_bench" NEW
SET INDEX TO "cdx_bench.cdx"
? "TAGS=" + LTrim(Str(OrdCount()))
// SEEK 50K by NAME
SET ORDER TO 3
nStart := Seconds()
nCount := 0
FOR i := 1 TO 50000
SEEK PadR("Name_" + PadL(LTrim(Str(i)), 5, "0"), 20)
IF Found()
nCount++
ENDIF
NEXT
nEnd := Seconds()
? "SEEK_NAME=" + LTrim(Str(Int((nEnd-nStart)*1000))) + "ms f=" + LTrim(Str(nCount))
// SCAN 50K
SET ORDER TO 3
GO TOP
nStart := Seconds()
nCount := 0
DO WHILE !EOF()
nCount++
SKIP
ENDDO
nEnd := Seconds()
? "SCAN=" + LTrim(Str(Int((nEnd-nStart)*1000))) + "ms c=" + LTrim(Str(nCount))
// ORDSCOPE on CITY
SET ORDER TO 1
OrdScope(0, PadR("London", 15))
OrdScope(1, PadR("Seoul", 15))
nStart := Seconds()
GO TOP
nCount := 0
DO WHILE !EOF()
nCount++
SKIP
ENDDO
nEnd := Seconds()
? "SCOPE=" + LTrim(Str(Int((nEnd-nStart)*1000))) + "ms c=" + LTrim(Str(nCount))
OrdScope(0, NIL)
OrdScope(1, NIL)
// SEEK by ID
SET ORDER TO 2
nStart := Seconds()
nCount := 0
FOR i := 1 TO 50000
SEEK Str(i, 8)
IF Found()
nCount++
ENDIF
NEXT
nEnd := Seconds()
? "SEEK_ID=" + LTrim(Str(Int((nEnd-nStart)*1000))) + "ms f=" + LTrim(Str(nCount))
CLOSE ALL
RETURN

28
examples/debug_cdx50k.prg Normal file
View File

@@ -0,0 +1,28 @@
PROCEDURE Main()
USE "cdx_bench" NEW
SET INDEX TO "cdx_bench.cdx"
? "Tags:", OrdCount()
? "Order1:", OrdName(1)
? "Order2:", OrdName(2)
? "Order3:", OrdName(3)
SET ORDER TO 3
? "Current:", IndexOrd()
GO TOP
? "Top:", RecNo(), EOF(), BOF()
? "Name:", RTrim(FieldGet(2))
SKIP
? "Skip:", RecNo(), EOF()
SET ORDER TO 1
GO TOP
? "City top:", RecNo(), RTrim(FieldGet(3)), EOF()
SET ORDER TO 0
GO TOP
? "Natural:", RecNo(), LTrim(Str(FieldGet(1)))
? "RC:", RecCount()
CLOSE ALL
RETURN