139 lines
3.9 KiB
Julia
139 lines
3.9 KiB
Julia
import Pkg
|
||
|
||
const PROJECT_DIR = @__DIR__
|
||
Pkg.activate(PROJECT_DIR; io=devnull)
|
||
|
||
using JSON
|
||
using Printf
|
||
using Statistics
|
||
using Base.Threads
|
||
|
||
include(joinpath(PROJECT_DIR, "src", "SafeHeuristic.jl"))
|
||
using .SafeHeuristic
|
||
|
||
const DEFAULT_SNAPSHOT_PATH = joinpath(
|
||
dirname(dirname(PROJECT_DIR)),
|
||
"runs",
|
||
"tmp",
|
||
"safe_heuristic_snapshots_3seeds.jsonl",
|
||
)
|
||
const RUNS_PER_CASE = 5
|
||
|
||
function load_records(path::AbstractString)
|
||
return [JSON.parse(line) for line in eachline(path) if !isempty(strip(line))]
|
||
end
|
||
|
||
function chunk_ranges(length::Int, chunks::Int)::Vector{UnitRange{Int}}
|
||
ranges = Vector{UnitRange{Int}}(undef, chunks)
|
||
base = length ÷ chunks
|
||
extra = length % chunks
|
||
start = 1
|
||
for chunk in 1:chunks
|
||
width = base + (chunk <= extra ? 1 : 0)
|
||
stop = start + width - 1
|
||
ranges[chunk] = start:stop
|
||
start = stop + 1
|
||
end
|
||
return ranges
|
||
end
|
||
|
||
function run_sequential(records)::Vector{Int}
|
||
actions = Vector{Int}(undef, length(records))
|
||
@inbounds for idx in eachindex(records)
|
||
actions[idx] = safe_heuristic_action(records[idx])
|
||
end
|
||
return actions
|
||
end
|
||
|
||
function run_threaded(records, chunks::Int)::Vector{Int}
|
||
actions = Vector{Int}(undef, length(records))
|
||
ranges = chunk_ranges(length(records), chunks)
|
||
@threads for chunk in eachindex(ranges)
|
||
@inbounds for idx in ranges[chunk]
|
||
actions[idx] = safe_heuristic_action(records[idx])
|
||
end
|
||
end
|
||
return actions
|
||
end
|
||
|
||
function run_case(records, chunks::Int)::Vector{Int}
|
||
if chunks == 1
|
||
return run_sequential(records)
|
||
end
|
||
return run_threaded(records, chunks)
|
||
end
|
||
|
||
function measure_case(records, chunks::Int, baseline::Vector{Int})::Float64
|
||
warmup_actions = run_case(records, chunks)
|
||
if warmup_actions != baseline
|
||
error("action parity failed during warmup for $(chunks) thread case")
|
||
end
|
||
|
||
times = Vector{Float64}(undef, RUNS_PER_CASE)
|
||
for run in 1:RUNS_PER_CASE
|
||
actions = Vector{Int}()
|
||
elapsed = @elapsed begin
|
||
actions = run_case(records, chunks)
|
||
end
|
||
if actions != baseline
|
||
error("action parity failed on run $(run) for $(chunks) thread case")
|
||
end
|
||
times[run] = elapsed * 1000.0
|
||
end
|
||
return median(times)
|
||
end
|
||
|
||
function scaling_label(efficiency::Float64)::String
|
||
if efficiency >= 0.80
|
||
return "near-linear"
|
||
elseif efficiency >= 0.50
|
||
return "partial scale"
|
||
end
|
||
return "contention"
|
||
end
|
||
|
||
function main()
|
||
if length(ARGS) > 1
|
||
println(stderr, "usage: julia --threads=8 experiments/julia_safe_heuristic/bench_threaded.jl [snapshots.jsonl]")
|
||
exit(2)
|
||
end
|
||
|
||
path = length(ARGS) == 1 ? ARGS[1] : DEFAULT_SNAPSHOT_PATH
|
||
if !isfile(path)
|
||
println(stderr, "snapshot file not found: $path")
|
||
exit(2)
|
||
end
|
||
|
||
records = load_records(path)
|
||
available = Threads.nthreads()
|
||
grid = [threads for threads in (1, 2, 4, 8) if threads <= available && threads <= length(records)]
|
||
if isempty(grid)
|
||
println(stderr, "no thread counts are available")
|
||
exit(2)
|
||
end
|
||
|
||
baseline = run_sequential(records)
|
||
results = Dict{Int,Float64}()
|
||
for threads in grid
|
||
results[threads] = measure_case(records, threads, baseline)
|
||
end
|
||
|
||
one_thread_ms = results[1]
|
||
println("threads total_ms μs/call speedup vs 1T efficiency")
|
||
for threads in grid
|
||
total_ms = results[threads]
|
||
us_per_call = total_ms * 1000.0 / length(records)
|
||
speedup = one_thread_ms / total_ms
|
||
efficiency = speedup / threads
|
||
@printf("%-7d %8.1f %8.1f %13.2f× %10.0f%%\n", threads, total_ms, us_per_call, speedup, efficiency * 100.0)
|
||
end
|
||
|
||
if 8 in grid
|
||
speedup = one_thread_ms / results[8]
|
||
efficiency = speedup / 8
|
||
@printf("8T efficiency %.0f%% — %s\n", efficiency * 100.0, scaling_label(efficiency))
|
||
end
|
||
end
|
||
|
||
main()
|