M1搭載MacBook Airが届いたのでJavaやDockerなどいろいろベンチマークした

M1 MacBook Airが届いていろいろやってたら年も明けてだいぶたったけども、ビルド速度とかJavaとかDockerとかTensorFlowとか、技術者が気になるベンチマークを試してたので、まとめました。

MacBook Airを買ってしまった

なんかM1 Mac解説動画をとるためにいろいろ調べていたら、悪質サイトのリンクを踏んだみたいで、MacBook Airを買ってしまっていた。 その悪質サイトは最初は7万円台ですよーっていっておいて、結局12万円くらいになっていた。
みんなもapple.comってサイトには注意しましょうね。
www.youtube.com

とどいた!

12/12到着予定といいつつ11日になっても羽田から動いてなかったので大丈夫かーと思ったら11日深夜というか12日未明というかそのあたりには福岡に届いてて、朝発想されて夜にとどいた。
f:id:nowokay:20201214123216p:plain

でこれだ!
f:id:nowokay:20201215131717p:plain

ベンチマーク

GeekBenchも前評判どおりの値がでました。 f:id:nowokay:20201215121055p:plain https://browser.geekbench.com/v5/cpu/5337092

GeekbenchはUniversalAppになってるようで、アプリケーションの情報から「Rosettaを使用して開く」にチェックを入れるとx86版を動かすことができます。
f:id:nowokay:20201215120555p:plain:w300

プロセッサがVirtualAppleになっています。
f:id:nowokay:20201215120434p:plain

実行すると、Arm版を動かすのとほとんど同じ値が出ていますね。
f:id:nowokay:20201215120336p:plain
https://browser.geekbench.com/v5/cpu/5337021

あと、7世代8コアi7(i7-7820X)が載ってるWindowsと7世代4コアi7(i7-7700HQ)が載ってるMacBook Pro 2017でベンチマークをとっています。

M1 VirtualApple i7-7820X i7-7700HQ
Single-Core 1735 1721 1023 767
Multi-Core 7345 7263 7606 2901

みてわかるのは、シングルスレッド性能は圧倒的に7世代i7たちをしのいでいますね。8コアi7はかろうじてマルチコア性能で勝ってますが、MacBook Pro 2017は完全に圧倒しています。これがエントリモデルであるMacBook Airていうところがすごいです。

f:id:nowokay:20201222100712p:plain

GeekbenchはGPUの計測もできるので比較してみます。 。

M7 RTX2070 Super Radeon Pro 555 Intel HD Graphic 630
16692 94251 13165 4629

さすがにRTX 2070 Superにはぜんぜんかないませんが、MacBook Pro 2017にのってるRadeon Pro 555よりかなりパフォーマンスがいいですね。CPU内蔵のHD Graphicよりはかなりいいです。これがCPU内蔵のGPUってところがすごい

f:id:nowokay:20201222101536p:plain

Javaのインストール

それでは、Javaを動かしてみます。
現在、JavaのArm Mac対応はJEP 391として進められていますが、3月リリースのJava 16には取り込まれず、おそらく9月リリースのJava 17に入ることになると思います。
とはいえAzul SystemsがArm Mac対応のJavaを出しているので、これを使って試してみます。

あと、x86Javaも試してみようと思って、まずはzip版を落としてみるとCPUが違うといわれるました。このメッセージはキャプチャしそこねた。。。
Rosettaインストール後は警告が出ます。 f:id:nowokay:20201213140056p:plain:w300

で、dmgをインストールしてみたのだけど、ここではAdoptOpenJDKJava 11を使いました。インストールするときにRosettaのインストール確認が出たのだけど、これもキャプチャしそこね。もったいない。

NetBeansを動かす

NetBeans 12.2がArm Macに対応したということで、試してみました。 最初はZulu 16eaで動かしたんだけど、すぐ落ちていたので、Zulu 11だと安定しました。 f:id:nowokay:20210120020111p:plain

x86 Java on RosettaでもNetBeans自体は動きました。 f:id:nowokay:20201213141221p:plain
ところでRosettaで動かすとOSバージョンが10.16になってるの気になる。Arm版だと11.1です。

Arm JavaNetBeansを動かしてもx86 Javaで動かしても、Arm JavaMavenプロジェクトを動かすのは問題ないんだけど、x86 JavaMavenプロジェクトを動かすとなんか怒られます。けどここでキャンセルするとビルドは行われる。
f:id:nowokay:20201213141340p:plain:w300

Gradleプロジェクトであれば、Arm Javaで動かしてもx86 Javaで動かしてもすんなりいけました。

レイトレでベンチマーク

いつもなんだかんだレイトレでベンチマークを取ってるので、ここでもやってみます。
SmallPTをJavaで書きなおしたものです。
https://github.com/kishida/smallpt4j/blob/original/src/main/java/naoki/smallpt/SmallPT.java

こんな感じの絵が出力されます。(ちゃんと時間をかければきれいになる)
f:id:nowokay:20201215130151p:plain

それぞれ計ってみると、こんな感じになりました。単位は秒。

M1 VirtualApple i7-7800X i7-7700HQ
9.3 10.7 5.9 14.6

実行時間なので、低いほど性能が高いです。
ほぼメモリアクセスなしの計算のみの処理で、ベンチマークよりはRosetta経由で性能劣化してるけど、CPUエミュレーションしてると考えればほぼ遜色ない性能といえると思います。コア数の差でi7-7800Xが有利ですが、MacBook Pro 2017のi7-7700HQよりはかなり速いですね。

f:id:nowokay:20201222101608p:plain

メモリアクセスがあったほうがいいかとテクスチャありでも比較してみました。
https://github.com/kishida/smallpt4j
f:id:nowokay:20201215125621p:plain

結果はこんな感じで、比率としてはほぼかわらずです。

M1 VirtualApple i7-7800X i7-7700HQ
25.2 26.2 14.9 36.5

まあテクスチャ使うといっても、ほぼキャッシュに載ってしまってるのかなという感じでした。

f:id:nowokay:20201222102329p:plain

Vector API

ついでに、Java 16ではSIMD命令をサポートするVector APIが取り込まれているので試してみます。x86であればAVX命令ですけど、ArmだとNEONですかね。
M1 Macでも使えて、doubleであれば2つの値を同時に計算できます。128bit幅ということですね。

f:id:nowokay:20201215122456p:plain

レイトレでは3次元のベクトルを処理するために3つの値を同時に計算したいので、float x4にしてみたのですけど、精度不足のせいか変な絵になりました。
f:id:nowokay:20201215122610p:plain

参考までに、処理時間としては12.6秒かかっているので、Vector APIを使わない場合の9.3秒と比べれば遅くなっています。これはx86のときも同様なので、まだJITの性能が出てないのかなという感じです。

Spring Boot

さて、もっと実用的なものということでSpring Bootを動かしてみます。
Spring InitializerでSpring Webを含めて作ったプロジェクトに簡単なコントローラを追加して起動時間を計っています。

@RestController
@RequestMapping("/url")
public class NewRestController {
    @GetMapping
    String hello() {
        return String.format("Hello %s on %s",
                System.getProperty("java.vm.name"),
                System.getProperty("os.name"));
    }
}

Arm JDKだと起動に5.6秒かかってます。
f:id:nowokay:20201214005927p:plain

x86 JDK on Rosettaだと7秒
f:id:nowokay:20201214005659p:plain

i7と比較してみると、やたら遅い感じです。

M1 VirtualApple i7-7800X i7-7700HQ
5.6 7.1 1.5 1.5

Rosettaでも遅くなってるというのはありますが、MacBook Pro 2017と比べてもかなり遅くなっています。
f:id:nowokay:20201222104112p:plain

見ているとAttaching agents: []のところで5秒かかっている感じです。なんらかセキュリティ機能でひっかかってるんでしょうか。
f:id:nowokay:20201214091711p:plain:w400

