Ok, I just couldn’t leave this one alone. It is possible to read the the “shared” fields with no overhead, but it’s super unsafe. I used unsafe_load
along with a modified version of pointer_from_objref
that doesn’t check if classes are mutable or not (that check is really slow). I also made all the structs mutable, because they obviously need to be heap allocated for my use case (still learning! ).
The performance is much better:
julia> @time speedTest()
10003000000
0.082848 seconds (10.03 k allocations: 413.281 KiB)
That’s ~30x faster than the fastest method dispatch. Obviously this is totally a microbenchmark and does not represent real-world usage. Still, it does show the overhead was there, and it’s gone now. The higher count of allocations is from heap allocating the structs.
I benchmarked a C++ translation of this code, at it runs at the same speed as Julia. I’m honestly very impressed with Julia here – what I’m doing is totally against the design of the language, but Julia gives me the tools to do it anyway and compiles it down into a super-optimized result. And then I can write a macro to wrap it all up into something that looks nice. Awesome.
The code is below. This is just a quick proof-of-concept and very unsafe. getLength()
is pulling from pointer offset 1 in the struct, you could get the offsets of the other fields with fieldoffset()
. Also, for struct references (pointers) you’d need to convert them back to objrefs.
abstract type Car end
mutable struct HondaAccord <: Car
length::Int64
end
mutable struct ToyotaCamry1 <: Car
length::Int64
end
mutable struct ToyotaCamry2 <: Car
length::Int64
end
function unsafe_pointer_from_objref(@nospecialize(x))
ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), x)
end
function getLength(a::Car)::Int64
unsafe_load(Core.Intrinsics.bitcast(Ptr{Int64}, unsafe_pointer_from_objref(a)), 1)
end
function speedTest()
cars = Car[]
while length(cars) < 10000
push!(cars, HondaAccord(100))
push!(cars, ToyotaCamry1(100))
push!(cars, ToyotaCamry2(100))
end
sum = 0::Int64
for j = 1:10000
for i = 1:length(cars)
sum = sum + getLength(cars[i])
end
end
println(sum)
end