-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Use @inbounds
annotations for Dict
implementation
#23843
Conversation
It seems to me that `Dict`s can be used in performance sensitive code, and ought to be as fast as possible. I made liberty of `@propagate_inbounds` in order to factor out the "chain of custody" (so to speak) of the indices.
Any microbenchmarks to show? |
Give me one minute to recompile master. I did one on v0.6 that took 1.3 seconds and this branch takes 0.95 seconds... I'll post an update soon. |
Here's a function that does some general dict-y operations. I heard BenchmakTools isn't working (didn't try) so I rolled my own: julia> function f(n)
keys = 1:n
vals1 = 2*(1:n)
vals2 = 3*(1:n)
dict = Dict{Int,Int}()
for k in keys
@inbounds dict[k] = vals1[k]
end
for k in keys
@inbounds dict[k] = dict[k] + vals2[k]
end
out = 0
for k in keys
out += dict[k]
end
return out
end
f (generic function with 1 method)
julia> @noinline g(n) = f(n)
g (generic function with 1 method)
julia> h(n) = for i in 1:10000; g(n); end
h (generic function with 1 method) On this branch (after warmup):
On master
On v0.6
So this PR represents a 10% improvement on this benchmark (with another 25% since v0.6... nice). |
base/dict.jl
Outdated
@@ -713,13 +713,19 @@ function start(t::Dict) | |||
return i | |||
end | |||
done(t::Dict, i) = i > length(t.vals) | |||
next(t::Dict{K,V}, i) where {K,V} = (Pair{K,V}(t.keys[i],t.vals[i]), skip_deleted(t,i+1)) | |||
function next(t::Dict{K,V}, i) where {K,V} | |||
@inbounds return (Pair{K,V}(t.keys[i],t.vals[i]), skip_deleted(t,i+1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is safe --- it's possible some user code could pass a bad value of i
by mistake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These could be marked @propagate_inbounds
, though; that seems to be what arrays do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ref #15291
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming that what is now here makes sense? And that for
loops expand/lower to include @inbounds next(...)
for fast iteration over e.g. Array
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, for loop cannot be lowered to @inbounds
. It only guarantee that the iterator protocol is properly executed, but not inbounds access in next
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Umm... so are you telling me that for x in vector
isn't optimally fast? It really spends time checking bounds, immediately after it has verified done
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh dear, I see #15291 wasn't merged... (I figured it was merged and we've moved onto something simpler, but alas)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @yuyichao for clearing that up. At least with @propagate_inbounds
here, users have the option of @inbounds for kv in dict
for a speedup, right?
Also, I'll have to update my benchmark.
base/dict.jl
Outdated
next(v::KeyIterator{<:Dict}, i) = (v.dict.keys[i], skip_deleted(v.dict,i+1)) | ||
next(v::ValueIterator{<:Dict}, i) = (v.dict.vals[i], skip_deleted(v.dict,i+1)) | ||
function next(v::KeyIterator{<:Dict}, i) | ||
@inbounds return (v.dict.keys[i], skip_deleted(v.dict,i+1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
Is it normal for CircleCI 64 bit to give |
Happens somewhat regularly. |
cf. JuliaCI/BaseBenchmarks.jl#109, which benchmarks some operations with |
OK, I'm sad we've regressed to stochastic testing... but I guess I should merge this then. |
It seems to me that
Dict
s can be used in performance sensitive code, and ought to be as fast as possible.I made liberty of
@propagate_inbounds
in order to factor out the "chain of custody" (so to speak) of the correctness of the indices.EDIT: See microbenchmark below (I didn't see anything in BaseBenchmarks.jl...)