Should I use @inbounds on for loop iterating through the values of a Dict?

Hello,

I’m wondering if it’s appropriate (and necessary) to use @inbounds on a for loop that iterates through the keys/values of a Dict. After a quick search the examples where @inbounds helps are where I’m indexing an Array, for example, but I’m working with Dicts. Here’s a test with dummy Dicts:

k = collect(1:1:1000);
v = collect(1:2:2000);
dict = Dict(zip(k,v));
a = 0

function test1()
   for v in values(dict)
       a = v
    end
end

function test2()
   for v in values(dict)
       @inbounds a = v
   end
end
julia> using BenchmarkTools

julia> @benchmark test1()
BenchmarkTools.Trial:
  memory estimate:  56.44 KiB
  allocs estimate:  2612
  --------------
  minimum time:     64.799 μs (0.00% GC)
  median time:      66.600 μs (0.00% GC)
  mean time:        75.709 μs (1.30% GC)
  maximum time:     1.054 ms (89.97% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> @benchmark test2()
BenchmarkTools.Trial:
  memory estimate:  56.44 KiB
  allocs estimate:  2612
  --------------
  minimum time:     64.400 μs (0.00% GC)
  median time:      77.900 μs (0.00% GC)
  mean time:        81.345 μs (1.30% GC)
  maximum time:     1.101 ms (92.41% GC)
  --------------
  samples:          10000
  evals/sample:     1

Looks like the performance is slightly worse with @inbounds in this case… am I missing anything?

FYI in reality the values of the dicts I’d loop through are mutable structs I defined elsewhere, whose fields are mostly either Float64 or Int.

Adding @inbounds only affects bounds checks within the expression you’ve marked @inbounds. In your case, that’s a = v, which doesn’t do any indexing at all. In fact, that expression doesn’t do anything, period. The a inside each function is a local variable, unrelated to the global variable a with the same name, so you’re just setting the value of a local variable a which is discarded at the end of the function.

You can, however, do @inbounds for v in ... to also disable any bounds checking which may happen in the actual process of iterating over values.

But before doing that, there’s a much more important performance issue with this code: Both of your functions reference a non-const global variable (dict). The slowness of iterating over a non-constant global variable dwarfs any small optimization you could get by using @inbounds. We can fix that by making dict const or, even better, passing it in as an argument:

julia> function test3(dict)
         for v in dict
           a = v
         end
       end
test3 (generic function with 1 method)

julia> @btime test1()
  57.739 μs (2612 allocations: 56.44 KiB)

julia> @btime test2()
  58.564 μs (2612 allocations: 56.44 KiB)

julia> @btime test3($dict)
  7.269 μs (0 allocations: 0 bytes)

With the global variable issue fixed, we can finally see some benefit from using @inbounds on the for loop itself:

julia> function test4(dict)
         @inbounds for v in dict
           a = v
         end
       end
test4 (generic function with 1 method)

julia> @btime test4($dict)
  4.795 μs (0 allocations: 0 bytes)

For more info see Performance Tips · The Julia Language and remember to never use non-constant global variables for code whose performance you care about. After all, it’s the very first performance tip in that list for a reason.

5 Likes

Thank you very much for your explanation. You’re absolutely right: I forgot to mention that all the loops I was working with were not referencing a global dict, but more like

function foo(dict)
    ...
    for v in values(dict)
        access/change v.field1
        access/change v.field2
        ...
    end
    ...
end

And I’d do dict = load_fn("filename"); foo(dict), where load_fn is another function that reads a local file and build the dict. Lastly, do I need to explicitly call foo($dict) with $? I thought by default Julia function arguments are passed by reference/sharing?

Thanks again :slight_smile:

You only need the dollar sign to get meaningful performance testing numbers, ie for @btime
For your code you should not use it (it will produce an error)

3 Likes