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