The effect was catastrophic. The negative business impact was a few orders of magnitude larger than the entire cost of the year long Swift re-write. Turns out a ton of people are on a cellular network the first time they download the Uber app (who knew?).
So we formed another strike team. We started decompling our object files and going over the symbols line by line to identify why our Swift code size was so much larger. We deleted unused features. Tyler had to rewrite the watchOS app back into objc.
(The watch app was only about 4400 lines but it since the processor arch was different and there was no ABI compatibility that means we had to include an entire extra copy of the Swift runtime into the watch bundle)
We were at our breaking point. So tired. But everyone rallied. This is when the real brilliant engineers started to shine. One of the devs in Amsterdam figured out how rearrange the compiler optimization passes. For those of us that aren’t compiler engineers I’ll explain.
Modern compilers do a ton of passes on our code. For example one pass might inline your functions. Another might replace constant expressions with their values. Depending on the order they execute the machine code might be smaller or larger.
If your inlined function gets passed a constant the complier can reason about that and replace the whole thing so
int x = 3
func(x) {
X + 4
}
would become just a constant value (7) if the inline pass goes first (which is much less code).
int x = 3
func(x) {
X + 4
}
would become just a constant value (7) if the inline pass goes first (which is much less code).
If inlining goes second the constant pass won’t be able to reason about the function body and you’ll end up with more code. This all of course depends entirely what the code you’re writing looks like so it’s hard to optimize the order of passes generically
So said brilliant engineer in Amsterdam, built an annealing algorithm in the release build to reorder the optimization passes in such a way to as minimize size. This shaved a whooping 11 mbs off the total machine code size and bought us enough runway to keep development going.
This terrified the Swift compiler engineers, they were worried that untested complier pass orders would expose untested bugs (even though each pass is supposed to be internally safe it’s hard to reason about the possible combinations). We didn’t have any major issues though.
We applied a bunch of other solutions too (linting for code patterns that were particularly expensive). We measured each one in the number of normal development weeks that the savings unlocked. But the real problem was the growth curve. It was always eating our winnings back.
Eventually we bought enough runway to make it to Apple upping the cellular download limit to 150. They also added a number of compiler features to help with the size optimizations (-Osize). By their own admission Swift will never compile as small as Objective-C
But as of this year they’ve gotten Swift down to 1.5x the machine code size of Objective-C, and they eventually upped it again to a 200 mb optional limit. We had enough runway to make it a few more years.
We almost failed though. If Apple hadn’t upped the limit we would have been force to re-write the Uber App back in ObjC. Eventually we were able to fix the other problems too. The brilliant @alanzeino and team got Swift support added to BUCK, which hugely improved build times.
A bunch of folks burned out along the way. A ton of money was spent, hard lessons were learned, but still to this day most people insist the rewrite was all worth it. New engineers who joined up loved the architectural consistency and never knew the pain it took to get there.
The larger community benefited from our learnings. @ellsk1 put together an amazing presentation and went on a speaking tour to share our knowledge. I was able to take my experience with me after I moved on and teach other teams how to make better decisions.
So my advice. Everything in Computer Science is a trade off. There is no universally superior language. Whatever you do, understand what the tradeoff are why you are making them. Don’t let it descend into a political war between opinionated factions.
Build in failure points. Figure out how to identify the tradeoffs and give yourself a way out if you get to a certain point a realize that you made a mistake. Big efforts are hard, but the cost grows the longer you make the wrong trade off.
Don’t be a Curmudgeon who doesn't contribute to the solution. Don't be a Zealot who creates bigger problems for everyone else. The best engineers I’ve ever worked with are really good at not falling into either of these traps.
Thank you all for coming on this journey with me. It was rather therapeutic. Good night!
[end thread]
[end thread]