Skip to content

Commit

Permalink
simplify and slightly improve memorynew inference (#56857)
Browse files Browse the repository at this point in the history
while investigating some missed optimizations in
#56847, @gbaraldi and I realized
that `copy(::Array)` was using `jl_genericmemory_copy_slice` rather than
the `memmove`/`jl_genericmemory_copyto` that `copyto!` lowers to. This
version lets us use the faster LLVM based Memory initialization, and the
memove can theoretically be further optimized by LLVM (e.g. not copying
elements that get over-written without ever being read).

```
julia> @Btime copy($[1,2,3])
  15.521 ns (2 allocations: 80 bytes) # before
  12.116 ns (2 allocations: 80 bytes) #after

julia> m = Memory{Int}(undef, 3);
julia> m.=[1,2,3];
julia> @Btime copy($m)
  11.013 ns (1 allocation: 48 bytes) #before
  9.042 ns (1 allocation: 48 bytes)   #after
```

We also optimize the `memorynew` type inference to make it so that
getting the length of a memory with known length will propagate that
length information (which is important for cases like `similar`/`copy`
etc).
  • Loading branch information
oscardssmith authored Dec 23, 2024
1 parent dde5028 commit 8fac39b
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 18 deletions.
20 changes: 7 additions & 13 deletions Compiler/src/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2017,9 +2017,12 @@ function tuple_tfunc(𝕃::AbstractLattice, argtypes::Vector{Any})
return anyinfo ? PartialStruct(𝕃, typ, argtypes) : typ
end

@nospecs function memorynew_tfunc(𝕃::AbstractLattice, memtype, m)
hasintersect(widenconst(m), Int) || return Bottom
return tmeet(𝕃, instanceof_tfunc(memtype, true)[1], GenericMemory)
@nospecs function memorynew_tfunc(𝕃::AbstractLattice, memtype, memlen)
hasintersect(widenconst(memlen), Int) || return Bottom
memt = tmeet(𝕃, instanceof_tfunc(memtype, true)[1], GenericMemory)
memt == Union{} && return memt
# PartialStruct so that loads of Const `length` get inferred
return PartialStruct(𝕃, memt, Any[memlen, Ptr{Nothing}])
end
add_tfunc(Core.memorynew, 2, 2, memorynew_tfunc, 10)

Expand Down Expand Up @@ -3125,16 +3128,7 @@ add_tfunc(Core.get_binding_type, 2, 2, @nospecs((𝕃::AbstractLattice, args...)

const FOREIGNCALL_ARG_START = 6

function foreigncall_effects(@specialize(abstract_eval), e::Expr)
args = e.args
name = args[1]
isa(name, QuoteNode) && (name = name.value)
if name === :jl_alloc_genericmemory
nothrow = new_genericmemory_nothrow(abstract_eval, args)
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow)
elseif name === :jl_genericmemory_copy_slice
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow=false)
end
function foreigncall_effects(@nospecialize(abstract_eval), e::Expr)
# `:foreigncall` can potentially perform all sorts of operations, including calling
# overlay methods, but the `:foreigncall` itself is not dispatched, and there is no
# concern that the method calls that potentially occur within the `:foreigncall` will
Expand Down
9 changes: 5 additions & 4 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,13 @@ See also [`copy!`](@ref Base.copy!), [`copyto!`](@ref), [`deepcopy`](@ref).
"""
copy

@eval function copy(a::Array{T}) where {T}
# `jl_genericmemory_copy_slice` only throws when the size exceeds the max allocation
# size, but since we're copying an existing array, we're guaranteed that this will not happen.
@eval function copy(a::Array)
# `copy` only throws when the size exceeds the max allocation size,
# but since we're copying an existing array, we're guaranteed that this will not happen.
@_nothrow_meta
ref = a.ref
newmem = ccall(:jl_genericmemory_copy_slice, Ref{Memory{T}}, (Any, Ptr{Cvoid}, Int), ref.mem, ref.ptr_or_offset, length(a))
newmem = typeof(ref.mem)(undef, length(a))
@inbounds unsafe_copyto!(memoryref(newmem), ref, length(a))
return $(Expr(:new, :(typeof(a)), :(memoryref(newmem)), :(a.size)))
end

Expand Down
9 changes: 8 additions & 1 deletion base/genericmemory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ function unsafe_copyto!(dest::Memory{T}, doffs, src::Memory{T}, soffs, n) where{
return dest
end

#fallback method when types don't match
function unsafe_copyto!(dest::Memory, doffs, src::Memory, soffs, n)
@_terminates_locally_meta
n == 0 && return dest
Expand Down Expand Up @@ -171,7 +172,13 @@ function unsafe_copyto!(dest::Memory, doffs, src::Memory, soffs, n)
return dest
end

copy(a::T) where {T<:Memory} = ccall(:jl_genericmemory_copy, Ref{T}, (Any,), a)
function copy(a::T) where {T<:Memory}
# `copy` only throws when the size exceeds the max allocation size,
# but since we're copying an existing array, we're guaranteed that this will not happen.
@_nothrow_meta
newmem = T(undef, length(a))
@inbounds unsafe_copyto!(newmem, 1, a, 1, length(a))
end

copyto!(dest::Memory, src::Memory) = copyto!(dest, 1, src, 1, length(src))
function copyto!(dest::Memory, doffs::Integer, src::Memory, soffs::Integer, n::Integer)
Expand Down

0 comments on commit 8fac39b

Please sign in to comment.