I developed a bit the ProtoStructs.jl package recently and I managed to improve the performance of the implementation, but there is something I donβt understand why is happening: microbenchmarks seems to lie with
julia> using ProtoStructs, BenchmarkTools
julia> @proto mutable struct A
const x::Int
y::Int
end
julia> mutable struct B
const x::Int
y::Int
end
julia> As = [A(x,x) for x in 1:1000];
julia> Bs = [B(x,x) for x in 1:1000];
julia> @benchmark sum(v.x for v in $As)
BenchmarkTools.Trial: 10000 samples with 401 evaluations.
Range (min β¦ max): 242.935 ns β¦ 299.566 ns β GC (min β¦ max): 0.00% β¦ 0.00%
Time (median): 243.012 ns β GC (median): 0.00%
Time (mean Β± Ο): 245.073 ns Β± 3.157 ns β GC (mean Β± Ο): 0.00% Β± 0.00%
β β β β β β β β
βββββββββββββββββ
ββ
βββββββ
βββββ
βββ
ββ
ββββ
ββ
β
β
βββββββ
βββββββ
βββ β
243 ns Histogram: log(frequency) by time 255 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
julia> @benchmark sum(v.y for v in $As)
BenchmarkTools.Trial: 10000 samples with 383 evaluations.
Range (min β¦ max): 247.055 ns β¦ 365.457 ns β GC (min β¦ max): 0.00% β¦ 0.00%
Time (median): 255.089 ns β GC (median): 0.00%
Time (mean Β± Ο): 257.748 ns Β± 5.791 ns β GC (mean Β± Ο): 0.00% Β± 0.00%
β ββββββββββββ
ββ ββββ
β
β
ββ
β
β
β
βββ ββββ β
ββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
247 ns Histogram: log(frequency) by time 274 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
julia> @benchmark sum(v.x for v in $Bs)
BenchmarkTools.Trial: 10000 samples with 212 evaluations.
Range (min β¦ max): 352.679 ns β¦ 64.380 ΞΌs β GC (min β¦ max): 0.00% β¦ 0.00%
Time (median): 366.009 ns β GC (median): 0.00%
Time (mean Β± Ο): 373.513 ns Β± 658.147 ns β GC (mean Β± Ο): 0.00% Β± 0.00%
ββ
ββββ
βββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββ β
353 ns Histogram: frequency by time 382 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
julia> @benchmark sum(v.y for v in $Bs)
BenchmarkTools.Trial: 10000 samples with 212 evaluations.
Range (min β¦ max): 350.792 ns β¦ 524.656 ns β GC (min β¦ max): 0.00% β¦ 0.00%
Time (median): 366.061 ns β GC (median): 0.00%
Time (mean Β± Ο): 364.981 ns Β± 6.284 ns β GC (mean Β± Ο): 0.00% Β± 0.00%
ββ
βββ
β
ββββββββββββββ
ββββββ
βββββββββββββββββββ
ββββββββββββββββββββββ β
351 ns Histogram: frequency by time 380 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
Here you can see that the Mutable ProtoStruct seems faster than the Base one!
But then I tried in a real program to add @proto
to the mutable struct, to my surprise all the program ended up 10x slower.
How can it be? Why this microbenchmark tells me the opposite?
Behind the scenes:
julia> @macroexpand @proto mutable struct A
const x::Int
y::Int
end
quote
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:110 =#
if !($(Expr(:isdefined, :A)))
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:111 =#
struct A{P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, NT <: NamedTuple} <: Any
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:112 =#
properties::NT
end
else
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:115 =#
if Any != Any && Any != Base.supertype(A)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:116 =#
error("The supertype of a proto struct is not redefinable. Please restart your julia session.")
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:118 =#
the_methods = collect(methods(A))
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:119 =#
Base.delete_method(the_methods[1])
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:120 =#
Base.delete_method(the_methods[2])
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:123 =#
function $(Expr(:where, :(A(x::Int, y::Int))))
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:123 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:124 =#
v = NamedTuple{(:x, :y)}((x = x, y = Ref(y)))
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:125 =#
return A{Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, typeof(v)}(v)
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:128 =#
function $(Expr(:where, :(A{}(x::Int, y::Int))))
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:128 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:129 =#
v = NamedTuple{(:x, :y)}((x = x, y = Ref(y)))
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:130 =#
return A{Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, typeof(v)}(v)
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:133 =#
function A(; x, y)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:133 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:134 =#
return A(x, y)
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:137 =#
function $(Expr(:where, :(A{}(; x, y))))
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:137 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:138 =#
A{}(x, y)
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:141 =#
function Base.getproperty(o::A, s::Symbol)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:141 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:142 =#
p = getproperty(getfield(o, :properties), s)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:143 =#
if p isa Base.RefValue
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:144 =#
p[]
else
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:146 =#
p
end
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:150 =#
function Base.setproperty!(o::A, s::Symbol, v)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:150 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:151 =#
p = getproperty(getfield(o, :properties), s)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:152 =#
if p isa Base.RefValue
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:153 =#
p[] = v
else
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:155 =#
error("const field $(s) of type ", A, " cannot be changed")
end
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:159 =#
function Base.propertynames(o::A)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:159 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:160 =#
return propertynames(getfield(o, :properties))
end
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:163 =#
function Base.show(io::IO, o::A)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:163 =#
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:164 =#
vals = join([if x[] isa String
"\"$(x[])\""
else
x[]
end for x = getfield(o, :properties)], ", ")
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:165 =#
params = (typeof(o)).parameters[1:(end - 15) - 1]
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:166 =#
if isempty(params)
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:167 =#
print(io, string(A), "($(vals))")
else
#= /home/bob/.julia/packages/ProtoStructs/uaUaY/src/ProtoStruct.jl:169 =#
print(io, string(A, "{", join(params, ", "), "}"), "($(vals))")
end
end
end