perf: 50K benchmark — Harbour vs Five on ext4
50K records benchmark on native ext4 (home directory): - APPEND 50K: Five 140ms / Harbour 61ms (2.3x) - INDEX 50K: Five 31ms / Harbour 6ms (5.2x) - SEEK 50K: Five 142ms / Harbour 23ms (6.2x) - SCAN 50K: Five 35ms / Harbour 5ms (7x) - PACK 50K: Five 19ms / Harbour 16ms (1.2x) All within acceptable Go vs C overhead (2-7x). PACK nearly identical. APPEND close (2.3x). Known issue: 3-level NTX bulk build has separator duplication at interior→root level (count=50083 vs 50000). Does not affect correctness for <= 2-level trees (100 records OK). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
174
examples/bench_heavy.prg
Normal file
174
examples/bench_heavy.prg
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
// Heavy RDD benchmark — 50K records, multiple indexes, mixed operations
|
||||||
|
|
||||||
|
|
||||||
|
PROCEDURE Main()
|
||||||
|
LOCAL i, j, nH, nStart, nEnd, nCount, nSum
|
||||||
|
LOCAL aStruct, aCities, nIdx
|
||||||
|
|
||||||
|
ErrorBlock({|e| Break(e)})
|
||||||
|
aCities := {"Seoul","Tokyo","Beijing","London","NYC","Paris","Berlin","Rome","Madrid","Oslo"}
|
||||||
|
|
||||||
|
aStruct := { ;
|
||||||
|
{"ID","N",8,0}, {"NAME","C",30,0}, {"CITY","C",15,0}, ;
|
||||||
|
{"SALARY","N",12,2}, {"ACTIVE","L",1,0}, {"CODE","C",10,0} ;
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN SEQUENCE
|
||||||
|
|
||||||
|
// ======== APPEND 50K ========
|
||||||
|
nStart := Seconds()
|
||||||
|
dbCreate("heavy_test", aStruct)
|
||||||
|
USE "heavy_test" NEW
|
||||||
|
FOR i := 1 TO 50000
|
||||||
|
APPEND BLANK
|
||||||
|
REPLACE ID WITH i
|
||||||
|
REPLACE NAME WITH PadR("Name_" + PadL(LTrim(Str(i)), 5, "0"), 30)
|
||||||
|
nIdx := ((i-1) % 10) + 1
|
||||||
|
REPLACE CITY WITH PadR(aCities[nIdx], 15)
|
||||||
|
REPLACE SALARY WITH 20000 + i * 1.50
|
||||||
|
REPLACE ACTIVE WITH (i % 3 != 0)
|
||||||
|
REPLACE CODE WITH PadR(LTrim(Str(Int(((i-1)/500))+1)), 10)
|
||||||
|
NEXT
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H1_APPEND_50K", FmtMs(nEnd - nStart) + " rc=" + LTrim(Str(RecCount())))
|
||||||
|
|
||||||
|
// ======== INDEX 50K ========
|
||||||
|
nStart := Seconds()
|
||||||
|
INDEX ON FIELD->NAME TO heavy_name
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H2_INDEX_50K", FmtMs(nEnd - nStart))
|
||||||
|
CLOSE ALL
|
||||||
|
|
||||||
|
// ======== SEEK 50K sequential ========
|
||||||
|
USE "heavy_test" NEW
|
||||||
|
SET INDEX TO heavy_name
|
||||||
|
nStart := Seconds()
|
||||||
|
nCount := 0
|
||||||
|
FOR i := 1 TO 50000
|
||||||
|
SEEK PadR("Name_" + PadL(LTrim(Str(i)), 5, "0"), 30)
|
||||||
|
IF Found()
|
||||||
|
nCount++
|
||||||
|
ENDIF
|
||||||
|
NEXT
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H3_SEEK_50K", FmtMs(nEnd - nStart) + " f=" + LTrim(Str(nCount)))
|
||||||
|
|
||||||
|
// ======== SEEK 50K random ========
|
||||||
|
nStart := Seconds()
|
||||||
|
nCount := 0
|
||||||
|
FOR i := 1 TO 50000
|
||||||
|
j := ((i * 7919) % 50000) + 1
|
||||||
|
SEEK PadR("Name_" + PadL(LTrim(Str(j)), 5, "0"), 30)
|
||||||
|
IF Found()
|
||||||
|
nCount++
|
||||||
|
ENDIF
|
||||||
|
NEXT
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H4_SEEK_RND", FmtMs(nEnd - nStart) + " f=" + LTrim(Str(nCount)))
|
||||||
|
CLOSE ALL
|
||||||
|
|
||||||
|
// ======== FULL SCAN 50K ========
|
||||||
|
USE "heavy_test" NEW
|
||||||
|
SET INDEX TO heavy_name
|
||||||
|
nStart := Seconds()
|
||||||
|
GO TOP
|
||||||
|
nCount := 0
|
||||||
|
DO WHILE !EOF()
|
||||||
|
nCount++
|
||||||
|
SKIP
|
||||||
|
ENDDO
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H5_SCAN_50K", FmtMs(nEnd - nStart) + " c=" + LTrim(Str(nCount)))
|
||||||
|
CLOSE ALL
|
||||||
|
|
||||||
|
// ======== CITY INDEX 50K ========
|
||||||
|
USE "heavy_test" NEW
|
||||||
|
nStart := Seconds()
|
||||||
|
INDEX ON FIELD->CITY TO heavy_city
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H6_IDX_CITY", FmtMs(nEnd - nStart))
|
||||||
|
|
||||||
|
// ======== DUP KEY SEEK+SCAN ========
|
||||||
|
nStart := Seconds()
|
||||||
|
nCount := 0
|
||||||
|
FOR i := 1 TO 10
|
||||||
|
SEEK PadR(aCities[i], 15)
|
||||||
|
DO WHILE !EOF() .AND. RTrim(FieldGet(3)) == aCities[i]
|
||||||
|
nCount++
|
||||||
|
SKIP
|
||||||
|
ENDDO
|
||||||
|
NEXT
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H7_DUPKEY_50K", FmtMs(nEnd - nStart) + " c=" + LTrim(Str(nCount)))
|
||||||
|
CLOSE ALL
|
||||||
|
|
||||||
|
// ======== DELETE 10K + SET DELETED SCAN ========
|
||||||
|
USE "heavy_test" NEW
|
||||||
|
SET INDEX TO heavy_name
|
||||||
|
SET ORDER TO 0
|
||||||
|
FOR i := 1 TO 50000
|
||||||
|
GO i
|
||||||
|
IF i % 5 == 0
|
||||||
|
DELETE
|
||||||
|
ENDIF
|
||||||
|
NEXT
|
||||||
|
|
||||||
|
SET ORDER TO 1
|
||||||
|
SET DELETED ON
|
||||||
|
nStart := Seconds()
|
||||||
|
GO TOP
|
||||||
|
nCount := 0
|
||||||
|
DO WHILE !EOF()
|
||||||
|
nCount++
|
||||||
|
SKIP
|
||||||
|
ENDDO
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H8_DELSCAN_50K", FmtMs(nEnd - nStart) + " c=" + LTrim(Str(nCount)))
|
||||||
|
|
||||||
|
SET DELETED OFF
|
||||||
|
SET ORDER TO 0
|
||||||
|
FOR i := 1 TO 50000
|
||||||
|
GO i
|
||||||
|
IF Deleted()
|
||||||
|
RECALL
|
||||||
|
ENDIF
|
||||||
|
NEXT
|
||||||
|
CLOSE ALL
|
||||||
|
|
||||||
|
// ======== COMPOUND INDEX ========
|
||||||
|
USE "heavy_test" NEW
|
||||||
|
nStart := Seconds()
|
||||||
|
INDEX ON FIELD->CITY + FIELD->NAME TO heavy_comp
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H9_IDX_COMP", FmtMs(nEnd - nStart))
|
||||||
|
|
||||||
|
SEEK PadR("Seoul", 15) + PadR("Name_00001", 30)
|
||||||
|
R("H10_COMP_SEEK", IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())))
|
||||||
|
CLOSE ALL
|
||||||
|
|
||||||
|
// ======== PACK 50K (10K deleted) ========
|
||||||
|
USE "heavy_test" NEW
|
||||||
|
FOR i := 1 TO 50000
|
||||||
|
GO i
|
||||||
|
IF i % 5 == 0
|
||||||
|
DELETE
|
||||||
|
ENDIF
|
||||||
|
NEXT
|
||||||
|
nStart := Seconds()
|
||||||
|
PACK
|
||||||
|
nEnd := Seconds()
|
||||||
|
R("H11_PACK_50K", FmtMs(nEnd - nStart) + " rc=" + LTrim(Str(RecCount())))
|
||||||
|
CLOSE ALL
|
||||||
|
|
||||||
|
RECOVER
|
||||||
|
? "ERROR"
|
||||||
|
END SEQUENCE
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION FmtMs(nSec)
|
||||||
|
RETURN LTrim(Str(Int(nSec * 1000))) + "ms"
|
||||||
|
|
||||||
|
PROCEDURE R(cKey, cVal)
|
||||||
|
? cKey + "=" + cVal
|
||||||
|
RETURN
|
||||||
Reference in New Issue
Block a user