Instantly share code, notes, and snippets.

Embed
What would you like to do?
Compiler Optimizations, Compiling Optimally, and Whole Modules

DEPRECATED for Xcode 10 🎈

(check out What's New in Swift at 11:40, slide 42)

Whole Module Compilation Optimizations: Why these terms are sometimes misleading

When you look up how to compile swift faster for debug builds, people very earnestly give advice that seems contradictory: you should "try using the whole module optimization flag," and also "never use whole module optimization for debugging". [^1]

This is confusing because some of us are using these two general words:

compilation: "turning text into an executable program"

optimization: "making a thing better"

to mean very specific and different things in different contexts:

optimal compiling: "turning text into an executable program that builds as fast as possible"

compiling with high optimization: "turning text into an executable program that runs as fast as possible"

When talking about compiling, though, people on the Swift team are mostly careful to use these words in precise ways: optimization is about making programs smaller or faster (or both!). For these engineers, "making something optimal" and "optimizing" don't imply the same thing at all – so if they say that "you shouldn't use optimized builds for debugging", they're not suggesting that "you should use a sub-optimal build to debug," only that "debugging with the small/fast version of your app is hard."

We'll call this kind of optimization capital-o-Optimization, to differentiate from the everyday meaning of "making a thing better".

When people aren't as strict with the way they use the word "optimization", though, or if you're not used to thinking of the term in this extremely narrow sense, the advice on using "whole module optimization" can be really tough to follow. But this stuff is confusing to lots of people – you are definitely not alone!

Compiling with Whole Module Optimization: The History

"Whole module optimization" basically means "let's reorganize implementations across all the module's files so the app runs reallyreally fast, or compacts down to be reallreally small." In that case, the compiler cracks its knuckles and says "yes ma'am, when I am done putting this text together you will not even RECOGNIZE it" and we have a hearty chuckle about ever trying to debug it.

Before the compiler can munge implementations across files, though, it's got to gather all that information together. Once it has access to all the declarations & implementations in one place, the compiler goes "oh thank goodness, I'd been inventing a new wheel for every single file, and now I can just invent it ONCE omg I am going to turn this text into a program before you can boil water for tea."

One real downside to the whole-module compilation step: the compiler will cheerfully head off to invent a wheel even if all you changed was the diameter of the cupholder. This is what people mean when they say you'll lose incremental compiling with whole-module compilation: the compiler won't iterate as easily on one isolated bit of code after it's adopted the philosophy that all files are really one.

Optimized Compiling sans Optimization: Hacks of Opportunity

Since gathering up all the files is a necessary step of capital-o-Optimizing your app's code across the module, and since that gathering step would sometimes incidentally make builds finish faster, people started using the -whole-module-optimization compiler flag just for faster debug builds.

Most people don't want capital-o-Optimization for day-to-day debugging, though, so in order to keep their readable text code aligned with their executing application, they had to explicitly set SWIFT_OPTIMIZATION_LEVEL to Onone. And this is where you get the seemingly paradoxical advice to "avoid whole module optimization when you're compiling with whole module optimization": you've got to run the advice through a stricter formatter to realize that you should "avoid SWIFT_OPTIMIZATION_LEVEL=Owholemodule when you use -whole-module-optimization" for debugging.

So even though you may now watch the build process and say, "Gosh, this is sure optimal!", your compiled code has not been capital-o-Optimized.

Optimal Compilation Modes in Xcode 9.3

Luckily, Xcode 9.3 has added a project setting to make this less brain-twisty: a "Swift Compiler - Code generation" project setting called "Compilation Mode" (or SWIFT_COMPILATION_MODE, if you generate your project files) that you can set to either "whole module" or "single file". It's no accident that the word "optimize" is completely missing from this setting, either:  this compilation mode is unrelated to capital-o-Optimization. It might even be LESS optimal: it is absolutely possible that your project will compiles SLOWER in whole-module mode, or you might find that single-file compilation mode fits your iterative workflow better.

The Swift team is actively working to diminish this tradeoff, though, and I will be delighted when this post has outlived its usefulness.

tl;dr:

When you're looking up "fast swift compilation" and people say "use THE whole module optimization flag", check whether they're talking about "how to compile swift so it builds faster" (which sometimes, but not always, means using the project setting SWIFT_COMPILATION_MODE=wholemodule), or "how to compile swift so it runs faster" (with the project setting SWIFT_OPTIMIZATION_LEVEL=Owholemodule), or both (with the compiler flag -whole-module-optimization).

Footnotes

[^1] Surprisingly, the most helpful description I found of this difference was from a sentence that threw me for a loop on first read:

enable whole module optimization (WMO) by setting the SWIFT_WHOLE_MODULE_OPTIMIZATION flag to YES. This is different to enabling it as an optimization flag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment