The best hardware to build with Swift is not what you might think

The best hardware to build with Swift is not what you might think

In the previous article I shared some data regarding Swift compiler performance when compared with Objective-C. I noted that Swift builds are significantly slower than comparable Objective-C code. One may be tempted to mitigate this problem by upgrading build hardware to speed things up.

Don't rush it

When LinkedIn embraced Swift and rebuilt its flagship iOS app using (mostly) Swift, I spent a good amount of time profiling the new compiler characteristics. At that time (mid 2015), it was clear that the compiler was CPU-bound (I/O and memory had no visible impact on build speed). However, the build times were determined primarily by the number of cores and threads available to the compiler, more so than by the CPU clock speed. This made sense as LinkedIn app code was big enough, and we modularized it into independent frameworks that could be built in parallel. Swift compiler made a good use of all the cores available - the more the better!

Based on this analysis, we decided to use 12-core MacPros for core development, and we saw a significant (2-3 times) speedup of our builds compared with quad-core laptops.

Lately, we noticed that majority of our mobile engineers stopped using their expensive MacPro machines, and would spend more time coding and building on their MacBookPro laptops instead. When asked why, devs response was simple: the laptops are faster to build - to the extent where they started to refer to the MacPro as the "trashcan" - not only because of the way they looked.

Being mindful about our expenses, this triggered another investigation. On the surface, the statement made no sense, both in the context of our prior measurements, but also when looking at Mac performance benchmarks available online. In multi-threaded environment MacPro is the most performant piece of Mac hardware available on the market. Even a 7 years old MacPros perform better than the latest quad-core iMacs. So what happened?

Profiling

"xcodebuild" tool allows to control the number of threads used during the builds using "-jobs" option, or with "IDEBuildOperationMaxNumberOfConcurrentCompileTasks" Xcode setting. By default it uses the max available, which equals to the number of virtual cores on the system. For a quad-core MacBookPro, Xcode will use 8 threads. On a 12-core MacPro, it'll be 24 threads. Of course, the number of concurrent build operations kicked off will depend on your project structure and dependency graph.

I measured build times for our main application using different values for the number of concurrent build operations allowed, and discovered a very disturbing phenomenon.

The more cores you let Xcode use, the slower the builds get.

The chart above shows debug build times in seconds (vertical axis) over the number of concurrent jobs allowed (horizontal axis), for 4 different machines I tested: MacBookPro (quad-core late 2013 model, 2.6 GHz i7), MacPro (12-core late 2013 model, 2.7 GHz Xeon E5), iMac (quad-core late 2015, 4 GHz i7) and a MacMini (dual-core late 2014, 3 GHz i7). Builds were performed on OS 10.11.6 (El Capitan) using Xcode 8.2.1.

As you can see, 12-core MacPro is indeed the slowest machine to build our code with Swift, and going from the default 24 jobs setting down to only 5 threads improves compilation time by 23%. Due to this, even a 2-core MacMini ($1,399.00) builds faster than the 12-cores MacPro ($6,999.00).

I re-run the tests for a release flavor, with a smaller app, tried building on macSierra and even built for the new APFS RAM disk suspecting some filesystem I/O congestion issues - with similar results. In the end, the regression in behavior seems to be coming either from the compiler itself, or the OS.

Conclusion and Tips

Until Apple fixes this issue, I suggest you measure and make careful decisions about your build hardware. Your mileage may vary depending on the nature of your code base, mix of Swift, Objective-C, C/C++ and other languages - just don't assume that industry-blessed performance benchmarks apply to the world of Swift.

  • For machines with more than 4 cores consider reducing number of concurrent jobs allowed for Xcode - until the concurrency issue described above has been resolved. I found 5 to be a good number for MacPro, and 6-8 for MacBookPro and iMac. Depending on the machine and the project, you can get over 20% build time improvement by doing so. To force Xcode to use up to 5 jobs you can run the line below in the Terminal: "defaults write com.apple.dt.Xcode IDEBuildOperationMaxNumberOfConcurrentCompileTasks 5"
  • Disable Spotlight indexing for build cache folders. I saw 10% improvement after blacklisting derived data, cache and related folders ("~/Library/Developer" and "~/Library/Caches") from Spotlight indexing.


Julian Iaccopucci

iOS Developer at Ford Motor Company

3y

Have you replayed this test on latest hardware?

Caio Dias

iOS Developer at Flybits

4y

Hello, did you had a chance to check this with Xcode 11.x? I wonder if it had changed.

Jacek Suliga

Dev Experience @ LinkedIn

5y

Xcode 8.3.2 and above fixed a number of issues related to concurrency, plus as explained earlier, there was a threading issue with Carbon Black. New iMac still builds faster than the Mac Pro though, but not by such a huge margin (10-20% depending on the project)

Like
Reply
Randy Hill

Investment Manager at nVariant Capital

6y

Hmm, this is confusing. With XCode 9, our build times with a 90k line Swift project (some is obj c in Cocoapods) on a MacBook Pro (2.5 mhz) dropped from 12 minutes to about 4 minutes on a base 16 gig Mac Pro. I'm wondering why our experiences are so vastly different? We aren't doing anything tricky with ram disks, etc. Pretty vanilla.

Justin Lloyd

Lead Software System-level Engineer | MSc CompSci & MSc AI/DL & MBA

6y

First few things I do when setting up my build machine is move my ~/Library/Developer/DerivedData to a RAM disk. Disable XCode indexing on the dedicated build machines. Twiddle the number of concurrent jobs and, if I can, aggressively cache as much of the project in RAM (easy on larger Macs). Without investigation I cannot say what you are experiencing, but I suspect it is a combination of IO issues, L2 cache issues and core residency issues. I've just spent the past three months reducing the build times of a cross-platform project (iOS, Android & Linux) from 7+ hours per platform to under 13 minutes through a combination of techniques.

To view or add a comment, sign in

Insights from the community

Others also viewed

Explore topics