Skip to content

Fix unnecessary allocations in prepend! for small vectors by checking… #58839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 53 additions & 12 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1426,34 +1426,74 @@ julia> prepend!([6], [1, 2], [3, 4, 5])
"""
function prepend! end

function prepend!(a::Vector{T}, items::Union{AbstractVector{<:T},Tuple}) where T
import Base: _capacity, _set_length!

function prepend!(a::Vector{T}, items::Union{AbstractVector{<:T}, Tuple}) where T
# Convert tuple to vector if needed
items isa Tuple && (items = map(x -> convert(T, x), items))
n = length(items)
_growbeg!(a, n)
# in case of aliasing, the _growbeg might have shifted our data, so copy
# just the last n elements instead of all of them from the first
copyto!(a, 1, items, lastindex(items)-n+1, n)
len = length(a)
cap = _capacity(a)

if cap >= len + n
# Enough capacity: shift elements right by n
for i in reverse(1:len)
a[i + n] = a[i]
end
# Copy new items at the beginning
copyto!(a, 1, items, lastindex(items) - n + 1, n)
# Update length of the vector
_set_length!(a, len + n)
else
# Not enough capacity: fall back to growbeg! (may allocate)
_growbeg!(a, n)
copyto!(a, 1, items, lastindex(items) - n + 1, n)
end

return a
end

prepend!(a::AbstractVector, iter) = _prepend!(a, IteratorSize(iter), iter)
pushfirst!(a::AbstractVector, iter...) = prepend!(a, iter)
prepend!(a::AbstractVector, iter...) = (for v = reverse(iter); prepend!(a, v); end; return a)

function _prepend!(a::Vector, ::Union{HasLength,HasShape}, iter)
function _prepend!(a::Vector, ::Union{HasLength, HasShape}, iter)
@_terminates_locally_meta
require_one_based_indexing(a)
n = Int(length(iter))::Int
sizehint!(a, length(a) + n; first=true, shrink=false)
n = 0
len = length(a)
cap = Base._capacity(a)

if cap < len + n
# Not enough capacity, fallback to repeated pushfirst! (less efficient)
sizehint!(a, len + n; first=true, shrink=false)
for item in iter
pushfirst!(a, item)
end
reverse!(a, 1, n)
return a
end

# Enough capacity: shift elements right once
for i in reverse(1:len)
a[i + n] = a[i]
end

# Copy items to the front
idx = 1
for item in iter
n += 1
pushfirst!(a, item)
a[idx] = item
idx += 1
end
reverse!(a, 1, n)
a

# Update length after prepend
Base._set_length!(a, len + n)

return a
end

function _prepend!(a::Vector, ::IteratorSize, iter)
# fallback for iterators with unknown length
n = 0
for item in iter
n += 1
Expand All @@ -1463,6 +1503,7 @@ function _prepend!(a::Vector, ::IteratorSize, iter)
a
end


"""
resize!(a::Vector, n::Integer) -> a

Expand Down