Skip to content

Commit 8fac39b

Browse files
authored
simplify and slightly improve memorynew inference (#56857)
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).
1 parent dde5028 commit 8fac39b

File tree

3 files changed

+20
-18
lines changed

3 files changed

+20
-18
lines changed

Compiler/src/tfuncs.jl

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,9 +2017,12 @@ function tuple_tfunc(𝕃::AbstractLattice, argtypes::Vector{Any})
20172017
return anyinfo ? PartialStruct(𝕃, typ, argtypes) : typ
20182018
end
20192019

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

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

31263129
const FOREIGNCALL_ARG_START = 6
31273130

3128-
function foreigncall_effects(@specialize(abstract_eval), e::Expr)
3129-
args = e.args
3130-
name = args[1]
3131-
isa(name, QuoteNode) && (name = name.value)
3132-
if name === :jl_alloc_genericmemory
3133-
nothrow = new_genericmemory_nothrow(abstract_eval, args)
3134-
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow)
3135-
elseif name === :jl_genericmemory_copy_slice
3136-
return Effects(EFFECTS_TOTAL; consistent=CONSISTENT_IF_NOTRETURNED, nothrow=false)
3137-
end
3131+
function foreigncall_effects(@nospecialize(abstract_eval), e::Expr)
31383132
# `:foreigncall` can potentially perform all sorts of operations, including calling
31393133
# overlay methods, but the `:foreigncall` itself is not dispatched, and there is no
31403134
# concern that the method calls that potentially occur within the `:foreigncall` will

base/array.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,12 +346,13 @@ See also [`copy!`](@ref Base.copy!), [`copyto!`](@ref), [`deepcopy`](@ref).
346346
"""
347347
copy
348348

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

base/genericmemory.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ function unsafe_copyto!(dest::Memory{T}, doffs, src::Memory{T}, soffs, n) where{
144144
return dest
145145
end
146146

147+
#fallback method when types don't match
147148
function unsafe_copyto!(dest::Memory, doffs, src::Memory, soffs, n)
148149
@_terminates_locally_meta
149150
n == 0 && return dest
@@ -171,7 +172,13 @@ function unsafe_copyto!(dest::Memory, doffs, src::Memory, soffs, n)
171172
return dest
172173
end
173174

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

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

0 commit comments

Comments
 (0)