# Why is this simple function twice as slow as its Python version

**URL:** <https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004>\
**Category:** Performance\
**Tags:** question\
**Created:** [April 10, 2021, 6:58pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004 "2021-04-10T18:58:14Z")\
**Posts on this page:** 20\
**Page:** 2

<div class="post-metadata">

**Author:** ![Semi](https://avatars.discourse-cdn.com/v4/letter/s/a698b9/32.png) [@Semi](https://discourse.julialang.org/u/Semi)\
**Post date:** [April 10, 2021, 8:24pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/21 "2021-04-10T20:24:06Z")

</div>

This is actually slightly (2%) slower.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [April 10, 2021, 8:26pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/22 "2021-04-10T20:26:52Z")

</div>

```julia
julia> using LinearAlgebra, Random, BenchmarkTools

julia> function test(rng, n::Int64, k::Int64, tmp2::Matrix{Float64})
           t = Matrix{Float64}(undef, n, n)
           tmp1 = Matrix{Float64}(undef, n, n)
           for j in 1:k
               t .= rand.(rng) 
               for i in 1:k
                   tmp1 .= rand.(rng)
                   @inbounds mul!(@view(tmp2[(i-1)*n+1:i*n,(j-1)*n+1:j*n]), t, tmp1)
               end
           end
       end
test (generic function with 2 methods)

julia> @btime test(rng, n, k, tmp2) setup=(rng = Random.default_rng(); n = 300; k = 30; tmp = Matrix{Float64}(undef, n * k, n * k));
  949.974 ms (4 allocations: 1.37 MiB)

```

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 8:26pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/23 "2021-04-10T20:26:53Z")

</div>

This strange. Your original function gives for me:

```julia
julia> @btime test($n,$k,$tmp2)
  1.137 s (3660 allocations: 1.23 GiB)

```

Similar to your original timing.

---

<div class="post-metadata">

