Making Xcode build times great again

Making Xcode build times great again

8837458bad71ad8edb2fbbd4dc9e8bb2?s=128

Xavier Jurado

May 24, 2018
Tweet

Transcript

  1. Making build times great again SCHIBSTED iOS JAM 2018 Xavier

    Jurado iOS Lead @ Schibsted Spain
  2. Making build times great bearable again SCHIBSTED iOS JAM 2018

    Xavier Jurado iOS Lead @ Schibsted Spain
  3. Agenda → Motivation → Benchmarks → Recap 3/51

  4. Motivation 4/51

  5. 5/51

  6. 6/51

  7. Optimize build times → Lots of tips & tricks available

    → Some outdated information → Some myths → Lack of up to date analytical data 7/51
  8. Benchmarks 8/51

  9. Methodology → Real life project → Ruby script + fastlane

    → Xcode 9.3.1, debug configuration, simulator → 5 iterations cleaning derived data → 5 incremental iterations → https://github.schibsted.io/xavier-jurado/ios- common--xcode-benchmark 9/51
  10. Benchmarks 1. Baseline 2. Function & expression optimization 3. Number

    of files/classes 4. New build system 5. dSYM generation 6. CPU 7. WMO 10/51
  11. Baseline 11/51

  12. 28 % 72 % Swift Objective-C Baseline → Coches.net project

    → 4 years old → ~700 source files → 44k loc → Cocoapods (~20 dependencies) → Forced default settings 12/51
  13. Baseline Non incremental → Average: 159.76s → Stdev: 3.9s Incremental

    → Average: 9.5s → Stdev: 0,05s 13/51
  14. Function & expression optimization 14/51

  15. Motivation → Type checking: historical problem → Still an issue?

    Edge cases? 15/51
  16. Execution Swift compiler diagnostic optionss1: OTHER_SWIFT_FLAGS=-driver-time-compilation -Xfrontend -debug-time-function-bodies -Xfrontend -debug-time-compilation

    … plus awk, grep, sed magick1 k1 https://koke.me/2017/03/24/diving-into-swift-compiler-performance/ s1 https://github.com/apple/swift/blob/master/docs/ CompilerPerformance.md#diagnostic-options 16/51
  17. Results 11.5645 ( 42.5%) {compile: PopTip.o <= PopTip.swift} 10.6891 (

    8.9%) {compile: CameraPresenter.o <= CameraPresenter.swift} 10.6874 ( 8.9%) {compile: CameraUI.o <= CameraUI.swift} 10.6855 ( 8.9%) {compile: CameraViewController.o <= CameraViewController.swift} 10.6844 ( 8.9%) {compile: CheckCameraAccess.o <= CheckCameraAccess.swift} 10.6829 ( 8.9%) {compile: CheckPhotoAccess.o <= CheckPhotoAccess.swift} 10.6814 ( 8.9%) {compile: CollectionSelectorPresenter.o <= CollectionSelectorPresenter.swift} 10.6795 ( 8.9%) {compile: CollectionSelectorTableViewCell.o <= CollectionSelectorTableViewCell.swift} 10.6781 ( 8.9%) {compile: CollectionSelectorTableViewDataSource.o <= CollectionSelectorTableViewDataSource.swift} 10.1187 ( 11.5%) {compile: BadgeManager.o <= BadgeManager.swift} 10.1169 ( 11.5%) {compile: DeviceTokenManager.o <= DeviceTokenManager.swift} 10.1133 ( 11.5%) {compile: KnockerError.o <= KnockerError.swift} 10.1118 ( 11.4%) {compile: KnockerNotification.o <= KnockerNotification.swift} 10.1104 ( 11.4%) {compile: Knocker.o <= Knocker.swift} 10.1102 ( 11.4%) {compile: KnockerTrackingModule.o <= KnockerTrackingModule.swift} ... > 400 files took more than 1 second to compile 17/51
  18. Results 11.5645 ( 42.5%) {compile: PopTip.o <= PopTip.swift} 10.6891 (

    8.9%) {compile: CameraPresenter.o <= CameraPresenter.swift} 10.6874 ( 8.9%) {compile: CameraUI.o <= CameraUI.swift} 10.6855 ( 8.9%) {compile: CameraViewController.o <= CameraViewController.swift} 10.6844 ( 8.9%) {compile: CheckCameraAccess.o <= CheckCameraAccess.swift} 10.6829 ( 8.9%) {compile: CheckPhotoAccess.o <= CheckPhotoAccess.swift} 10.6814 ( 8.9%) {compile: CollectionSelectorPresenter.o <= CollectionSelectorPresenter.swift} 10.6795 ( 8.9%) {compile: CollectionSelectorTableViewCell.o <= CollectionSelectorTableViewCell.swift} 10.6781 ( 8.9%) {compile: CollectionSelectorTableViewDataSource.o <= CollectionSelectorTableViewDataSource.swift} 10.1187 ( 11.5%) {compile: BadgeManager.o <= BadgeManager.swift} 10.1169 ( 11.5%) {compile: DeviceTokenManager.o <= DeviceTokenManager.swift} 10.1133 ( 11.5%) {compile: KnockerError.o <= KnockerError.swift} 10.1118 ( 11.4%) {compile: KnockerNotification.o <= KnockerNotification.swift} 10.1104 ( 11.4%) {compile: Knocker.o <= Knocker.swift} 10.1102 ( 11.4%) {compile: KnockerTrackingModule.o <= KnockerTrackingModule.swift} ... 18/51
  19. import Foundation extension Data { func toHexString() -> String {

    return self.reduce("", {$0 + String(format: "%02.2hhx", $1)}) } } @objc public class DeviceTokenManager: NSObject { private static let key:String = "knocker.device.token" static let defaults = UserDefaults.standard public static func store(_ deviceToken: Data) { defaults.set(deviceToken.toHexString(), forKey: key) defaults.synchronize() } public static func retrieve() -> String? { return defaults.object(forKey: key) as? String } public static func clear() { defaults.removeObject(forKey: key) } } 19/51
  20. Conclusions → Quick wins. → Risk: write for the compiler

    instead of the developer. 20/51
  21. Number of files vs number of classes 21/51

  22. Motivation → As projects get bigger, Xcode gets slower →

    By how much? → Who's to blame? Code complexity? File count? 22/51
  23. Execution → Add 100, 300 and 900 randomly generated classes

    to the project → Check incremental & absolute build times → Compare with 900 randomly generated classes inside a single file 23/51
  24. // Autogenerated import Foundation class BenchmarkSwiftClass_0 { let dep: BenchmarkSwiftClass_15

    private let iteration = 0 init(dep: BenchmarkSwiftClass_15) { self.dep = dep } } 24/51
  25. Results 25/51

  26. Conclusions → Huge cost on incremental builds. → Shall we

    take it into account when creating protocols, extensions, etc? 26/51
  27. New build system 27/51

  28. Motivation → Provides higher reliabilitya1 → Catches many project configuration

    problems. → Improves overall build-system performance. a1 https://developer.apple.com/library/content/documentation/DeveloperTools/ Conceptual/WhatsNewXcode/xcode9/xcode9.html 28/51
  29. None
  30. Execution UseNewBuildSystem=YES 30/51

  31. Results ! 31/51

  32. Conclusions → Performance wise it's a net loss. → May

    make Xcode more stable 32/51
  33. dSYM generation 33/51

  34. Motivation → "Popular culture" → DWARF vs DWARF + dSYM?

    → dSYM files are only needed for external analysis (ie: release builds) 34/51
  35. Execution → DEBUG_INFORMATION_FORMAT: "dwarf" 35/51

  36. Results meh. 36/51

  37. Conclusions → No tangible effect 37/51

  38. CPU 38/51

  39. Motivation → Real life experience between MBP 13 (2 cores)

    & MBP 15 (4 cores) is abysmal. → What about faster processors? More cores? 39/51
  40. Execution Computer CPU #Cores Freq Max Freq MacbookPro Mid 2015

    i7-4980HQ 4 2,8 Ghz 4 Ghz Hackintosh Late 2017 i7-7700k 4 4.2 GHz 4.5 Ghz MacbookPro Mid 2017 i7-7820HQ 4 2.9 Ghz 3.9 Ghz MacbookPro Mid 2014 i7-4578U 2 3 Ghz 3.5 Ghz https://technical.city/en/cpu/Core-i7-7700K-vs-Core-i7-4980HQ 40/51
  41. Results 41/51

  42. Conclusions → #cores matters a lot. → If you don't

    have a 4 core machine, ask for one . → CPU matters, but newer != better. → Apple hardware is terrible has room for improvement. 42/51
  43. WMO Whole Module Optimizations 43/51

  44. Motivation → Introduced in Xcode 8 to produce faster Release

    binaries by applying optimizations that can only be performed to a whole module s2. → WMO: Compilation Mode + Optimization Level → It significantly changes the way the compiler runs. s2 https://swift.org/blog/whole-module-optimizations/ 44/51
  45. Execution → Pre Xcode 9.3: SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule" OTHER_SWIFT_FLAGS="-Onone" →

    Xcode 9.3: has its own setting SWIFT_COMPILATION_MODE 45/51
  46. As expected…? 46/51

  47. ! 47/51

  48. Conclusions → Greatly reduces absolute build times. → Can increase

    incremental build times. → Better for projects with lots of files. → Give it a try! 48/51
  49. Recap 49/51

  50. Worth your time 1. CPU 2. WMO (for big projects)

    3. Function/expression optimization 4. Be aware of the implications of having lots of small files Not worth it → New build system (but may be worth it for Xcode stability) → dSYM 50/51
  51. Thanks! 51/51