こちらでDockerで動かしたものと比較していますが、Dockerで動かした場合にはかなり起動が速くなっているので、プロセッサの問題ではなく、なんらかOSの問題でひっかかってるんではないかと思います。
Jibで作ったx86イメージがM1 Macで不安定。あとM1 BigSurでSpring Bootの起動が遅い - きしだのHatena

Kafkaのビルド

なんかまとまったソースのビルドのビルドを試そうと思って、ScalaのソースがいいなとKafkaをビルドしてみました。
すでに2.7.0がリリースされてるけど、試したのは2.6.0です。
https://kafka.apache.org/downloads

./gradlew releaseTarGzしています。
f:id:nowokay:20210121215907p:plain

Rosetta経由だと表示が違うのは なんなんだろう? f:id:nowokay:20210121215421p:plain

--scanをつけて各タスクの時間を見るとこんな感じ。
こちらはM1 Mac
f:id:nowokay:20201229204122p:plain

こっちがi7-7800X Windows。
f:id:nowokay:20201229203751p:plain

結果をまとめるとこんな感じ。

M1 MacBook Air M1 Rosetta i7-7800X Windows i7-7700HQ MBP 2017
1m27s 3m39s 2m59s 3m56s

M1Macはかなり速く、8コア i7の倍速以上になっていますね。コードのビルドは一般にマルチスレッド化しづらい処理なので、シングルコア性能が出ているんではないかと思います。
一方でRosettaはかなり遅く2.5倍の差がついていますね。レイトレではほとんど差がなかったので、シングルスレッドだとJIT結果が共有できなくて遅いとかディスクアクセスなどが弱いとかなんでしょうか。とはいえ、MacBook Pro 2017より速いので、実用上十分といえるのかもしれません。
f:id:nowokay:20210114213022p:plain

Docker

DockerのM1 Mac対応はTech Previewとして開発されています。
https://docs.docker.com/docker-for-mac/apple-m1/

MacのDockerではx86イメージもArmイメージも動かせます。 x86 Macの場合ArmイメージはQEMUで、M1 Macの場合はx86イメージをQEMUで動かします。

ということで先ほどのレイトレをDockerで動かしてみます。
Dockerfileはこんな感じ
https://gist.github.com/kishida/0842ce696fc58b9809405d6714e0d7c9

JDKはGraalVMを使います。
https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-20.3.0

比較してみるとこんな感じ

M1 QEMU/Rosetta i7-7800X i7-7700HQ
Docker 9.4 3M32.3 7.3 18.8
Host 9.3 10.7 5.9 14.6

まあなんか、ホスト側とほとんどかわらないですけど、x86イメージをQEMU経由で動かしたものはかなり遅いです。
OSのコントロールできるRosettaと違って、OSごとのエミュレートだと不利なんだろうなという感じですね。複雑な処理が必要なものはあまり動かさないほうがよさそう。

f:id:nowokay:20210114214459p:plain

Native Imageビルド

GraalVMにはJavaのコードをネイティブバイナリにコンパイルする機能があります。
せっかくGraalVMを使うのでNative Imageのビルドについても見てみます。

native-imageコマンドで、先ほどのレイトレーシングをネイティブ化してみます。

root@f9268a585b9a:/src# native-image SmallPT
[smallpt:235]    classlist:     949.93 ms,  0.96 GB
[smallpt:235]        (cap):     300.72 ms,  0.96 GB
[smallpt:235]        setup:     951.14 ms,  0.96 GB
[smallpt:235]     (clinit):     115.10 ms,  1.22 GB
[smallpt:235]   (typeflow):   4,217.23 ms,  1.22 GB
[smallpt:235]    (objects):   3,925.83 ms,  1.22 GB
[smallpt:235]   (features):     181.08 ms,  1.22 GB
[smallpt:235]     analysis:   8,624.17 ms,  1.22 GB
[smallpt:235]     universe:     266.92 ms,  1.22 GB
[smallpt:235]      (parse):     736.24 ms,  1.56 GB
[smallpt:235]     (inline):     947.00 ms,  1.56 GB
[smallpt:235]    (compile):   4,792.27 ms,  1.53 GB
[smallpt:235]      compile:   6,779.24 ms,  1.53 GB
[smallpt:235]        image:     769.52 ms,  1.52 GB
[smallpt:235]        write:      97.46 ms,  1.52 GB
[smallpt:235]      [total]:  18,542.54 ms,  1.52 GB