**Author:** ![Semi](https://avatars.discourse-cdn.com/v4/letter/s/a698b9/32.png) [@Semi](https://discourse.julialang.org/u/Semi)\
**Post date:** [April 10, 2021, 8:28pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/24 "2021-04-10T20:28:44Z")

</div>

Yeah, but I could also have done the same in python/cython.  
As [pdeffebach](https://discourse.julialang.org/u/pdeffebach) wrote, it is perplexing why there is such a difference in performance between vanilla versions of python and julia.

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 8:29pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/25 "2021-04-10T20:29:55Z")

</div>

@inbound doesn’t change much, it’s mul! which does a larger step.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [April 10, 2021, 8:31pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/26 "2021-04-10T20:31:21Z")

</div>

> [@Semi](#):
>
> As [pdeffebach](https://discourse.julialang.org/u/pdeffebach) wrote, it is perplexing why there is such a difference in performance between vanilla versions of python and julia.

Different BLAS library? The bottleneck seems to be the matrix multiplication.

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 8:31pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/27 "2021-04-10T20:31:25Z")

</div>

@giordano gives for me:

```julia
julia> @btime test(rng, n, k, tmp2) setup=(rng = Random.default_rng(); n = 300; k = 30; tmp = Matrix{Float64}(undef, n * k, n * k));
  850.540 ms (4 allocations: 1.37 MiB)

```

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 8:31pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/28 "2021-04-10T20:31:46Z")

</div>

> [@giordano](#):
>
> Different BLAS library? The bottleneck seems to be the matrix multiplication.

Thats what I think too.

---

<div class="post-metadata">

**Author:** ![Semi](https://avatars.discourse-cdn.com/v4/letter/s/a698b9/32.png) [@Semi](https://discourse.julialang.org/u/Semi)\
**Post date:** [April 10, 2021, 8:32pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/29 "2021-04-10T20:32:25Z")

</div>

That was my idea as well.  
But I tried both openblas and mkl, and there is no difference.

As I reached my limit of replies as a newbie, I can’t reply any more for the next 22 hours.

---

<div class="post-metadata">

**Author:** ![dmolina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dmolina/32/5246_2.png) [@dmolina](https://discourse.julialang.org/u/dmolina)\
**Post date:** [April 10, 2021, 8:42pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/30 "2021-04-10T20:42:45Z")

</div>

In that example of code, with .= you can avoid many allocations, because the matrix values are modified and not the complete matrix.

```julia
function test2(n)
           k = 100
           t = rand(n, n)
           tmp1 = rand(n, n) 
           for i in 1:k
               tmp1 .= t .+ t;
           end
           for j in 1:k
               tmp1 .= tmp1.*t;
           end
       end

```

```julia
julia> @btime test(100)
  7.308 ms (404 allocations: 15.43 MiB)
julia> @btime test2(100)
 990.166 μs (4 allocations: 156.41 KiB)

```

I cannot check your original code

```julia
test(n::Int64,k::Int64,tmp2::Array{Float64,2})

```

But maybe that strategy could be useful for you.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [April 10, 2021, 8:46pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/31 "2021-04-10T20:46:28Z")

</div>

> [@dfdx](#):
>
> In Python, it’s an elementwise multiplication, while in Julia it’s matrix-by-matrix multiplication.

In the flux of messages I had missed this one, but this is very likely the explanation of the difference, we were all doing matrix multiplication in Julia, and element-wise multiplication in Python

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 8:48pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/33 "2021-04-10T20:48:12Z")

</div>

The python code was adjusted to do this also with: t@tmp1

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [April 10, 2021, 8:51pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/34 "2021-04-10T20:51:59Z")

</div>

Well, then I don’t understand the question about Python faster than Julia, using the original code (but with matrix multiplication for Python) I get

```julia
julia> @btime test(n, k, tmp2) setup=(n = 300; k = 30; tmp2 = Matrix{Float64}(undef, n * k, n * k));
  1.258 s (3660 allocations: 1.23 GiB)

```

```julia
In [2]: %%timeit
   ...: test(n,k,tmp2)
   ...: 
   ...: 
1.98 s ± 56.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

```

---

<div class="post-metadata">

**Author:** ![dmolina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dmolina/32/5246_2.png) [@dmolina](https://discourse.julialang.org/u/dmolina)\
**Post date:** [April 10, 2021, 8:58pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/35 "2021-04-10T20:58:26Z")

</div>

I think you are right:

```julia
function test2(n::Int64,k::Int64,tmp2::Array{Float64,2})
           for i in 1:k
               t = rand(n, n); tmp1 = similar(t) 
               for j in 1:k
                   rand!(tmp1)
                   tmp2[(i-1)*n+1:i*n,(j-1)*n+1:j*n] = t.*tmp1
               end
           end
       end;

```

```julia
julia> @time test(n, k, tmp2)
 1.625298 seconds (3.66 k allocations: 1.227 GiB, 0.60% gc time)
julia> @time test2(n,k,tmp2)
  0.325837 seconds (1.92 k allocations: 659.253 MiB, 0.62% gc time)

```

I only have changed the matrix to broadcast multiplication and use rand! to avoid so many allocations.

@Semi, that version is faster than python version, didn’t it?

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 9:01pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/36 "2021-04-10T21:01:18Z")

</div>

At OPs side it is a MacBook pro running Julia 1.4, which also seems to make a difference here.

I can only check the Julia code, and for it, it went down from

```julia
julia> @btime test($n,$k,$tmp2)
  1.137 s (3660 allocations: 1.23 GiB)

```

in the original Julia code to

```julia
@btime test($n,$k,$tmp2)
  809.509 ms (1862 allocations: 639.34 MiB)

```

using `mul!` and `@inbounds`.

Running in Julia 1.5.3 is significantly slower but performance gain using `mul!` is similar.

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 9:03pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/37 "2021-04-10T21:03:19Z")

</div>

Please read the complete thread.  
We are comparing matrix multiplication:  
Julia: t\*tmp1  
Python: t@tmp1  
which OP has changed during discussion.  
Maybe OP could edit his original post?

---

<div class="post-metadata">

**Author:** ![dmolina](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dmolina/32/5246_2.png) [@dmolina](https://discourse.julialang.org/u/dmolina)\
**Post date:** [April 10, 2021, 9:10pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/38 "2021-04-10T21:10:56Z")

</div>

Yes, you are right. I have read the first email, and I did not see the change in the multiplication in the email. I think your proposal (using mul!) is the best option:

```julia
function test2(n::Int64,k::Int64,tmp2::Array{Float64,2})
           for i in 1:k
               t = rand(n, n); tmp1 = similar(t) 
               for j in 1:k
                   rand!(tmp1)
                   @views @inbounds mul!(tmp2[(i-1)*n+1:i*n,(j-1)*n+1:j*n], t, tmp1)
               end
           end
       end

```

Comparing both versions in my slow laptop (with Julia 1.6):

```julia
# Original code
julia> @time test(n,k,tmp2)
  1.806398 seconds (3.66 k allocations: 1.227 GiB, 1.27% gc time)
# New version
julia> @time test2(n,k,tmp2)
  1.453730 seconds (120 allocations: 41.203 MiB, 0.11% gc time)

```

It takes 33% less time than original (and a lot less memory).

**Update:** My laptop is clearly slower than I though, I need another one 🙂 .

---

<div class="post-metadata">

**Author:** ![oheil](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oheil/32/220745_2.png) [@oheil](https://discourse.julialang.org/u/oheil)\
**Post date:** [April 10, 2021, 9:22pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/39 "2021-04-10T21:22:44Z")

</div>

```julia
julia> @btime test2(n,k,tmp2)
  663.240 ms (120 allocations: 41.20 MiB)

```

@dmolina 's version is best for me

---

<div class="post-metadata">

**Author:** ![danielw2904](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielw2904/32/10890_2.png) [@danielw2904](https://discourse.julialang.org/u/danielw2904)\
**Post date:** [April 10, 2021, 10:05pm UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/40 "2021-04-10T22:05:14Z")

</div>

I cannot reproduce this at all:

```julia
%timeit test(n, k, tmp2)
3.27 s ± 7.21 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

```

Original version in jl.

```julia
@btime test(n, k, tmp2)
  1.648 s (3660 allocations: 1.23 GiB)

```

Version similar to @dmolina

```julia
julia> function test(n::Int64,k::Int64,tmp2::Array{Float64,2})
                  tmp1=Matrix{Float64}(undef,n,n);t=Matrix{Float64}(undef,n,n);
                  for j in 1:k
                      Random.rand!(t) 
                      for i in 1:k
                          Random.rand!(tmp1)
                          mul!(@inbounds(@view(tmp2[(i-1)*n+1:i*n,(j-1)*n+1:j*n])), t, tmp1)
                      end
                  end
              end;

julia> @btime test(n, k, tmp2)
  1.046 s (4 allocations: 1.37 MiB)

```

All with `n=300;k=30`  
EDIT: No idea how to optimize the python version

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [April 11, 2021, 12:34am UTC](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004/41 "2021-04-11T00:34:58Z")

</div>

> [@danielw2904](#):
>
> I cannot reproduce this at all:

I cannot see that difference here either. But I noticed that

1. both are using multi-threading. But Julia is launching 8 threads, while Python is launching 4. My laptop has 4 physical cores. Maybe that explains differences in some system?

2. The times measured fluctuate in about 30% in both cases. Here I can get something between 1.2 and 0.9 seconds for either of them (using `%timeit` or `@btime`).

(I’m using Julia 1.6).

> **Codes**
>
> ```python
> import numpy
> from numpy import zeros
> from numpy.random import rand
> def test(n,k,tmp2):
> for i in range(k):
> t = rand(n, n) 
> for j in range(k):
> tmp1 = rand(n, n)
> tmp2[i*n:(i+1)*n,j*n:(j+1)*n] = t@tmp1
> n = 300
> k = 30
> tmp2=zeros((n*k,n*k))
> %timeit test(n, k, tmp2)
> # 1.18 s ± 78.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
> 
> ```
> 
> ```julia
> julia> function test(n::Int64,k::Int64,tmp2::Array{Float64,2})
> for i in 1:k
> t = rand(n, n) 
> for j in 1:k
> tmp1 = rand(n, n)
> tmp2[(i-1)*n+1:i*n,(j-1)*n+1:j*n] = t*tmp1
> end
> end
> end;
> 
> julia> n = 300
> 300
> 
> julia> k = 30
> 30
> 
> julia> tmp2=zeros((n*k,n*k));
> 
> julia> using BenchmarkTools
> 
> julia> @btime test($n,$k,$tmp2)
> 1.016 s (3660 allocations: 1.23 GiB)
> 
> ```

[Previous page](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004.md?page=1)

[Next page](https://discourse.julialang.org/t/why-is-this-simple-function-twice-as-slow-as-its-python-version/59004.md?page=3)
