Skip to content

Commit cf123a8

Browse files
tkfvtjnash
andauthored
Optimize copy!(::Dict, ::Dict) (#34101)
Co-authored-by: Jameson Nash <[email protected]>
1 parent faafb78 commit cf123a8

File tree

2 files changed

+81
-5
lines changed

2 files changed

+81
-5
lines changed

base/dict.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,60 @@ function empty!(h::Dict{K,V}) where V where K
274274
return h
275275
end
276276

277+
# Fast pass for exactly same `Dict` type:
278+
function copy!(dst::D, src::D) where {D <: Dict}
279+
copy!(dst.vals, src.vals)
280+
copy!(dst.keys, src.keys)
281+
copy!(dst.slots, src.slots)
282+
dst.ndel = src.ndel
283+
dst.count = src.count
284+
dst.age = src.age
285+
dst.idxfloor = src.idxfloor
286+
dst.maxprobe = src.maxprobe
287+
return dst
288+
end
289+
290+
# Fast pass when not changing key types (hence not changing hashes).
291+
# It is unsafe to call this function in the sense it may leave `dst`
292+
# in a state that is unsafe to use after `unsafe_copy!` failed.
293+
function unsafe_copy!(dst::Dict, src::Dict)
294+
resize!(dst.vals, length(src.vals))
295+
resize!(dst.keys, length(src.keys))
296+
297+
svals = src.vals
298+
skeys = src.keys
299+
dvals = dst.vals
300+
dkeys = dst.keys
301+
@inbounds for i = src.idxfloor:lastindex(svals)
302+
if isslotfilled(src, i)
303+
dvals[i] = svals[i]
304+
dkeys[i] = skeys[i]
305+
end
306+
end
307+
308+
copy!(dst.slots, src.slots)
309+
310+
dst.ndel = src.ndel
311+
dst.count = src.count
312+
dst.age = src.age
313+
dst.idxfloor = src.idxfloor
314+
dst.maxprobe = src.maxprobe
315+
return dst
316+
end
317+
318+
function copy!(dst::Dict{Kd}, src::Dict{Ks}) where {Kd, Ks<:Kd}
319+
try
320+
return unsafe_copy!(dst, src)
321+
catch
322+
empty!(dst) # avoid leaving `dst` in a corrupt state
323+
rethrow()
324+
end
325+
end
326+
327+
# It's safe to call `unsafe_copy!` if `Vd>:Vs`:
328+
copy!(dst::Dict{Kd,Vd}, src::Dict{Ks,Vs}) where {Kd, Ks<:Kd, Vd, Vs<:Vd} =
329+
unsafe_copy!(dst, src)
330+
277331
# get the index where a key is stored, or -1 if not present
278332
function ht_keyindex(h::Dict{K,V}, key) where V where K
279333
sz = length(h.keys)

test/dict.jl

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,13 +1071,35 @@ end
10711071
end
10721072

10731073
@testset "copy!" begin
1074-
s = Dict(1=>2, 2=>3)
1075-
for a = ([3=>4], [0x3=>0x4], [3=>4, 5=>6, 7=>8], Pair{UInt,UInt}[3=>4, 5=>6, 7=>8])
1076-
@test s === copy!(s, Dict(a)) == Dict(a)
1077-
if length(a) == 1 # current limitation of Base.ImmutableDict
1078-
@test s === copy!(s, Base.ImmutableDict(a[])) == Dict(a[])
1074+
@testset "copy!(::$(typeof(s)), _)" for s in Any[
1075+
Dict(1 => 2, 2 => 3), # concrete key type
1076+
Dict{Union{Int,UInt},Int}(1 => 2, 2 => 3), # union key type
1077+
Dict{Any,Int}(1 => 2, 2 => 3), # abstract key type
1078+
Dict{Int,Float64}(1 => 2, 2 => 3), # values are converted
1079+
]
1080+
@testset "copy!(_, ::$(typeof(Dict(a))))" for a in Any[
1081+
[3 => 4],
1082+
[0x3 => 0x4],
1083+
[3 => 4, 5 => 6, 7 => 8],
1084+
Pair{UInt,UInt}[3=>4, 5=>6, 7=>8],
1085+
]
1086+
if s isa Dict{Union{Int,UInt},Int} && a isa Vector{Pair{UInt8,UInt8}}
1087+
@test_broken s === copy!(s, Dict(a)) == Dict(a)
1088+
continue
1089+
end
1090+
@test s === copy!(s, Dict(a)) == Dict(a)
1091+
if length(a) == 1 # current limitation of Base.ImmutableDict
1092+
@test s === copy!(s, Base.ImmutableDict(a[])) == Dict(a[])
1093+
end
10791094
end
10801095
end
1096+
1097+
@testset "no corruption on failed copy!" begin
1098+
dst = Dict{Int,Int}(1 => 2)
1099+
# Fails while trying `convert`:
1100+
@test_throws MethodError copy!(dst, Dict(1 => "2"))
1101+
@test dst == Dict()
1102+
end
10811103
end
10821104

10831105
@testset "map!(f, values(dict))" begin

0 commit comments

Comments
 (0)