I’m currently working with a lot of File IO, and I noticed that whenever i call the function write(s::IO, x::Union{Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128,Float16,Float32,Float64})
, there is an Allocation.
julia> using BenchmarkTools
julia> const io = open(tempname(; cleanup=true), "w")
IOStream(<file /tmp/jl_vp1BTP>)
julia> @btime write(io, 100)
59.570 ns (1 allocation: 16 bytes)
8
After testing around with it and where this allocation happens, I’ve concluded that this is due to the function
@noinline unsafe_write(s::IO, p::Ref{T}, n::Integer) where {T} =
unsafe_write(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller)
in io.jl, line 673-674. I’m wondering why the @noinline
here is needed, and why GC.@preserve p
would not be used instead. From my testing, overwriting that function, using GC.@preserve p
leads to no allocations.
julia> function Base.unsafe_write(s::IO, p::Ref{T}, n::Integer) where T
GC.@preserve p unsafe_write(s, Base.unsafe_convert(Ref{T}, p)::Ptr, n)
end
julia> @btime write(io, 100)
51.872 ns (0 allocations: 0 bytes)
8
Am I missing something here? Am I misunderstanding the role of GC-Rooting it via the caller instead of using GC.@preserve
?
The same @noinline
is present in
@noinline unsafe_read(s::IO, p::Ref{T}, n::Integer) where {T} = unsafe_read(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller)
and generic IO types that go into this function when calling e.g. read(io, Int)
(IOStream goes into a specialized function that doesn’t cause an allocation) have the same issue of having an allocation.
julia> struct tstio <: IO
b::UInt8
end
julia> Base.read(t::tstio, ::Type{UInt8}) = t.b
julia> Base.write(::tstio, x::UInt8) = 1
julia> const t = tstio(0xFF)
tstio(0xff)
julia> @btime read(t, UInt64)
11.796 ns (1 allocation: 16 bytes)
0xffffffffffffffff
julia> function Base.unsafe_read(s::IO, p::Ref{T}, n::Integer) where {T}
GC.@preserve p unsafe_read(s, Base.unsafe_convert(Ref{T}, p)::Ptr, n)
end
julia> @btime read(t, UInt64)
1.494 ns (0 allocations: 0 bytes)
0xffffffffffffffff
Is the @noinline
in these functions, which seems to have last been edited 4/5 years ago according to the git blame still needed, or does switching it out with GC.@preserve
make more sense?