18秒かかってますね。 x86イメージをQEMUで動かした場合には、途中でコアダンプ吐いたりして、ネイティブ化ができませんでした。

M1 QEMU i7-7800X i7-7700HQ
18.5 N/A 22.1 42.4

ビルド時間を比べてみると、8コアであるi7-7800Xよりも速くビルドが通っています。やはりシングルスレッド性能が出たのではないかと思います。

f:id:nowokay:20210114232505p:plain

実行してみるとこんな感じです。QEMUではあらかじめi7で作成しておいたネイティブイメージを使っています。

M1 QEMU i7-7800X i7-7700HQ
native-image 21.4 5M28.7 34.2 48.2
Java 9.1 3M54 7.3 19.2
比率 2.4 1.4 4.7 2.5

ネイティブ化するまえ、Javaコードで動かした場合にはi7-7800Xのほうが速かったのですが、M1のほうが速くなっていますね。WindowsのDockerでなにかボトルネックがあるのかな。i7のCPU負荷が80%くらいにしかならなくて、ちゃんとコアが使い切れてない感じでした。
あと、QEMUでは差が小さいのが興味深いところ。実際の計算よりもエミュレーション用のなんらかのフットプリントのほうが時間をくってる感じでしょうか。

f:id:nowokay:20210114220958p:plain

TensorFlow

M1にはニューラルユニットが載っているので、ニューラルネットワークの計算も得意なはずです。TensorFlowがM1 Macに最適化したAlpha版を出しているので、これを試してみます。
Releases · apple/tensorflow_macos · GitHub

まずはチュートリアルにある全結合層が2段のネットワークで試してみます。
https://gist.github.com/kishida/dc2c2a1c0eadea6708d991506e6b72d6

試してみるとこんな感じで6.2秒で終了しました。
f:id:nowokay:20201214144040p:plain

なんと、RTX2070 Superより速いですね。

M1 RTX2070 Super i7-7700HQ
6.2 11.8 19.9

グラフにするとこう f:id:nowokay:20201222104030p:plain

これは恐らく、ネットワークが小さいためRTX2070 Superのコアをほとんど使っておらず、データ転送だけで時間がかかってるのではないかという気がします。
また、M1でニューラルユニットとArmコアが同じメモリを見ているというユニファイドメモリのおかげでデータ転送不要になっているのも大きいのだと思います。

そこで、もっと大きいネットワークを作ってみます。
畳み込みニューラルネットワークのチュートリアルにある、 畳み込み層が3層、プーリング層が2層と全結合層が2層の、AlexNetに近いネットワークです。
https://gist.github.com/kishida/01d88f72fdf95170294d637d5d6d62f5

実行すると45.385秒になりました。
f:id:nowokay:20201215115841p:plain

RTX 2070 Superなどと比較するとこんな感じです。

M1 RTX2070 Super i7-7700HQ
45.4 22.1 93.6

f:id:nowokay:20201222104318p:plain

やはりサイズの大きいネットワークだとRTX 2070 Superより速いということはありませんでしたね。ただ、RTX 2070 Superの倍でしかない、ともいえるし、MacBook Pro 17 2017よりはかなり速いです。ノートパソコンとしてはかなり優秀なんじゃないでしょうか。
手元で機械学習をする場合にもよさそうです。

まとめ

やはり、かなりいいパフォーマンスが出てますね。
シングルスレッド性能が重要になりがちなビルドがかなり速くなっているので、エンジニアにはよさそうです。
GPU機械学習もノートパソコンとしてはかなり高いパフォーマンスです。
まだDockerでのQEMUが不安定だったり、改善が必要な点はありますが、かなりよさげです。