M1 MacBook Airが届いていろいろやってたら年も明けてだいぶたったけども、ビルド速度とかJavaとかDockerとかTensorFlowとか、技術者が気になるベンチマークを試してたので、まとめました。
MacBook Airを買ってしまった
なんかM1 Mac解説動画をとるためにいろいろ調べていたら、悪質サイトのリンクを踏んだみたいで、MacBook Airを買ってしまっていた。
その悪質サイトは最初は7万円台ですよーっていっておいて、結局12万円くらいになっていた。
みんなもapple.comってサイトには注意しましょうね。
www.youtube.com
とどいた!
12/12到着予定といいつつ11日になっても羽田から動いてなかったので大丈夫かーと思ったら11日深夜というか12日未明というかそのあたりには福岡に届いてて、朝発想されて夜にとどいた。
でこれだ!
ベンチマーク
GeekBenchも前評判どおりの値がでました。 https://browser.geekbench.com/v5/cpu/5337092
GeekbenchはUniversalAppになってるようで、アプリケーションの情報から「Rosettaを使用して開く」にチェックを入れるとx86版を動かすことができます。
プロセッサがVirtualAppleになっています。
実行すると、Arm版を動かすのとほとんど同じ値が出ていますね。
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ていうところがすごいです。
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ってところがすごい
Javaのインストール
それでは、Javaを動かしてみます。
現在、JavaのArm Mac対応はJEP 391として進められていますが、3月リリースのJava 16には取り込まれず、おそらく9月リリースのJava 17に入ることになると思います。
とはいえAzul SystemsがArm Mac対応のJavaを出しているので、これを使って試してみます。
あと、x86版Javaも試してみようと思って、まずはzip版を落としてみるとCPUが違うといわれるました。このメッセージはキャプチャしそこねた。。。
Rosettaインストール後は警告が出ます。
で、dmgをインストールしてみたのだけど、ここではAdoptOpenJDKでJava 11を使いました。インストールするときにRosettaのインストール確認が出たのだけど、これもキャプチャしそこね。もったいない。
NetBeansを動かす
NetBeans 12.2がArm Macに対応したということで、試してみました。 最初はZulu 16eaで動かしたんだけど、すぐ落ちていたので、Zulu 11だと安定しました。
x86 Java on RosettaでもNetBeans自体は動きました。
ところでRosettaで動かすとOSバージョンが10.16になってるの気になる。Arm版だと11.1です。
Arm JavaでNetBeansを動かしてもx86 Javaで動かしても、Arm JavaでMavenプロジェクトを動かすのは問題ないんだけど、x86 JavaでMavenプロジェクトを動かすとなんか怒られます。けどここでキャンセルするとビルドは行われる。
Gradleプロジェクトであれば、Arm Javaで動かしてもx86 Javaで動かしてもすんなりいけました。
レイトレでベンチマーク
いつもなんだかんだレイトレでベンチマークを取ってるので、ここでもやってみます。
SmallPTをJavaで書きなおしたものです。
https://github.com/kishida/smallpt4j/blob/original/src/main/java/naoki/smallpt/SmallPT.java
こんな感じの絵が出力されます。(ちゃんと時間をかければきれいになる)
それぞれ計ってみると、こんな感じになりました。単位は秒。
M1 | VirtualApple | i7-7800X | i7-7700HQ |
---|---|---|---|
9.3 | 10.7 | 5.9 | 14.6 |
実行時間なので、低いほど性能が高いです。
ほぼメモリアクセスなしの計算のみの処理で、ベンチマークよりはRosetta経由で性能劣化してるけど、CPUエミュレーションしてると考えればほぼ遜色ない性能といえると思います。コア数の差でi7-7800Xが有利ですが、MacBook Pro 2017のi7-7700HQよりはかなり速いですね。
メモリアクセスがあったほうがいいかとテクスチャありでも比較してみました。
https://github.com/kishida/smallpt4j
結果はこんな感じで、比率としてはほぼかわらずです。
M1 | VirtualApple | i7-7800X | i7-7700HQ |
---|---|---|---|
25.2 | 26.2 | 14.9 | 36.5 |
まあテクスチャ使うといっても、ほぼキャッシュに載ってしまってるのかなという感じでした。
Vector API
ついでに、Java 16ではSIMD命令をサポートするVector APIが取り込まれているので試してみます。x86であればAVX命令ですけど、ArmだとNEONですかね。
M1 Macでも使えて、doubleであれば2つの値を同時に計算できます。128bit幅ということですね。
レイトレでは3次元のベクトルを処理するために3つの値を同時に計算したいので、float x4にしてみたのですけど、精度不足のせいか変な絵になりました。
参考までに、処理時間としては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秒かかってます。
i7と比較してみると、やたら遅い感じです。
M1 | VirtualApple | i7-7800X | i7-7700HQ |
---|---|---|---|
5.6 | 7.1 | 1.5 | 1.5 |
Rosettaでも遅くなってるというのはありますが、MacBook Pro 2017と比べてもかなり遅くなっています。
見ているとAttaching agents: []
のところで5秒かかっている感じです。なんらかセキュリティ機能でひっかかってるんでしょうか。
こちらで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
しています。
Rosetta経由だと表示が違うのは なんなんだろう?
--scan
をつけて各タスクの時間を見るとこんな感じ。
こちらはM1 Mac。
こっちがi7-7800X Windows。
結果をまとめるとこんな感じ。
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より速いので、実用上十分といえるのかもしれません。
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ごとのエミュレートだと不利なんだろうなという感じですね。複雑な処理が必要なものはあまり動かさないほうがよさそう。
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よりも速くビルドが通っています。やはりシングルスレッド性能が出たのではないかと思います。
実行してみるとこんな感じです。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では差が小さいのが興味深いところ。実際の計算よりもエミュレーション用のなんらかのフットプリントのほうが時間をくってる感じでしょうか。
TensorFlow
M1にはニューラルユニットが載っているので、ニューラルネットワークの計算も得意なはずです。TensorFlowがM1 Macに最適化したAlpha版を出しているので、これを試してみます。
Releases · apple/tensorflow_macos · GitHub
まずはチュートリアルにある全結合層が2段のネットワークで試してみます。
https://gist.github.com/kishida/dc2c2a1c0eadea6708d991506e6b72d6
試してみるとこんな感じで6.2秒で終了しました。
なんと、RTX2070 Superより速いですね。
M1 | RTX2070 Super | i7-7700HQ |
---|---|---|
6.2 | 11.8 | 19.9 |
グラフにするとこう
これは恐らく、ネットワークが小さいためRTX2070 Superのコアをほとんど使っておらず、データ転送だけで時間がかかってるのではないかという気がします。
また、M1でニューラルユニットとArmコアが同じメモリを見ているというユニファイドメモリのおかげでデータ転送不要になっているのも大きいのだと思います。
そこで、もっと大きいネットワークを作ってみます。
畳み込みニューラルネットワークのチュートリアルにある、
畳み込み層が3層、プーリング層が2層と全結合層が2層の、AlexNetに近いネットワークです。
https://gist.github.com/kishida/01d88f72fdf95170294d637d5d6d62f5
実行すると45.385秒になりました。
RTX 2070 Superなどと比較するとこんな感じです。
M1 | RTX2070 Super | i7-7700HQ |
---|---|---|
45.4 | 22.1 | 93.6 |
やはりサイズの大きいネットワークだとRTX 2070 Superより速いということはありませんでしたね。ただ、RTX 2070 Superの倍でしかない、ともいえるし、MacBook Pro 17 2017よりはかなり速いです。ノートパソコンとしてはかなり優秀なんじゃないでしょうか。
手元で機械学習をする場合にもよさそうです。
まとめ
やはり、かなりいいパフォーマンスが出てますね。
シングルスレッド性能が重要になりがちなビルドがかなり速くなっているので、エンジニアにはよさそうです。
GPUや機械学習もノートパソコンとしてはかなり高いパフォーマンスです。
まだDockerでのQEMUが不安定だったり、改善が必要な点はありますが、かなりよさげです。