Skip to content

Commit f820b8b

Browse files
ararslanjrevels
authored andcommitted
RFC: Migrate serialization format to JSON (#79)
* Migrate serialization format to JSON * I derped on the error type
1 parent 8e46ac5 commit f820b8b

File tree

5 files changed

+153
-60
lines changed

5 files changed

+153
-60
lines changed

REQUIRE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
julia 0.6
22
Compat 0.26
3-
JLD 0.6.6
3+
JSON

src/BenchmarkTools.jl

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,8 @@ __precompile__()
33
module BenchmarkTools
44

55
using Compat
6-
import JLD
7-
8-
# `show` compatibility for pre-JuliaLang/julia#16354 builds
9-
if VERSION < v"0.5.0-dev+4305"
10-
Base.get(io::IO, setting::Symbol, default::Bool) = default
11-
end
12-
13-
if VERSION >= v"0.6.0-dev.1015"
14-
using Base.Iterators
15-
end
6+
using JSON
7+
using Base.Iterators
168

179
const BENCHMARKTOOLS_VERSION = v"0.0.6"
1810

src/serialization.jl

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,87 @@
1-
const VERSION_KEY = "__versions__"
1+
const VERSIONS = Dict("Julia" => string(VERSION),
2+
"BenchmarkTools" => string(BENCHMARKTOOLS_VERSION))
23

3-
const VERSIONS = Dict("Julia" => string(VERSION), "BenchmarkTools" => string(BENCHMARKTOOLS_VERSION))
4+
# TODO: Add any new types as they're added
5+
const SUPPORTED_TYPES = [Benchmark, BenchmarkGroup, Parameters, TagFilter, Trial,
6+
TrialEstimate, TrialJudgement, TrialRatio]
47

5-
mutable struct ParametersPreV006
6-
seconds::Float64
7-
samples::Int
8-
evals::Int
9-
overhead::Int
10-
gctrial::Bool
11-
gcsample::Bool
12-
time_tolerance::Float64
13-
memory_tolerance::Float64
14-
end
15-
16-
mutable struct TrialPreV006
17-
params::Parameters
18-
times::Vector{Int}
19-
gctimes::Vector{Int}
20-
memory::Int
21-
allocs::Int
8+
for T in SUPPORTED_TYPES
9+
@eval function JSON.lower(x::$T)
10+
d = Dict{String,Any}()
11+
for i = 1:nfields(x)
12+
name = String(fieldname($T, i))
13+
field = getfield(x, i)
14+
value = typeof(field) in SUPPORTED_TYPES ? JSON.lower(field) : field
15+
push!(d, name => value)
16+
end
17+
[string(typeof(x)), d]
18+
end
2219
end
2320

24-
function JLD.readas(p::ParametersPreV006)
25-
return Parameters(p.seconds, p.samples, p.evals, Float64(p.overhead), p.gctrial,
26-
p.gcsample, p.time_tolerance, p.memory_tolerance)
21+
function recover(x::Vector)
22+
length(x) == 2 || throw(ArgumentError("Expecting a vector of length 2"))
23+
typename = x[1]::String
24+
fields = x[2]::Dict
25+
T = eval(parse(typename))::Type
26+
fc = fieldcount(T)
27+
xs = Vector{Any}(fc)
28+
for i = 1:fc
29+
ft = fieldtype(T, i)
30+
fn = String(fieldname(T, i))
31+
xs[i] = if ft in SUPPORTED_TYPES
32+
recover(fields[fn])
33+
else
34+
convert(ft, fields[fn])
35+
end
36+
end
37+
T(xs...)
2738
end
2839

29-
function JLD.readas(t::TrialPreV006)
30-
new_times = convert(Vector{Float64}, t.times)
31-
new_gctimes = convert(Vector{Float64}, t.gctimes)
32-
return Trial(t.params, new_times, new_gctimes, t.memory, t.allocs)
40+
function badext(filename)
41+
noext, ext = splitext(filename)
42+
msg = if ext == ".jld"
43+
"JLD serialization is no longer supported. Benchmarks should now be saved in\n" *
44+
"JSON format using `save(\"$noext\".json, args...)` and loaded from JSON using\n" *
45+
"using `load(\"$noext\".json, args...)`. You will need to convert existing\n" *
46+
"saved benchmarks to JSON in order to use them with this version of BenchmarkTools."
47+
else
48+
"Only JSON serialization is supported."
49+
end
50+
throw(ArgumentError(msg))
3351
end
3452

35-
function save(filename, args...)
36-
JLD.save(filename, VERSION_KEY, VERSIONS, args...)
37-
JLD.jldopen(filename, "r+") do io
38-
JLD.addrequire(io, BenchmarkTools)
53+
function save(filename::AbstractString, args...)
54+
endswith(filename, ".json") || badext(filename)
55+
isempty(args) && throw(ArgumentError("Nothing to save"))
56+
goodargs = Any[]
57+
for arg in args
58+
if arg isa String
59+
warn("Naming variables in serialization is no longer supported.\nThe name " *
60+
"will be ignored and the object will be serialized in the order it appears " *
61+
"in the input.")
62+
continue
63+
elseif !any(T->arg isa T, SUPPORTED_TYPES)
64+
throw(ArgumentError("Only BenchmarkTools types can be serialized."))
65+
end
66+
push!(goodargs, arg)
67+
end
68+
isempty(goodargs) && error("Nothing to save")
69+
open(filename, "w") do io
70+
JSON.print(io, [VERSIONS, goodargs])
3971
end
40-
return nothing
4172
end
4273

43-
@inline function load(filename, args...)
44-
# no version-based rules are needed for now, we just need
45-
# to check that version information exists in the file.
46-
if JLD.jldopen(file -> JLD.exists(file, VERSION_KEY), filename, "r")
47-
result = JLD.load(filename, args...)
48-
else
49-
JLD.translate("BenchmarkTools.Parameters", "BenchmarkTools.ParametersPreV006")
50-
JLD.translate("BenchmarkTools.Trial", "BenchmarkTools.TrialPreV006")
51-
result = JLD.load(filename, args...)
52-
JLD.translate("BenchmarkTools.Parameters", "BenchmarkTools.Parameters")
53-
JLD.translate("BenchmarkTools.Trial", "BenchmarkTools.Trial")
74+
function load(filename::AbstractString, args...)
75+
endswith(filename, ".json") || badext(filename)
76+
if !isempty(args)
77+
throw(ArgumentError("Looking up deserialized values by name is no longer supported, " *
78+
"as names are no longer saved."))
79+
end
80+
parsed = open(JSON.parse, filename, "r")
81+
if !isa(parsed, Vector) || length(parsed) != 2 || !isa(parsed[1], Dict) || !isa(parsed[2], Vector)
82+
error("Unexpected JSON format. Was this file originally written by BenchmarkTools?")
5483
end
55-
return result
84+
versions = parsed[1]::Dict
85+
values = parsed[2]::Vector
86+
map!(recover, values, values)
5687
end

test/SerializationTests.jl

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,84 @@
11
module SerializationTests
22

3-
using Base.Test
3+
using Compat
4+
using Compat.Test
45
using BenchmarkTools
56

6-
old_data = BenchmarkTools.load(joinpath(dirname(@__FILE__), "data_pre_v006.jld"), "results")
7-
BenchmarkTools.save(joinpath(dirname(@__FILE__), "tmp.jld"), "results", old_data)
8-
new_data = BenchmarkTools.load(joinpath(dirname(@__FILE__), "tmp.jld"), "results")
7+
eq(x::T, y::T) where {T<:Union{BenchmarkTools.SUPPORTED_TYPES...}} =
8+
all(i->eq(getfield(x, i), getfield(y, i)), 1:fieldcount(T))
9+
eq(x::T, y::T) where {T} = isapprox(x, y)
910

10-
@test old_data == new_data
11+
function withtempdir(f::Function)
12+
d = mktempdir()
13+
try
14+
cd(f, d)
15+
finally
16+
rm(d, force=true, recursive=true)
17+
end
18+
nothing
19+
end
1120

12-
rm(joinpath(dirname(@__FILE__), "tmp.jld"))
21+
@testset "Successful (de)serialization" begin
22+
b = @benchmarkable sin(1)
23+
tune!(b)
24+
bb = run(b)
25+
26+
withtempdir() do
27+
tmp = joinpath(pwd(), "tmp.json")
28+
29+
BenchmarkTools.save(tmp, b, bb)
30+
@test isfile(tmp)
31+
32+
results = BenchmarkTools.load(tmp)
33+
@test results isa Vector{Any}
34+
@test length(results) == 2
35+
@test eq(results[1], b)
36+
@test eq(results[2], bb)
37+
end
38+
end
39+
40+
@testset "Deprecated behaviors" begin
41+
b = @benchmarkable sin(1)
42+
tune!(b)
43+
bb = run(b)
44+
45+
@test_throws ArgumentError BenchmarkTools.save("x.jld", b)
46+
@test_throws ArgumentError BenchmarkTools.save("x.txt", b)
47+
@test_throws ArgumentError BenchmarkTools.save("x.json")
48+
@test_throws ArgumentError BenchmarkTools.save("x.json", 1)
49+
50+
withtempdir() do
51+
tmp = joinpath(pwd(), "tmp.json")
52+
@test_warn "Naming variables" BenchmarkTools.save(tmp, "b", b)
53+
@test isfile(tmp)
54+
results = BenchmarkTools.load(tmp)
55+
@test length(results) == 1
56+
@test eq(results[1], b)
57+
end
58+
59+
@test_throws ArgumentError BenchmarkTools.load("x.jld")
60+
@test_throws ArgumentError BenchmarkTools.load("x.txt")
61+
@test_throws ArgumentError BenchmarkTools.load("x.json", "b")
62+
end
63+
64+
@testset "Error checking" begin
65+
withtempdir() do
66+
tmp = joinpath(pwd(), "tmp.json")
67+
open(tmp, "w") do f
68+
print(f, """
69+
{"never":1,"gonna":[{"give":3,"you":4,"up":5}]}
70+
""")
71+
end
72+
try
73+
BenchmarkTools.load(tmp)
74+
error("madness")
75+
catch err
76+
# This function thows a bunch of errors, so test for this specifically
77+
@test contains(err.msg, "Unexpected JSON format")
78+
end
79+
end
80+
81+
@test_throws ArgumentError BenchmarkTools.recover([1])
82+
end
1383

1484
end # module

test/data_pre_v006.jld

-1.1 MB
Binary file not shown.

0 commit comments

Comments
 (0)