bench: RDD performance benchmark — Harbour vs Five comparison
10,000 records, 3 indexes, 12 benchmarks: - APPEND: Five 72s vs Harbour 16ms (flush-per-record — optimization needed) - INDEX: Five 30-36ms vs Harbour 1-2ms (per-key insert vs bulk) - SEEK: Five 35ms vs Harbour 5ms (7x — acceptable) - SCAN: Five 8-11ms vs Harbour 1-4ms (3-9x — acceptable) - PACK: Five 4ms = Harbour 4ms (identical!) B6 correctness: Five found=10000 (all), Harbour found=1 (hash collision) All counts match: 10000 records, 8000 after SET DELETED, 8000 after PACK Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
243
examples/bench_rdd.prg
Normal file
243
examples/bench_rdd.prg
Normal file
@@ -0,0 +1,243 @@
|
||||
// RDD Performance Benchmark — Harbour vs Five
|
||||
// Run in home directory for fair comparison
|
||||
|
||||
|
||||
PROCEDURE Main()
|
||||
LOCAL i, j, nH, nStart, nEnd, nElapsed, nCount
|
||||
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
|
||||
|
||||
// ============================================
|
||||
// BENCH 1: CREATE + APPEND 10,000 records
|
||||
// ============================================
|
||||
nStart := Seconds()
|
||||
dbCreate("bench_test", aStruct)
|
||||
USE "bench_test" NEW
|
||||
FOR i := 1 TO 10000
|
||||
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)/100))+1)), 10)
|
||||
NEXT
|
||||
nEnd := Seconds()
|
||||
R("B1_APPEND_10K", FmtMs(nEnd - nStart))
|
||||
|
||||
// ============================================
|
||||
// BENCH 2: INDEX ON NAME (10,000 records)
|
||||
// ============================================
|
||||
nStart := Seconds()
|
||||
INDEX ON FIELD->NAME TO bench_name
|
||||
nEnd := Seconds()
|
||||
R("B2_INDEX_NAME", FmtMs(nEnd - nStart))
|
||||
|
||||
// ============================================
|
||||
// BENCH 3: INDEX ON CITY (duplicate keys)
|
||||
// ============================================
|
||||
CLOSE ALL
|
||||
USE "bench_test" NEW
|
||||
nStart := Seconds()
|
||||
INDEX ON FIELD->CITY TO bench_city
|
||||
nEnd := Seconds()
|
||||
R("B3_INDEX_CITY", FmtMs(nEnd - nStart))
|
||||
|
||||
// ============================================
|
||||
// BENCH 4: INDEX ON numeric key
|
||||
// ============================================
|
||||
CLOSE ALL
|
||||
USE "bench_test" NEW
|
||||
nStart := Seconds()
|
||||
INDEX ON Str(FIELD->ID, 8) TO bench_id
|
||||
nEnd := Seconds()
|
||||
R("B4_INDEX_ID", FmtMs(nEnd - nStart))
|
||||
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 5: Sequential SEEK (10,000 exact seeks)
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
SET INDEX TO bench_name
|
||||
|
||||
nStart := Seconds()
|
||||
nCount := 0
|
||||
FOR i := 1 TO 10000
|
||||
SEEK PadR("Name_" + PadL(LTrim(Str(i)), 5, "0"), 30)
|
||||
IF Found()
|
||||
nCount++
|
||||
ENDIF
|
||||
NEXT
|
||||
nEnd := Seconds()
|
||||
R("B5_SEEK_10K", FmtMs(nEnd - nStart) + " found=" + LTrim(Str(nCount)))
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 6: Random SEEK (10,000 random seeks)
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
SET INDEX TO bench_name
|
||||
|
||||
nStart := Seconds()
|
||||
nCount := 0
|
||||
FOR i := 1 TO 10000
|
||||
j := ((i * 7919) % 10000) + 1
|
||||
SEEK PadR("Name_" + PadL(LTrim(Str(j)), 5, "0"), 30)
|
||||
IF Found()
|
||||
nCount++
|
||||
ENDIF
|
||||
NEXT
|
||||
nEnd := Seconds()
|
||||
R("B6_SEEK_RND", FmtMs(nEnd - nStart) + " found=" + LTrim(Str(nCount)))
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 7: Full forward traversal (GO TOP → EOF)
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
SET INDEX TO bench_name
|
||||
|
||||
nStart := Seconds()
|
||||
GO TOP
|
||||
nCount := 0
|
||||
DO WHILE !EOF()
|
||||
nCount++
|
||||
SKIP
|
||||
ENDDO
|
||||
nEnd := Seconds()
|
||||
R("B7_SCAN_FWD", FmtMs(nEnd - nStart) + " count=" + LTrim(Str(nCount)))
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 8: Full backward traversal (GO BOTTOM → BOF)
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
SET INDEX TO bench_name
|
||||
|
||||
nStart := Seconds()
|
||||
GO BOTTOM
|
||||
nCount := 0
|
||||
DO WHILE !BOF()
|
||||
nCount++
|
||||
SKIP -1
|
||||
ENDDO
|
||||
nEnd := Seconds()
|
||||
R("B8_SCAN_BWD", FmtMs(nEnd - nStart) + " count=" + LTrim(Str(nCount)))
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 9: Duplicate key SEEK + count (CITY)
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
SET INDEX TO bench_city
|
||||
|
||||
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("B9_DUPKEY_SCAN", FmtMs(nEnd - nStart) + " count=" + LTrim(Str(nCount)))
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 10: Numeric SEEK (ID index)
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
SET INDEX TO bench_id
|
||||
|
||||
nStart := Seconds()
|
||||
nCount := 0
|
||||
FOR i := 1 TO 10000
|
||||
SEEK Str(i, 8)
|
||||
IF Found()
|
||||
nCount++
|
||||
ENDIF
|
||||
NEXT
|
||||
nEnd := Seconds()
|
||||
R("B10_SEEK_NUM", FmtMs(nEnd - nStart) + " found=" + LTrim(Str(nCount)))
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 11: DELETE + SET DELETED ON + traversal
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
SET INDEX TO bench_name
|
||||
SET ORDER TO 0
|
||||
FOR i := 1 TO 10000
|
||||
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("B11_DEL_SCAN", FmtMs(nEnd - nStart) + " count=" + LTrim(Str(nCount)))
|
||||
|
||||
SET DELETED OFF
|
||||
SET ORDER TO 0
|
||||
FOR i := 1 TO 10000
|
||||
GO i
|
||||
IF Deleted()
|
||||
RECALL
|
||||
ENDIF
|
||||
NEXT
|
||||
CLOSE ALL
|
||||
|
||||
// ============================================
|
||||
// BENCH 12: PACK 10,000 records (2,000 deleted)
|
||||
// ============================================
|
||||
USE "bench_test" NEW
|
||||
FOR i := 1 TO 10000
|
||||
GO i
|
||||
IF i % 5 == 0
|
||||
DELETE
|
||||
ENDIF
|
||||
NEXT
|
||||
|
||||
nStart := Seconds()
|
||||
PACK
|
||||
nEnd := Seconds()
|
||||
R("B12_PACK", FmtMs(nEnd - nStart) + " remain=" + LTrim(Str(RecCount())))
|
||||
CLOSE ALL
|
||||
|
||||
RECOVER
|
||||
? "ERROR"
|
||||
END SEQUENCE
|
||||
|
||||
|
||||
RETURN
|
||||
|
||||
FUNCTION FmtMs(nSec)
|
||||
LOCAL nMs
|
||||
nMs := Int(nSec * 1000)
|
||||
RETURN LTrim(Str(nMs)) + "ms"
|
||||
|
||||
PROCEDURE R(cKey, cVal)
|
||||
? cKey + "=" + cVal
|
||||
RETURN
|
||||
Reference in New Issue
Block a user