# Runtime (memory) on M1 Macbooks: something is not right

**URL:** <https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721>\
**Category:** Performance\
**Tags:** mac-m1\
**Created:** [January 16, 2022, 10:26pm UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721 "2022-01-16T22:26:39Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![Schneeschaufel](https://avatars.discourse-cdn.com/v4/letter/s/a87d85/32.png) [@Schneeschaufel](https://discourse.julialang.org/u/Schneeschaufel)\
**Post date:** [January 16, 2022, 10:26pm UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/1 "2022-01-16T22:26:39Z")

</div>

The following link: [Trying to understand memory usage](https://discourse.julialang.org/t/trying-to-understand-memory-usage/25316)

If I do this (see link above) on my Macbook 13" with M1 processor and 8GB RAM and SSD (Julia Rosetta Version 1.7.1 (2021-12-22)):

julia\> using LinearAlgebra  
julia\> using BenchmarkTools

julia\> function func1()  
a = rand(100,1000)  
return norm(a,2)  
end

julia\> @benchmark begin  
for i in 1:10000  
func1()  
end  
end

BenchmarkTools.Trial: 1 sample with 1 evaluation.  
Single result which took **92.445 s** (0.04% GC) to evaluate,  
with a memory estimate of 7.45 GiB, over 20000 allocations.

_Something is not right here_. A runtime of 92 seconds (the poster in the original thread, see link above, had it back in 4 seconds).

My Python version takes (time python test.py): 5.41s user 0.05s system 99% cpu 5.493 total

Even if Python does not (which I don’t know just speculation) copy a new array every time it advances func1() in the loop there is no reason why Julia is so slow (note: I haven’t checked if python does the same as the Julia code 1-based indexing):

import numpy as np

def func1():  
a = np.random.rand(100,1000)  
#return np.linalg.norm(a, axis=0)  
return np.linalg.norm(a, axis=1)

def benchmark():  
for i in range(0,10000):  
tmp = func1()  
#print statement won’t make a difference in terms of timing  
#print('i ', i, np.sum(tmp))  
return ‘okay’

print(benchmark())

Edit: My Python is Python 3.8.12, [Clang 10.0.0] :: Anaconda, Inc. on darwin

---

<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:** [January 17, 2022, 8:57am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/2 "2022-01-17T08:57:15Z")

</div>

I can’t say much about any M1/Julia issues but one general thing which catches my eye is that you don’t have to loop for benchmarks. To use @benchmark in Julia you just do

```julia
@benchmark func1()

```

You can set the number of samples to take for benchmarking with

```julia
BenchmarkTools.DEFAULT_PARAMETERS.samples = 10000

```

but 10000 is already the default value.

This is what I get:

```julia
julia> @benchmark func1()
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 97.700 μs … 7.997 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 324.900 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 384.368 μs ± 419.740 μs ┊ GC (mean ± σ): 12.67% ± 11.68%

```

Comparing it with a proper python benchmark:

```julia
import numpy as np

def func1():
    a = np.random.rand(100,1000)
    return np.linalg.norm(a, axis=1)

import timeit
num_runs = 10000
duration = timeit.Timer(func1).timeit(number = num_runs)
avg_duration = duration/num_runs
print(f'On average it took {avg_duration} seconds')

On average it took 0.0010858759800000002 seconds

```

Which yields 3x faster python/numpy. However, this seems to be more comparable, perhaps you can check out on your M1 system?

---

<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:** [January 17, 2022, 9:18am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/3 "2022-01-17T09:18:00Z")

</div>

Also, your benchmark is vastly dominated by the generation of random numbers. If you’re interested in benchmarking the `norm` function, just do that:

```julia
julia> using BenchmarkTools, LinearAlgebra

julia> function func1()
           a = rand(100,1000)
           return norm(a,2)
       end
func1 (generic function with 1 method)

julia> @benchmark func1()
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 138.996 μs … 848.082 μs ┊ GC (min … max): 0.00% … 53.99%
 Time (median): 140.990 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 151.041 μs ± 44.092 μs ┊ GC (mean ± σ): 3.20% ± 8.53%

  █▅▄▂▂▁ ▁
  ████████▇▇▇▇▇▇██▇▆▆▅▅▃▁▁▄▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▆▇▆▇▇▇ █
  139 μs Histogram: log(frequency) by time 412 μs <

 Memory estimate: 781.30 KiB, allocs estimate: 2.

julia> @benchmark norm(a, 2) setup=(a = rand(100, 1000))
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 34.553 μs … 105.522 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 35.873 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 37.324 μs ± 4.416 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▁▇██▆▅▅▄▃▃▃▂▂▂▁▁▁ ▂
  █████████████████████▇█▇▇▇▇▆▆▅▆▆▇▄▆▆▅▅▅▆▆▅▆▆▅▅▅▅▅▆▆▅▆▆▅▅▅▅▅▄ █
  34.6 μs Histogram: log(frequency) by time 59 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

---

<div class="post-metadata">

**Author:** ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)\
**Post date:** [January 17, 2022, 9:42am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/4 "2022-01-17T09:42:41Z")

</div>

It seems both of these answers - while providing sound advice - are slightly missing the point of OP. They are running a benchmark which, irrespective of the merits of its exact implementation, should take on the order of 5 seconds (indeed I can confirm that on my system I see min 4.3, median 4.7 seconds) but takes twenty times as long on their M1 Rosetta system.

It would be helpful if anyone could try this on M1 Rosetta to see if it is a problem specific to that setup. (@giordano I was under the impression you had an M1?)

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [January 17, 2022, 9:54am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/5 "2022-01-17T09:54:44Z")

</div>

~~Yes, as a way to time `norm` this is awful, but as some kind of memory stress-test… I guess it runs into some limitation of Rosetta?~~ No, see below. On an M1 with 16GB of memory:

```julia
julia> @time for i in 1:10_000
           func1()
       end
  4.307090 seconds (20.00 k allocations: 7.451 GiB, 13.03% gc time) # native
150.006328 seconds (30.00 k allocations: 7.451 GiB, 0.09% gc time) # rosetta

```

Maybe one run isn’t so different?

```julia
julia> @btime func1();
  min 313.833 μs, mean 397.210 μs (2 allocations, 781.30 KiB) # native
  min 14.915 ms, mean 15.115 ms (2 allocations, 781.30 KiB) # rosetta

julia> 15.115 / 0.397
38.07304785894206

julia> function func2()
       a = fill(pi/2,100,1000) # instead of random numbers
       return norm(a,2)
       end
func2 (generic function with 1 method)

julia> @btime func2();
  min 218.042 μs, mean 331.412 μs (2 allocations, 781.30 KiB)
  min 13.020 ms, mean 13.299 ms (2 allocations, 781.30 KiB)

```

---

<div class="post-metadata">

**Author:** ![Schneeschaufel](https://avatars.discourse-cdn.com/v4/letter/s/a87d85/32.png) [@Schneeschaufel](https://discourse.julialang.org/u/Schneeschaufel)\
**Post date:** [January 17, 2022, 10:03am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/6 "2022-01-17T10:03:18Z")

</div>

Will have more time later. But even reducing the loop counter from 1e4 to 1e3 it takes just too long:

@benchmark begin  
for i in 1:1000  
func1()  
end  
end

BenchmarkTools.Trial: 1 sample with 1 evaluation.  
Single result which took 9.309 s (0.12% GC) to evaluate,  
with a memory estimate of 762.99 MiB, over 2000 allocations.

Actually I am not interested in micro benchmarks but the above doesn’t seem right. I really wonder if something is interfering with my Julia installation. Watching ‘Activity Monitor’ while the above little program runs I cannot see that Julia consumers a lot of memory or putting the systems to a standstill on my 8GB RAM and SSD powered little Macbook.

My OSX: macOS Big Sur 11.6.1

---

<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:** [January 17, 2022, 10:11am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/7 "2022-01-17T10:11:23Z")

</div>

x86-64 Julia 1.6:

```julia
julia> using LinearAlgebra, BenchmarkTools

julia> @benchmark norm(a, 2) setup=(a = rand(100, 100))
BenchmarkTools.Trial: 5057 samples with 1 evaluation.
 Range (min … max): 910.791 μs … 5.149 ms ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 946.209 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 969.911 μs ± 142.010 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  █▇▅▅▆▆▅▅▅▄▃▂▂▂▁▁▁▁▁ ▂
  ██████████████████████▇▆▆▇▇▆▆▅▅▆▆▆▅▅▅▆▅▇▅▅▁▃▄▅▃▄▄▅▁▅▁▃▃▄▄▁▄▅▃ █
  911 μs Histogram: log(frequency) by time 1.44 ms <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> versioninfo()
Julia Version 1.6.4
Commit 35f0c911f4 (2021-11-19 03:54 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin19.5.0)
  CPU: Apple M1
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-11.0.1 (ORCJIT, westmere)

```

aarch64 Julia 1.7:

```julia
julia> using LinearAlgebra, BenchmarkTools

julia> @benchmark norm(a, 2) setup=(a = rand(100, 100))
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 12.375 μs … 52.667 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 12.667 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 13.038 μs ± 1.833 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▄█▄ ▆▆▄▁▁▅▄▂▂ ▁ ▁ ▁ ▂
  ███▃▇████████████████▇▆▄▁▁▄▁▃▁▃▁▃▁▁▁▃▁▁▁▃▃▁▁▁▄▁▁▃▃▄▃▃▁▃▄▃▁▃ █
  12.4 μs Histogram: log(frequency) by time 18.2 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> versioninfo()
Julia Version 1.7.0
Commit 3bf9d17731 (2021-11-30 12:12 UTC)
Platform Info:
  OS: macOS (arm64-apple-darwin21.1.0)
  CPU: Apple M1
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-12.0.1 (ORCJIT, cyclone)

```

When you benchmark only `norm` and not the generation of random numbers, the ratio is closer to 75 than 40. Yes, `norm` seems to be particularly bad under Rosetta (sorry, I don’t have the same version of Julia for both architectures)

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [January 17, 2022, 10:20am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/8 "2022-01-17T10:20:50Z")

</div>

The generic fallback one is less affected than the BLAS version:

```julia
julia> @btime norm(a, 2) setup=(a = rand(100, 100));
  min 20.333 μs, mean 20.716 μs (0 allocations) # native, 1.8
  min 1.483 ms, mean 1.491 ms (0 allocations) # rosetta, 1.7 + openblas

julia> @btime LinearAlgebra.generic_norm2(a) setup=(a = rand(100, 100));
  min 25.708 μs, mean 26.305 μs (0 allocations) # native
  min 71.167 μs, mean 71.660 μs (0 allocations) # rosetta

```

---

<div class="post-metadata">

**Author:** ![Schneeschaufel](https://avatars.discourse-cdn.com/v4/letter/s/a87d85/32.png) [@Schneeschaufel](https://discourse.julialang.org/u/Schneeschaufel)\
**Post date:** [January 17, 2022, 10:42am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/9 "2022-01-17T10:42:07Z")

</div>

If I use norm2 it will do the following and reduce the time significantly for 1e4 iterations in the loop:

julia\> @benchmark begin  
for i in 1:10000  
func1()  
end  
end  
BenchmarkTools.Trial: 1 sample with 1 evaluation.  
**Single result which took 5.356** s (0.95% GC) to evaluate,  
with a memory estimate of 7.45 GiB, over 20000 allocations.

Just for info and if someone knows the answer: ‘norm2’ is different to ‘norm’ because?

norm2 doesn’t accept the axis option: **ERROR:** MethodError: no method matching generic\_norm2(::Matrix{Float64}, ::Int64)

Maybe I know the answer: norm2 is meant to calc the norm for axis=2.

Thanks

---

<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:** [January 17, 2022, 11:19am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/10 "2022-01-17T11:19:31Z")

</div>

What axis argument? The second argument of [`norm`](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#LinearAlgebra.norm) is the order of the norm (2 by default, BTW)

---

<div class="post-metadata">

**Author:** ![Schneeschaufel](https://avatars.discourse-cdn.com/v4/letter/s/a87d85/32.png) [@Schneeschaufel](https://discourse.julialang.org/u/Schneeschaufel)\
**Post date:** [January 17, 2022, 11:38am UTC](https://discourse.julialang.org/t/runtime-memory-on-m1-macbooks-something-is-not-right/74721/11 "2022-01-17T11:38:05Z")

</div>

I thought it is the axis argument (the Python also lets you specify the axis over which you want the norm).

But in my original code changing it to order 2:

def func1():  
a = np.random.rand(100,1000)  
return np.linalg.norm(a, 2)

also increases the runtime a lot to:

python test.py 555.32s user 102.05s system 725% cpu 1:30.58 total

This is the problem with those implicit arguments and converting code.

**Can anyone please confirm that np.linag.norm(a,2) does the same as norm2(a) or norm(a,2) for that matter.**
