Gene was tweeting about Clojure's startup performance on Apple Silicon, so I decided to benchmark my new MacBook Air with the M1 chip against my Intel based MacBook Pro.
The M1 chip in the MacBook Air - which lacks a fan - was able to quite tidily beat an almost top-of-the-line Intel chip. The standard deviation of results was also much tighter for the M1.
The average for the M1 was 1.846s vs. 2.798s for the Intel, a speedup of 1.5x.
The approach of testing leiningen
startup time comes from this excellent blog post by Alexander Yakushev. The idea is really simple - just start lein
in a loop and record how long it takes each time. Perform enough iterations to attempt to smooth out confounding system effects like caching and power management.
for i in {1..32}; do TIMEFORMAT=%E; time ( echo "(quit)" | lein repl >/dev/null 2>&1 ) ; done
Hardware:
Hardware Overview:
Model Name: MacBook Air
Model Identifier: MacBookAir10,1
Chip: Apple M1
Total Number of Cores: 8 (4 performance and 4 efficiency)
Memory: 16 GB
System Firmware Version: 6723.80.17
Hardware:
Hardware Overview:
Model Name: MacBook Pro
Model Identifier: MacBookPro16,1
Processor Name: 8-Core Intel Core i9
Processor Speed: 2.3 GHz
Number of Processors: 1
Total Number of Cores: 8
L2 Cache (per Core): 256 KB
L3 Cache: 16 MB
Hyper-Threading Technology: Enabled
Memory: 16 GB
System Firmware Version: 1554.60.15.0.0 (iBridge: 18.16.14338.0.0,0)
Ensuring you have a full ARM toolchain to see the true performance of the M1 is still a little tricky, but it's improving all the time. I've tried to note down the full set of steps I used.
The steps to get the Zulu OpenJDK installed on Intel should be trivial and I have not bothered to document them here :)
On Apple Silicon machines, the new conventional path for homebrew is /opt/homebrew
.
git clone https://github.com/Homebrew/brew.git /opt/homebrew
As of this writing, the only OpenJDK version with an Apple Silicon port is from Azul. They even have multiple versions to choose from.
hdiutil mount -mountpoint /Volumes/zulu https://cdn.azul.com/zulu/bin/zulu13.35.1025-ca-jdk13.0.5.1-macosx_aarch64.dmg
sudo installer -pkg "/Volumes/zulu/*.pkg" -target /
hdiutil unmount /Volumes/zulu
Note that in order to guarantee that x8664 binaries or bottles aren't sneaking in, I prefix all brew
commands with arch -arm64
# install jenv for managing JDK paths
arch -arm64 brew install jenv
# make jenv aware of the Zulu JDK
jenv add /Library/Java/JavaVirtualMachines/zulu-13.jdk/Contents/Home/
# use the Zulu JDK in the current shell (you can also set this globally)
jenv shell zulu64-13.0.5.1
# link the Zulu JDK to stop some (but not all) brew recipes complaining
ln -s /Library/Java/JavaVirtualMachines/zulu-13.jdk/Contents/Home/ /opt/homebrew/opt/openjdk
# install rlwrap, required by clojure
arch -arm64 brew install --build-from-source rlwrap
# install maven, required by clojure, ignoring the openjdk dep
arch -arm64 brew install maven --ignore-dependencies
# install clojure from the clojure tap, which does not have a hard-coded path
arch -arm64 brew install clojure/tools/clojure
# install leniningen, ignoring the openjdk dep
arch -arm64 brew install --build-from-source leiningen --ignore-dependencies
At the end of this you should be able to test the installation with:
clj -e '(System/getProperty "java.home")'
"/Library/Java/JavaVirtualMachines/zulu-13.jdk/Contents/Home"
Leiningen should return the same result:
echo '(System/getProperty "java.home")' | lein repl
nREPL server started on port 50059 on host 127.0.0.1 - nrepl://127.0.0.1:50059
REPL-y 0.4.4, nREPL 0.8.3
Clojure 1.10.1
OpenJDK 64-Bit Server VM 13.0.5.1+1-MTS
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=> "/Library/Java/JavaVirtualMachines/zulu-13.jdk/Contents/Home"
user=> Bye for now!