Skip to content

Commit b7960c9

Browse files
authored
Merge pull request #23 from richiejp/read-perf
Improve read performance & benchmark
2 parents 7ed00f3 + b02ac83 commit b7960c9

File tree

4 files changed

+107
-21
lines changed

4 files changed

+107
-21
lines changed

src/BSON.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ const Primitive = Union{Nothing,Bool,Int32,Int64,Float64,String,Vector{UInt8},BS
1414
datetime, null, regex, dbpointer, javascript, symbol, javascript_scoped,
1515
int32, timestamp, int64, decimal128, minkey=0xFF, maxkey=0x7F)
1616

17-
applychildren!(f, x) = x
17+
applychildren!(::Function, x) = x
1818

19-
function applychildren!(f, x::BSONDict)
19+
function applychildren!(f::Function, x::BSONDict)::BSONDict
2020
for k in keys(x)
2121
x[k] = f(x[k])
2222
end
2323
return x
2424
end
2525

26-
function applychildren!(f, x::BSONArray)
26+
function applychildren!(f::Function, x::BSONArray)::BSONArray
2727
for i = 1:length(x)
2828
x[i] = f(x[i])
2929
end

src/extensions.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,16 @@ function newstruct(T, xs...)
9999
flds = Any[convert(fieldtype(T, i), x) for (i,x) in enumerate(xs)]
100100
return ccall(:jl_new_structv, Any, (Any,Ptr{Cvoid},UInt32), T, flds, length(flds))
101101
else
102-
newstruct!(initstruct(T), xs...)
102+
# Manual inline of newstruct! to work around bug
103+
# https://github.com/MikeInnes/BSON.jl/issues/2#issuecomment-452204339
104+
x = initstruct(T)
105+
106+
for (i, f) = enumerate(xs)
107+
f = convert(fieldtype(typeof(x),i), f)
108+
ccall(:jl_set_nth_field, Nothing, (Any, Csize_t, Any), x, i-1, f)
109+
end
110+
x
111+
103112
end
104113
end
105114

src/read.jl

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
jtype(tag) =
1+
jtype(tag::BSONType)::DataType =
22
tag == null ? Nothing :
33
tag == boolean ? Bool :
44
tag == int32 ? Int32 :
55
tag == int64 ? Int64 :
66
tag == double ? Float64 :
77
error("Unsupported tag $tag")
88

9-
function parse_cstr(io::IO)
10-
buf = IOBuffer()
11-
while (ch = read(io, UInt8)) != 0x00
12-
write(buf, ch)
13-
end
14-
return String(read(seek(buf, 0)))
15-
end
9+
parse_cstr(io::IO) = readuntil(io, '\0')
1610

17-
function parse_tag(io::IO, tag)
11+
function parse_tag(io::IO, tag::BSONType)
1812
if tag == null
1913
nothing
2014
elseif tag == document
2115
parse_doc(io)
2216
elseif tag == array
23-
Any[map(x->x[2], parse_pairs(io))...]
17+
parse_array(io)
2418
elseif tag == string
2519
len = read(io, Int32)-1
2620
s = String(read(io, len))
@@ -35,18 +29,32 @@ function parse_tag(io::IO, tag)
3529
end
3630
end
3731

38-
function parse_pairs(io::IO)
32+
function parse_array(io::IO)::BSONArray
3933
len = read(io, Int32)
40-
ps = []
34+
ps = BSONArray()
35+
4136
while (tag = read(io, BSONType)) eof
42-
k = Symbol(parse_cstr(io))
43-
v = parse_tag(io::IO, tag)
44-
push!(ps, k => v)
37+
# Note that arrays are dicts with the index as the key
38+
while read(io, UInt8) != 0x00
39+
nothing
40+
end
41+
push!(ps, parse_tag(io::IO, tag))
4542
end
46-
return ps
43+
44+
ps
4745
end
4846

49-
parse_doc(io::IO) = BSONDict(parse_pairs(io))
47+
function parse_doc(io::IO)::BSONDict
48+
len = read(io, Int32)
49+
dic = BSONDict()
50+
51+
while (tag = read(io, BSONType)) eof
52+
k = Symbol(parse_cstr(io))
53+
dic[k] = parse_tag(io::IO, tag)
54+
end
55+
56+
dic
57+
end
5058

5159
backrefs!(x, refs) = applychildren!(x -> backrefs!(x, refs), x)
5260

test/benchmark.jl

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module Benchmark
2+
3+
using Profile
4+
using BSON
5+
6+
chars = [x for x in '0':'z']
7+
strings = [String([rand(chars) for _ in 1:20]) for _ in 1:20]
8+
rstr(n::Int)::String = rand(strings)[1:n]
9+
10+
struct Baz
11+
going::String
12+
deeper::String
13+
end
14+
15+
Baz() = Baz(rstr(20), rstr(1))
16+
17+
struct Bar
18+
level::Int64
19+
bazes::Vector{Baz}
20+
end
21+
22+
Bar() = Bar(rand(Int64), [Baz() for _ in 1:50])
23+
24+
struct Foo
25+
agile::String
26+
software::String
27+
management::String
28+
consultant::String
29+
training::String
30+
projects::Vector{Bar}
31+
end
32+
33+
Foo() = Foo(rstr(5), rstr(7), rstr(17), rstr(11), rstr(13), [Bar() for _ in 1:2000])
34+
35+
foos = Dict(:Foo => Foo())
36+
io = IOBuffer()
37+
38+
@info "Bench Save BSON"
39+
@timev bson(io, foos)
40+
seek(io, 0)
41+
42+
@info "Bench Parse BSON"
43+
dict = @timev BSON.parse(io)
44+
45+
@info "Bench Raise BSON to Julia types"
46+
@timev BSON.raise_recursive(dict)
47+
48+
@info "Profile Save BSON"
49+
@profile bson(io, foos)
50+
Profile.print(;noisefloor=2)
51+
Profile.clear()
52+
seek(io, 0)
53+
54+
@info "Profile Parse BSON"
55+
dict = @profile BSON.parse(io)
56+
Profile.print(;noisefloor=2)
57+
Profile.clear()
58+
59+
@info "Profile Raise BSON to Julia types"
60+
rfoos = @profile BSON.raise_recursive(dict)
61+
Profile.print(;noisefloor=2)
62+
Profile.clear()
63+
64+
# Sanity check the results
65+
rfoos[:Foo]::Foo
66+
rfoos[:Foo].projects[1]::Bar
67+
rfoos[:Foo].projects[1].bazes[1]::Baz
68+
69+
end

0 commit comments

Comments
 (0)