LoginSignup
4
1

More than 1 year has passed since last update.

M1 Mac も速くないことがある

Last updated at Posted at 2022-02-01

これは何?

先日まで Mid 2015 の 15 inch MacBook Pro (Core i7 クアッド / 2.2 GHz) を使っていた。
先日 MacBook Pro 14 inch (M1 非Max) を手に入れたんだけど、あんまり速くないなと思うことがあったので、今日も楽しいマイクロベンチマーク。

計算内容

ruby で書くと短くていいね。

ruby
N=10000
r=(1..N).max_by{ |x| ((N-x)**x/7) % 6074001001 }
p r

こういう内容。なんの意味もない。

出力は

8663

となれば正解。

これを、go, java, c++ with boost (clang, gcc), ruby, python3, node で試した。

以降、グラフで出てくる "m1", "rosetta", "amd64" の意味は下表のとおり。

記号 実行ハードウェア バイナリ
m1 MacBook Pro 14 inch (M1 非Max) arm64
rosetta MacBook Pro 14 inch (M1 非Max) x86_64
amd64 MacBook Pro (Core i7, Mid 2015) x86_64

コンパイルするチーム

go, java, c++ with boost (clang, g++-11)。

各コンパイラは下記の通り

  • go version go1.17.5 darwin/arm64
  • openjdk 17.0.1 2021-10-19 LTS
  • Apple clang version 13.0.0 (clang-1300.0.29.30)
  • g++-11 (Homebrew GCC 11.2.0_3) 11.2.0

java と clang の rosetta はサボった。
結果は下記の通り。 time コマンドの real の値を出しているので棒が短いほど速い。

ちなみに、 real なのは並列実行を優遇するため。実際、 Java は user が real の 1.5倍ぐらいある。

結果は下図。

go_java_clang_gcc.png

目盛りを見ると分かる通り、 go が速い。意外と clang が M1 を使いこなせてない感じ。
全体的にはまあそうだよねという結果だと思う。

コンパイルしないチーム

続いて、 ruby, python3, node。

各環境は下記の通り

  • ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21] / for m1
  • ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-darwin21] / for rosetta
  • ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-darwin21] / for amd64
  • Python 3.9.10 (main, Jan 15 2022, 11:40:53) / for m1
  • Python 3.9.10 (main, Jan 15 2022, 11:48:04) / for rosetta
  • Python 3.9.10 (main, Jan 15 2022, 11:48:04) / for amd64
  • node v17.3.0 / for m1 and rosetta
  • node v17.3.1 / for amd64

なんか ruby と node のバージョンが合ってないけど気にしない。

結果は下図。

ruby_python3_node.png

こちらはわりと思いがけなかった。

node はまあまあそうだよねという内容。m1 と amd64 の差はもうちょっとあってもいいかなと思うけど。
python3 は、三者ほぼ同タイム。

そして ruby は m1 が一番遅いという意外な展開。よく見てみると、m1 が遅いというより、amd64 が速すぎる。amd64 の中では go と並んでほぼ最速。m1 が遅いと書いたもののそれは ruby 内の比較の話。m1 内での比較だと Java と同等、clang より速い。node には負けるけど。

ruby や python で m1 がふるわないのは、おそらく、x86_64 バイナリは SSEとかをたっぷり使っていて、ARM64 バイナリは NEONとかを使いこなせてないんだろうと想像する。調べてないので想像するだけ。

誤解なきよう

ここでやっているのはマイクロベンチマーク。多倍長整数の特定の計算だけしかしていない。
「ruby は M1 でも遅いのか」という感想を持つべきではなくて「ruby は多倍長整数計算では M1 でも遅いことがあるのか」という感想が正解。

実際。
多倍長整数ではない計算をすると、私が試した範囲では全部、M1 は rosetta に圧勝する。

時間測定に使ったコード
ruby
# hash

require "json"

$ix=0
def foo(x)
    return { ($ix+=1).to_i=>($ix+=1).to_s } if x==0
    foo(x-1).merge(foo(x-1)) 
end

p foo((ARGV[0]||21).to_i).size
ruby
# json

require "json"

def foo(x)
    x.times.with_object({}){ |e,o| o[e] = JSON.parse(foo(x-1)) }.to_json
end

p foo((ARGV[0]||9).to_i).size
ruby
# eval long text

def foo(s, n)
    return eval(s) if n==0
    foo("(#{s})*2+(#{s})", n-1)

end

p foo("1", (ARGV[0]||21).to_i)
ruby
# many delete_at

def foo(n)
    a=[*1..n]
    (1..).each do |ix|
        return a[0] if a.size==1
        a.delete_at(ix % a.size)
    end
end

p foo((ARGV[0]||200000).to_i)
ruby
# float calc

def foo(n)
    n.downto(1).sum{ |e|
        x=e.to_f
        (x+1)/(x**(x**0.1))
    }
end

p foo((ARGV[0]||10000000).to_i)
ruby
# regexp

def foo(n)
    s = "___"+(1..n).map{ |x| "o"*x }.join("___")+"___"
    s.scan(/(_(o(o+))(\2+)_)/).sum{ |e| e[0].size-2}
end

p foo((ARGV[0]||4096).to_i)
ruby
# deep flatten

def foo(n)
    return [1] if n<1
    [foo(n-1)*n]*n
end

p foo(ARGV[0] || 7).flatten.sum
ruby
# recursive fibo

def foo(n)
    return n if n<2
    foo(n-1) + foo(n-2)
end

p foo(ARGV[0] || 37)

上記の折りたたんであるコードを実行すると、下図のとおり、 M1 が勝つ。
まあそりゃそうだ、という話。

result.png
result.png

まとめ

M1 ネイティブでも rosetta 2 より遅いこともあるよ。
とはいえ。ほとんどの場合は M1 ネイティブは速いし、今遅いものもそのうち早くなるんじゃないかと思うよ(思うだけ)。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1