# Performance of loops

**URL:** <https://discourse.julialang.org/t/performance-of-loops/118273>\
**Category:** New to Julia\
**Tags:** broadcast, loops, aliasing\
**Created:** [August 16, 2024, 1:04pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273 "2024-08-16T13:04:17Z")\
**Posts on this page:** 11\
**Page:** 2

<div class="post-metadata">

**Author:** ![stephancb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stephancb/32/14243_2.png) [@stephancb](https://discourse.julialang.org/u/stephancb)\
**Post date:** [August 16, 2024, 8:48pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273/23 "2024-08-16T20:48:03Z")

</div>

With the functions as in the OP I get

```julia
julia> x = Random.rand(Int64, 2)
2-element Vector{Int64}:
  -260682396598444552
 -7056639715971435629

julia> loop_with_map(x);

julia> x
2-element Vector{Int64}:
  -260682396598444552
 -7056639715971435629
# x still the same

```

but

```julia
julia> loop_with_map_dot(x);

julia> x
2-element Vector{Int64}:
 -521364793196889104
 4333464641766680358
# x has mutated

```

Similarly for `loop_via_broadcast`,

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [August 16, 2024, 8:56pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273/24 "2024-08-16T20:56:24Z")

</div>

A meta comment is that you almost never want a loop to perform an operation as simple as multiplying elements by 2 if performance matters. Your granularity is too small. You want to combine loops so that you make as few passes over your arrays as possible and do lots of work per iteration.

Coarse-graining your loops also helps you to eliminate temporary arrays.

(Languages like Matlab and Python teach people the bad habit of decomposing every algorithm into sequences of simple “vectorized” steps, to work around the performance limitations of their loops.)

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [August 16, 2024, 9:03pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273/25 "2024-08-16T21:03:39Z")

</div>

Yes, only the mutating functions should have a trailing `!` in the name. If you see `x = ...`, it’s assignment, not mutation.

---

<div class="post-metadata">

**Author:** ![stephancb](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stephancb/32/14243_2.png) [@stephancb](https://discourse.julialang.org/u/stephancb)\
**Post date:** [August 16, 2024, 9:12pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273/26 "2024-08-16T21:12:42Z")

</div>

So it should be

```julia
function loop_with_map_dot!(x) ...

```

but

```julia
function loop_with_map(x) ...

```

is fine, as it does not mutate?

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [August 16, 2024, 9:20pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273/27 "2024-08-16T21:20:29Z")

</div>

> [@abraemer](#):
>
> Everything else (like broadcasting) is eye candy/convenience in the end.

Broadcasting is more generic in that it also works well on GPU arrays. As a general rule, I’d recommend `x .*= 2` for that reason.

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [August 17, 2024, 5:23am UTC](https://discourse.julialang.org/t/performance-of-loops/118273/28 "2024-08-17T05:23:19Z")

</div>

> [@danielwe](#):
>
> Broadcasting is more generic in that it also works well on GPU arrays. As a general rule, I’d recommend `x .*= 2` for that reason.

In the light of OP’s context

> [@Matthijs\_1971](#):
>
> Professionally, I never feel brave, sorry. My company makes extremely expensive machines and downtime of these machines is also very expensive… and rework of the products of these machines is also costly. So I am looking for the safest, securest ways for loops without loosing performance so I can promote such ways to colleagues.

It doesn’t sound to me like GPU computing matters and then broadcasting bears the risk of being slower, is generally less expressive than loops and possible harder to read.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [August 17, 2024, 7:45am UTC](https://discourse.julialang.org/t/performance-of-loops/118273/29 "2024-08-17T07:45:37Z")

</div>

> [@abraemer](#):
>
> broadcasting bears the risk of being slower, is generally less expressive than loops and possible harder to read.

If this were really true, then broadcasting would would have no point at all. In many cases broadcasting is both as fast, as well as safer, and _definitely_ easier to read. Not always, but often.

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [August 17, 2024, 9:06am UTC](https://discourse.julialang.org/t/performance-of-loops/118273/30 "2024-08-17T09:06:39Z")

</div>

I see that my short statement might convey the wrong message.  
I assumed that OP likely has generally more complicated operations than multiplying all elements of an array by 2. Broadcasting is great for smallish and rather simple operations where it is also very readable. However in general explicit loops offer the most expressive power (i.e. there is loop code that you cannot reasonably express as broadcast). So in general a `for` loop is the most basic construct for efficient looping while broadcasting can make things nicer to read and perhaps a bit easier to write iff the problem is suited for broadcasting.

And for performance I was referring to:

> [@mbauman](#):
>
> Yes, broadcasting is much more conservative when it comes to aliasing — `broadcast!` _doesn’t_ have that warning, but you’ll also see that it (sometimes) allocates more than you’d expect. It errs on the side of (sometimes) making surprising defensive copies, which you may not want in cases where you’re chasing peak performance

So especially for beginners, I would recommend writing correct loop-based code first and then learning to see opportunities where broadcasting can simplify things. However, GPU computing might be an exception.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [August 17, 2024, 9:28am UTC](https://discourse.julialang.org/t/performance-of-loops/118273/31 "2024-08-17T09:28:47Z")

</div>

> [@abraemer](#):
>
> So especially for beginners, I would recommend writing correct loop-based code first and then learning to see opportunities where broadcasting can simplify things.

Writing loop-based code can in some cases be significantly more challenging, in particular, you need to pre-allocate and figure out correct element-types, which can be highly non-trivial. Broadcasting is also likely to be very familiar to beginners coming from the python/matlab/r world.

I would definitely recommend _both_ styles from the very beginning.

---

<div class="post-metadata">

**Author:** ![Matthijs\_1971](https://avatars.discourse-cdn.com/v4/letter/m/bb73d2/32.png) [@Matthijs\_1971](https://discourse.julialang.org/u/Matthijs_1971)\
**Post date:** [August 19, 2024, 2:29pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273/32 "2024-08-19T14:29:00Z")

</div>

Thanks for your comments and advice. The original code misses exclamation marks in function signatures and the use of `map!` opens possibilities for aliasing issues if the mutated argument shares memory with any other argument. So I conclude that `map` and `map!` are indeed slow and not recommended right now. Promoting `map!` on my end will sooner or later lead to an aliasing issue when the subtleties of the documentation are forgotten. It is better to use `broadcast` or `broadcast!` as replacement for `map` and `map!`. An allocation for the results of the calculation can be avoided if a pre-existing collection can be overwritten using `.=`. From the documentation: “A special syntax exists for broadcasting: f.(args…) is equivalent to broadcast(f, args…)” so the dot notation may as well be used for the right hand side instead of typing `broadcast` in full. Combining these two gives several dots and the `.@` macro can be used to keep track of the dots automatically. Also, broadcasting may allocate more memory than expected. With a `for`-loop it is unlikely that performance or clarity is lost.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [August 19, 2024, 8:20pm UTC](https://discourse.julialang.org/t/performance-of-loops/118273/33 "2024-08-19T20:20:45Z")

</div>

One thing I learned while thinking about this is that you really do have to explicitly broadcast the multiplication, otherwise the following forms are slow.

```julia-repl
julia> function f(x)
           x .= 2x
       end
f (generic function with 1 method)

julia> @benchmark f($x)
BenchmarkTools.Trial: 10000 samples with 194 evaluations.
 Range (min … max): 507.515 ns … 61.053 μs ┊ GC (min … max): 0.00% … 98.65%
 Time (median): 817.332 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 1.165 μs ± 1.813 μs ┊ GC (mean ± σ): 27.94% ± 16.65%

  ▃█▇▅ ▁ ▁
  ████▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆█████ █
  508 ns Histogram: log(frequency) by time 10.5 μs <

 Memory estimate: 8.00 KiB, allocs estimate: 1.

julia> function f(x)
           x .= 2*x
       end
f (generic function with 1 method)

julia> @benchmark f($x)
BenchmarkTools.Trial: 10000 samples with 193 evaluations.
 Range (min … max): 506.047 ns … 61.707 μs ┊ GC (min … max): 0.00% … 98.48%
 Time (median): 823.187 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 1.195 μs ± 1.886 μs ┊ GC (mean ± σ): 28.36% ± 16.65%

  ▂█▆▅ ▁
  ████▆▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆▇███▇█ █
  506 ns Histogram: log(frequency) by time 11 μs <

 Memory estimate: 8.00 KiB, allocs estimate: 1.

julia> function f(x)
           x .= 2 .* x
       end
f (generic function with 1 method)

```

Relative to using `.*` or `@.`.

```julia-repl
julia> @benchmark f($x)
BenchmarkTools.Trial: 10000 samples with 944 evaluations.
 Range (min … max): 99.664 ns … 220.824 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 101.076 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 101.127 ns ± 2.302 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

  █▅▂ ▆█▄▂▁ ▁▁ ▁▁▃▁▁ ▁▂▁ ▂
  ████▇█▇████████████████▇███████▇▇█▇▇▇▆▇▆▇▆▇▆▆▄▅▅▄▇▄▅▄▄▃▄▄▄▁▆▅ █
  99.7 ns Histogram: log(frequency) by time 109 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> function f(x)
           @. x = 2x
       end
f (generic function with 1 method)

julia> @benchmark f($x)
BenchmarkTools.Trial: 10000 samples with 946 evaluations.
 Range (min … max): 99.673 ns … 129.185 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 101.040 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 101.023 ns ± 1.653 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▇▆▁▂ ▃█▅▄▃ ▁ ▂ ▁
  ████▆▅▅▅▅▆▅███████▇█▇▆▇▆▇▆███▇▇▆▇▆█▅▇▆███▇▅▆▅▆▆▆▆▅▅▅▄▆▅▆▅▇▅▅▆ █
  99.7 ns Histogram: log(frequency) by time 106 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

[Previous page](https://discourse.julialang.org/t/performance-of-loops/118273.md?page=1)
