# Which is the most efficient way to add matrices/arrays in Julia 1.x?

**URL:** https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602
**Category:** Performance
**Tags:** matrices, julia-1x
**Created:** [May 16, 2020, 10:03pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602 "2020-05-16T22:03:27Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![KZiemian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kziemian/32/9020_2.png) [@KZiemian](https://discourse.julialang.org/u/KZiemian)
#### Post date: [May 16, 2020, 10:03pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/1 "2020-05-16T22:03:27Z")

</div>

Adding matrices is very important thing in numerical computations and I fell that discusion of results of benchmarking this in Julia 1.x should take place here. Because I don’t find such topic on Julia Discourse I put what I found as a starting point. I apologized if I just miss this topic already discussed.

Few remarks. First, I was using Julia 1.3.1. Second, I don’t came up with this code, I just did tutorial for `DiffEqTutorial.jl` section [_Optimizing DiffEq Code_](https://github.com/JuliaDiffEq/DiffEqTutorials.jl/blob/master/notebook/introduction/03-optimizing_diffeq_code.ipynb) and was unable to understand results that I get.

We are benchmarking functions:

```julia
test1(A, B, C) = A + B + C

test2(A, B, C) = map((a, b, c) -> a + b + c, A, B, C)

function test3(A,B,C)
  D = similar(A)
  @inbounds for i in eachindex(A)
    D[i] = A[i] + B[i] + C[i]
  end
  D
end

test4(A, B, C) = A .+ B .+ C

test5(A, B, C) = @. A + B + C

test6!(D, A, B, C) = D .= A .+ B .+ C

test7!(D, A, B, C) = @. D = A + B + C

test8!(D, A, B, C) = map!((a, b, c) -> a + b + c, D, A, B, C)

```

For this we create matrices

```julia
A = rand(1000,1000); B = rand(1000,1000); C = rand(1000,1000); D = zeros(1000,1000)

```

Resultants for benchmarking with `@benchmark` run in Jupyter Notebook on 32GB RAM stationary computer.

| Function | Memory allocated | Median of time (ms) |
| --- | --- | --- |
| `test(...)` | 7.63 MiB | 2.399 |
| `test2(...)` | 7.63 MiB | around 2.5 |
| `test3(...)` | 7.63 MiB | around 2.5 |
| `test4(...)` | 7.63 MiB | 2.421 |
| `test5(...)` | 7.63 MiB | around 2.5 |
| `test6!(...)` | 0 bytes | around 2.5 |
| `test7!(...)` | 0 bytes | around 2.5 |
| `test8!(...)` | 32 bytes | 4.029 |

Resultants for benchmarking with `@benchmark` run in Jupyter Notebook on 4GB RAM laptop.

| Function | Memory allocated | Median of time (ms) |
| --- | --- | --- |
| `test(...)` | 7.63 MiB | 4.677 |
| `test2(...)` | 7.63 MiB | 7.199 |
| `test3(...)` | 7.63 MiB | 4.676 |
| `test4(...)` | 7.63 MiB | 5.531 |
| `test5(...)` | 7.63 MiB | 4.768 |
| `test6!(...)` | 0 bytes | 4.682 |
| `test7!(...)` | 0 bytes | 4.936 |
| `test8!(...)` | 32 bytes | 11.588 |

Benchmarks for adding three 1000 x 1000 Float64 matrices in REPL on 32GB RAM stationary computer.

| Function | Memory allocated | Median of time (ms) |
| --- | --- | --- |
| `test(...)` | 7.63 MiB | 2.521 |
| `test2(...)` | 7.63 MiB | 2.529 |
| `test3(...)` | 7.63 MiB | 2.503 |
| `test4(...)` | 7.63 MiB | 2.517 |
| `test5(...)` | 7.63 MiB | 2.529 |
| `test6!(...)` | 0 bytes | 2.517 |
| `test7!(...)` | 0 bytes | 2.529 |
| `test8!(...)` | 32 bytes | 4.355 |

Benchmarks for adding three 1000 x 1000 Float64 matrices in REPL on 4GB RAM laptop.

| Function | Memory allocated | Median of time (ms) |
| --- | --- | --- |
| `test(...)` | 7.63 MiB | 4.878 |
| `test2(...)` | 7.63 MiB | 7.398 |
| `test3(...)` | 7.63 MiB | 4.484 |
| `test4(...)` | 7.63 MiB | 4.493 |
| `test5(...)` | 7.63 MiB | 4.505 |
| `test6!(...)` | 0 bytes | 4.470 |
| `test7!(...)` | 0 bytes | 4.490 |
| `test8!(...)` | 32 bytes | 10.579 |

I can’t understand why method 1 is over two time faster than 8, since I read somewhere that 8 should be faster, because it doesn’t allocate memory. Also I don’t understand why 6 and 7 use 0 bytes of memory and 8 use 32.

---

<div class="post-metadata">

### Author: ![Nosferican](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nosferican/32/9275_2.png) [@Nosferican](https://discourse.julialang.org/u/Nosferican)
#### Post date: [May 16, 2020, 11:14pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/2 "2020-05-16T23:14:34Z")

</div>

A few comments,  
Method 8 uses an anonymous function (not the best) and it does allocate the running sum before sending it to `D`.  
Methods 6 and 7 are non-allocating because you already allocated it before calling the function (a bit of cheating compared to the other methods such as 3 which have the preallocation being included in the timing.  
I don’t think there is a good reason to use anything other than method 1 unless you had already allocated a suitable object which then maybe 6 which can simply reuse it (in-place).  
Just in case, do escape the `$` arguments for the `@benchmark`.

---

<div class="post-metadata">

### Author: ![tbeason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tbeason/32/15898_2.png) [@tbeason](https://discourse.julialang.org/u/tbeason)
#### Post date: [May 16, 2020, 11:27pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/3 "2020-05-16T23:27:54Z")

</div>

If you are going to be doing this addition lots of times, it is likely that an inplace version will be faster. You would allocate the result array just once at the beginning. The actual addition operation is not going to be so different, as you saw.

---

<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: [May 16, 2020, 11:32pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/4 "2020-05-16T23:32:39Z")

</div>

> [@Nosferican](#):
>
> I don’t think there is a good reason to use anything other than method 1

I wouldn’t recommend method 1, initially, because it should allocate intermediate temporary arrays. Method 4 should be better. I don’t know why the benchmarks are so similar, perhaps they’re not set up correctly.

---

<div class="post-metadata">

### Author: ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)
#### Post date: [May 17, 2020, 2:54am UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/5 "2020-05-17T02:54:37Z")

</div>

> [@Nosferican](#):
>
> Method 8 uses an anonymous function (not the best)

Anonymous functions used to be slow, but they have had zero overhead since Julia v0.5. In fact, all of the broadcasting solutions are also using anonymous functions internally, so the presence of an anonymous function cannot be the reason test8 is slower.

As for why `map!` seems slow, I admit I can’t tell. I looked briefly at the code it calls in Base, and I don’t see anything obviously wrong.

---

<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: [May 17, 2020, 3:30am UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/6 "2020-05-17T03:30:03Z")

</div>

> [@KZiemian](#):
>
> | `test7!(...)` | 0 bytes | 4.490 |
> | --- | --- | --- |
> | `test8!(...)` | 32 bytes | 10.579 |

I can’t reproduce this kind of huge difference. Are you remembering to interpolate the global arguments with `$` into the benchmark expressions? Also, it’s more [reliable to report the minimum time](https://arxiv.org/abs/1608.04295) than the median or the mean, since the noise is all positive. `@btime` does the right thing:

```julia
julia> @btime test7!($D,$A,$B,$C);
  1.440 ms (0 allocations: 0 bytes)

julia> @btime test8!($D,$A,$B,$C);
  1.866 ms (1 allocation: 32 bytes)

```

_Update_: the above numbers were accidentally run with Julia 1.0.4. With 1.4 I get a big regression:

```julia
julia> @btime test7!($D,$A,$B,$C);
  1.455 ms (0 allocations: 0 bytes)

julia> @btime test8!($D,$A,$B,$C);
  2.926 ms (1 allocation: 32 bytes)

```

I reported an issue ([50% performance regression in map! · Issue #35914 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/35914)).

---

<div class="post-metadata">

### Author: ![KZiemian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kziemian/32/9020_2.png) [@KZiemian](https://discourse.julialang.org/u/KZiemian)
#### Post date: [May 17, 2020, 1:06pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/7 "2020-05-17T13:06:42Z")

</div>

I was using code from [Optimizing DiffEq Code](https://github.com/SciML/DiffEqTutorials.jl/blob/master/notebook/introduction/03-optimizing_diffeq_code.ipynb), where there is no `$`. All relevant code from this page is below.

```julia
@benchmark test(A,B,C)

@benchmark test2(A,B,C)

@benchmark test3(A,B,C)

@benchmark test4(A,B,C)

@benchmark test5(A,B,C)

@benchmark test6!(D,A,B,C)

@benchmark test7!(D,A,B,C)

@benchmark test8!(D,A,B,C)

@benchmark A*B

@benchmark mul!(D,A,B)

```

If this is wrong way to do that I will start issue about that on tutorials GitHub. (My GitHub is a mess and I can’t make pull request without learning git finally). And maybe other benchmarks all also wrong?

Thank you for information that we should take minimum time and especially for this article. I must read it.

---

<div class="post-metadata">

### Author: ![KZiemian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kziemian/32/9020_2.png) [@KZiemian](https://discourse.julialang.org/u/KZiemian)
#### Post date: [May 17, 2020, 1:59pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/8 "2020-05-17T13:59:45Z")

</div>

Following @Nosferican and @stevengj advice’s I repeat benchmarking using always call with `$` interpolation of global arguments.

```julia
@benchmark fun($A,$B,$C)

```

and reporting _minimum time_ not median time. Again, I use Julia 1.3.1.

Results are only for my 4GB RAM laptop, because due to SARS-Cov-2 I’m cut off my stationary computer.

Below is all code that I use, so maybe someone find mistake in it.

```julia
using BenchmarkTools

A = rand(1000,1000); B = rand(1000,1000); C = rand(1000,1000); D = zeros(1000,1000);

test(A,B,C) = A + B + C

test2(A,B,C) = map((a,b,c)->a+b+c,A,B,C)

function test3(A,B,C)
    D = similar(A)
    @inbounds for i in eachindex(A)
        D[i] = A[i] + B[i] + C[i]
    end
    D
end

test4(A,B,C) = A .+ B .+ C

test5(A,B,C) = @. A + B + C

test6!(D,A,B,C) = D .= A .+ B .+ C

test7!(D,A,B,C) = @. D = A + B + C

test8!(D,A,B,C) = map!((a,b,c)->a+b+c,D,A,B,C)

@benchmark test($A,$B,$C)

@benchmark test2($A,$B,$C)

@benchmark test3($A,$B,$C)

@benchmark test4($A,$B,$C)

@benchmark test5($A,$B,$C)

@benchmark test6!($D,$A,$B,$C)

@benchmark test7!($D,$A,$B,$C)

@benchmark test6!($D,$A,$B,$C)

```

Jupyter Notebook results.

| Function | Memory allocated | Minimum time (ms) |
| --- | --- | --- |
| `test(...)` | 7.63 MiB | 4.463 |
| `test2(...)` | 7.63 MiB | 6.867 |
| `test3(...)` | 7.63 MiB | 4.412 |
| `test4(...)` | 7.63 MiB | 4.418 |
| `test5(...)` | 7.63 MiB | 4.469 |
| `test6!(...)` | 0 bytes | 4.430 |
| `test7!(...)` | 0 bytes | 4.425 |
| `test8!(...)` | 32 bytes | 10.395 |

REPL

| Function | Memory allocated | Minimum time (ms) |
| --- | --- | --- |
| `test(...)` | 7.63 MiB | 4.479 |
| `test2(...)` | 7.63 MiB | 6.939 |
| `test3(...)` | 7.63 MiB | 4.485 |
| `test4(...)` | 7.63 MiB | 4.497 |
| `test5(...)` | 7.63 MiB | 4.491 |
| `test6!(...)` | 0 bytes | 4.451 |
| `test7!(...)` | 0 bytes | 4.477 |
| `test8!(...)` | 32 bytes | 10.403 |

---

<div class="post-metadata">

### Author: ![KZiemian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kziemian/32/9020_2.png) [@KZiemian](https://discourse.julialang.org/u/KZiemian)
#### Post date: [March 20, 2021, 3:37pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/9 "2021-03-20T15:37:51Z")

</div>

I repeated benchmarks for adding 1000 \times 1000 matrices generated by function `rand(1000, 1000)` in Julia 1.5.3. Resultants were produced with the use `@benchmark` run in Jupyter Notebook on 4GB RAM laptop and due to advice from Steven G. Johnson now I write down minimal time as main measure. Median time is stated to make compression with previous results easier.

Original code was taken from [DiffEqTutorials.jl tutorial](https://github.com/SciML/DiffEqTutorials.jl/blob/master/notebook/introduction/03-optimizing_diffeq_code.ipynb).

| Function | Memory allocated | Minimum time (ms) | Median time (ms) |
| --- | --- | --- | --- |
| `test($A, $B, $C)` | 7.63 MiB | around 3.5 | around 3.6 |
| `test2($A, $B, $C)` | 7.63 MiB | around 3.5 | around 3.6 |
| `test3($A, $B, $C)` | 7.63 MiB | around 3.5 | around 3.6 |
| `test4($A, $B, $C)` | 7.63 MiB | around 3.5 | around 3.6 |
| `test5($A, $B, $C)` | 7.63 MiB | around 3.5 | around 3.8 |
| `test6!($D, $A, $B, $C)` | 0 bytes | around 3.5 | around 3.6 |
| `test7!($D, $A, $B, $C)` | 0 bytes | around 3.5 | around 3.6 |
| `test8!($D, $A, $B, $C)` | 0 bytes | around 6.1 | around 6.3 |

We can see easily than Julia 1.5 is over 20% faster than 1.4 (depending on each `test` results vary) and there is almost no difference in performance of the most methods, so one can just write `A + B + C`, which is great!

Only using `map!` give you 74% penalty in time, but compering to benefits this is not a big problem. I hope that Julia 1.6 will solve this problem, but I don’t dare to work with master branch, not now.

Relevant code from the [page](https://github.com/SciML/DiffEqTutorials.jl/blob/master/notebook/introduction/03-optimizing_diffeq_code.ipynb).

```julia
A = rand(1000,1000); B = rand(1000,1000); C = rand(1000,1000); D = zeros(1000,1000);

test(A, B, C) = A + B + C

test2(A, B, C) = map((a, b, c) -> a + b + c, A, B, C)

function test3(A, B, C)
    D = similar(A)
    @inbounds for i in eachindex(A)
        D[i] = A[i] + B[i] + C[i]
    end
    D
end

test4(A, B, C) = A .+ B .+ C

test5(A, B, C) = @. A + B + C

test6!(D, A, B, C) = D .= A .+ B .+ C

test7!(D, A, B, C) = @. D = A + B + C

test8!(D, A, B, C) = map!((a, b, c) -> a + b + c, D, A, B, C)

```

---

<div class="post-metadata">

### Author: ![dpsanders](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpsanders/32/3573_2.png) [@dpsanders](https://discourse.julialang.org/u/dpsanders)
#### Post date: [March 20, 2021, 3:42pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/10 "2021-03-20T15:42:45Z")

</div>

You don’t need to work with the master branch. There is already a release candidate of 1.6 that will probably become the release.

---

<div class="post-metadata">

### Author: ![KZiemian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kziemian/32/9020_2.png) [@KZiemian](https://discourse.julialang.org/u/KZiemian)
#### Post date: [March 22, 2021, 11:42am UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/11 "2021-03-22T11:42:32Z")

</div>

I follow advise of @dpsanders and repeat tests in Julia 1.6.0-rc3. Since I have no experience with having two Julia version on the same machine, on computers strange things just happens, so better be cautious, I make this benchmarks in REPL on 4 GB RAM laptop.

| Function | Memory allocated | Minimum of time (ms) |
| --- | --- | --- |
| `test($A, $B, $C)` | 7.63 MiB | around 3.575 |
| `test2($A, $B, $C)` | 7.63 MiB | around 3.6 |
| `test3($A, $B, $C)` | 7.63 MiB | around 3.6 |
| `test4($A, $B, $C)` | 7.63 MiB | around 3.6 |
| `test5($A, $B, $C)` | 7.63 MiB | around 3.65 |
| `test6!($D, $A, $B, $C)` | 0 bytes | around 3.6 |
| `test7!($D, $A, $B, $C)` | 0 bytes | around 3.7 |
| `test8!($D, $A, $B, $C)` | 0 bytes | around 6.5 |

It looks like there is still a penalty for using `map!`, but hopefully this problem will be resolved soon. I make post about it in GitHub [issue](https://github.com/JuliaLang/julia/issues/35914).

---

<div class="post-metadata">

### Author: ![KZiemian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kziemian/32/9020_2.png) [@KZiemian](https://discourse.julialang.org/u/KZiemian)
#### Post date: [March 26, 2021, 8:17pm UTC](https://discourse.julialang.org/t/which-is-the-most-efficient-way-to-add-matrices-arrays-in-julia-1-x/39602/12 "2021-03-26T20:17:58Z")

</div>

Since official Julia 1.6 is out, I repeated this benchmark using Jupyter Notebook on my 4GB RAM laptop.

| Function | Memory allocated | Minimum time (ms) |
| --- | --- | --- |
| `test($A, $B, $C)` | 7.63 MiB | 3.581 |
| `test2($A, $B, $C)` | 7.63 MiB | 3.586 |
| `test3($A, $B, $C)` | 7.63 MiB | 3.573 |
| `test4($A, $B, $C)` | 7.63 MiB | 3.552 |
| `test5($A, $B, $C)` | 7.63 MiB | 3.602 |
| `test6!($A, $B, $C)` | 0 bytes | 3.582 |
| `test7!($A, $B, $C)` | 0 bytes | 3.604 |
| `test8!($A, $B, $C)` | 0 bytes | 6.363 |

It looks that something is still problem with `map!`, but I can live with that, since `A + B + C` is very fast.

Information about my settings.

```julia
Julia Version 1.6.0
Commit f9720dc2eb (2021-03-24 12:55 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-11.0.1 (ORCJIT, ivybridge)

```
