Alright guys, follow up.
How is it that this allocates:
const test_line1 = "\"x\":1622764914150,\"y\":31534070432,\0";
const fmt1 = "\"x\":%ld,\"y\":%ld,\0";
function pp(func::Function, line::AbstractString)
x_ref = Ref{Clong}();
y_ref = Ref{Clong}();
result = ccall(:sscanf, Cint, (Cstring, Ptr{UInt8}, Ptr{Clong}, Ptr{Clong}), pointer(line), fmt1, x_ref, y_ref)
# println("$(x_ref[]), $(y_ref[])")
return result
end
function test_alloc1(line)
counter = 0
r = pp((x)->counter+=1, line)
end
@allocated test_alloc1(test_line1)
But this doesn’t:
const test_line2 = "\"x\":1622764914150,";
const fmt2 = "\"x\":%ld,\0";
function pp(func::Function, line::AbstractString)
x_ref = Ref{Clong}();
result = ccall(:sscanf, Cint, (Cstring, Ptr{UInt8}, Ptr{Clong}), pointer(line), fmt2, x_ref)
# println("$(x_ref[])")
return result
end
function test_alloc2(line)
counter = 0
r = pp((x)->counter+=1, line)
end
@allocated test_alloc2(test_line2)
I’ve left inside commented out println
s which you can remove to check that the ccall
is actually doing the right thing.
EDIT:
I’ve found that the @code_typed
of these two functions is quite different. The Type{Code.Box}
on the first one looks super suspicious to me.
julia> @code_typed test_alloc1(test_line1)
CodeInfo(
1 ─ %1 = Core.Box::Type{Core.Box}
│ %2 = %new(%1)::Core.Box
│ Core.setfield!(%2, :contents, 0)::Int64
│ %4 = %new(Main.:(var"#15#16"), %2)::var"#15#16"
│ %5 = invoke Main.pp1(%4::Function, line::String)::Int32
└── return %5
) => Int32
julia> @code_typed test_alloc2(test_line2)
CodeInfo(
1 ─ %1 = %new(Base.RefValue{Int64})::Base.RefValue{Int64}
│ %2 = $(Expr(:foreigncall, :(:jl_string_ptr), Ptr{UInt8}, svec(Any), 0, :(:ccall), Core.Argument(2)))::Ptr{UInt8}
│ %3 = Base.bitcast(Base.Cstring, %2)::Cstring
│ %4 = Main.fmt2::String
│ %5 = $(Expr(:foreigncall, :(:jl_string_ptr), Ptr{UInt8}, svec(Any), 0, :(:ccall), :(%4)))::Ptr{UInt8}
│ %6 = $(Expr(:foreigncall, :(:jl_value_ptr), Ptr{Nothing}, svec(Any), 0, :(:ccall), :(%1)))::Ptr{Nothing}
│ %7 = Base.bitcast(Ptr{Int64}, %6)::Ptr{Int64}
│ %8 = $(Expr(:foreigncall, :(:sscanf), Int32, svec(Cstring, Ptr{UInt8}, Ptr{Int64}), 0, :(:ccall), :(%3), :(%5), :(%7), :(%1), :(%4), :(%3)))::Int32
└── return %8
) => Int32
EDIT2:
Some further hints. In the first code, replacing counter+=1
with counter+1
makes it the boxing disappear and allocations get reduced from 32 to 16. The @code_typed
in this case becomes:
julia> @code_typed test_alloc1(test_line1)
CodeInfo(
1 ─ %1 = π (0, Int64)
│ %2 = %new(var"#31#32"{Int64}, %1)::var"#31#32"{Int64}
│ %3 = invoke Main.pp1(%2::Function, line::String)::Int32
└── return %3
) => Int32
So the questions remain: why is boxing necessary? counter
is Int
and so is 1
. What is the %new
in the latest @code_typed
?