Files
five/examples/bench_rdd.prg
Charles KWON OhJun 1d3f897daf 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>
2026-04-07 08:38:19 +09:00

244 lines
6.1 KiB
Plaintext

// 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