Skip to content

Commit 6db72e7

Browse files
committed
add SwissDict to bench_dict, and improve benchmarks
1 parent a322c74 commit 6db72e7

File tree

4 files changed

+16
-21
lines changed

4 files changed

+16
-21
lines changed

src/DataStructures.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ module DataStructures
5656
export MultiDict, enumerateall
5757
export RobinDict
5858
export OrderedRobinDict, isordered
59+
export SwissDict
5960

6061
export DiBitVector
6162

@@ -96,6 +97,7 @@ module DataStructures
9697
include("container_loops.jl")
9798
include("robin_dict.jl")
9899
include("ordered_robin_dict.jl")
100+
include("swiss_dict.jl")
99101
export
100102
CircularBuffer,
101103
capacity,

src/swiss_dict.jl

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import Base: setindex!, sizehint!, empty!, isempty, length, copy, empty,
2-
getindex, getkey, haskey, iterate, @propagate_inbounds, ValueIterator,
3-
pop!, delete!, get, get!, isbitstype, in, hashindex, isbitsunion,
4-
isiterable, dict_with_eltype, KeySet, Callable, _tablesz, filter!
5-
61
const SWISS_DICT_LOAD_FACTOR = 0.75
72
const _u8x16 = NTuple{16, VecElement{UInt8}}
83

@@ -101,14 +96,14 @@ end
10196
return (hi, tag)
10297
end
10398

104-
@propagate_inbounds function _slotget(slots::Vector{_u8x16}, i::Int)
99+
Base.@propagate_inbounds function _slotget(slots::Vector{_u8x16}, i::Int)
105100
@boundscheck 0 < i <= length(slots)*16 || throw(BoundsError(slots, 1 + (i-1)>>4))
106101
GC.@preserve slots begin
107102
return unsafe_load(convert(Ptr{UInt8}, pointer(slots)), i)
108103
end
109104
end
110105

111-
@propagate_inbounds function _slotset!(slots::Vector{_u8x16}, v::UInt8, i::Int)
106+
Base.@propagate_inbounds function _slotset!(slots::Vector{_u8x16}, v::UInt8, i::Int)
112107
@boundscheck 0 < i <= length(slots)*16 || throw(BoundsError(slots, 1 + (i-1)>>4))
113108
GC.@preserve slots begin
114109
return unsafe_store!(convert(Ptr{UInt8}, pointer(slots)), v, i)
@@ -201,7 +196,7 @@ function _setindex!(h::SwissDict, v, key, index, tag)
201196
end
202197

203198
function _delete!(h::SwissDict{K,V}, index) where {K,V}
204-
#Caller is responsible for maybe shrinking the SwissDict after the deletion.
199+
# Caller is responsible for maybe shrinking the SwissDict after the deletion.
205200
isbitstype(K) || isbitsunion(K) || ccall(:jl_arrayunset, Cvoid, (Any, UInt), h.keys, index-1)
206201
isbitstype(V) || isbitsunion(V) || ccall(:jl_arrayunset, Cvoid, (Any, UInt), h.vals, index-1)
207202
isboundary = iszero(index & 0x0f) #boundaries: 16, 32, ...
@@ -212,7 +207,7 @@ function _delete!(h::SwissDict{K,V}, index) where {K,V}
212207
end
213208

214209

215-
#fast iteration over active slots.
210+
# fast iteration over active slots.
216211
function _iterslots(h::SwissDict, start::Int)
217212
i0 = ((start-1) & (length(h.keys)-1))>>4 + 1
218213
off = (start-1) & 0x0f
@@ -232,13 +227,13 @@ function _iterslots(h::SwissDict, state)
232227
return ((i-1)*16 + trailing_zeros(sl) + 1, (i, _blsr(sl)))
233228
end
234229

235-
#Dictionary resize logic:
236-
#Guarantee 40% of buckets and 15% of entries free, and at least 25% of entries filled
237-
#growth when > 85% entries full or > 60% buckets full, shrink when <25% entries full.
238-
#>60% bucket full should be super rare outside of very bad hash collisions or
239-
#super long-lived Dictionaries (expected 0.85^16 = 7% buckets full at 85% entries full).
240-
#worst-case hysteresis: shrink at 25% vs grow at 30% if all hashes collide.
241-
#expected hysteresis is 25% to 42.5%.
230+
# Dictionary resize logic:
231+
# Guarantee 40% of buckets and 15% of entries free, and at least 25% of entries filled
232+
# growth when > 85% entries full or > 60% buckets full, shrink when <25% entries full.
233+
# >60% bucket full should be super rare outside of very bad hash collisions or
234+
# super long-lived Dictionaries (expected 0.85^16 = 7% buckets full at 85% entries full).
235+
# worst-case hysteresis: shrink at 25% vs grow at 30% if all hashes collide.
236+
# expected hysteresis is 25% to 42.5%.
242237
function maybe_rehash_grow!(h::SwissDict)
243238
sz = length(h.keys)
244239
if h.count > sz * SWISS_DICT_LOAD_FACTOR || (h.nbfull-1) * 10 > sz * 6
@@ -459,15 +454,15 @@ function delete!(h::SwissDict, key)
459454
return h
460455
end
461456

462-
@propagate_inbounds function iterate(h::SwissDict, state = h.idxfloor)
457+
Base.@propagate_inbounds function iterate(h::SwissDict, state = h.idxfloor)
463458
is = _iterslots(h, state)
464459
is === nothing && return nothing
465460
i, s = is
466461
@inbounds p = h.keys[i] => h.vals[i]
467462
return (p, s)
468463
end
469464

470-
@propagate_inbounds function iterate(v::Union{KeySet{<:Any, <:SwissDict}, ValueIterator{<:SwissDict}}, state=v.dict.idxfloor)
465+
Base.@propagate_inbounds function iterate(v::Union{KeySet{<:Any, <:SwissDict}, Base.ValueIterator{<:SwissDict}}, state=v.dict.idxfloor)
471466
is = _iterslots(v.dict, state)
472467
is === nothing && return nothing
473468
i, s = is

test/bench_robin_dict.jl renamed to test/bench_dict.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ using Random
55

66
suite = BenchmarkGroup()
77

8-
dicttypes = [Dict, RobinDict]
8+
dicttypes = [Dict, RobinDict, SwissDict]
99
KVtypes = [Int, Float64]
1010
aexps = [4]
1111
for dt in dicttypes

test/test_swiss_dict.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
include("../src/swiss_dict.jl")
2-
31
@testset "Constructors" begin
42
h1 = SwissDict()
53
@test length(h1) == 0

0 commit comments

Comments
 (0)