Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Improving App Launch Time

Improving App Launch Time

Presented by Prashant Rane

Eeb061c8b2816b771920da1b3e7904a3?s=128

Swift India

January 25, 2020
Tweet

Transcript

  1. Intro @the69geeks

  2. Exception Type: EXC_CRASH (SIGKILL) Exception Codes: 0x0000000000000000, 0x0000000000000000 Exception Note:

    EXC_CORPSE_NOTIFY Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d Termination Description: SPRINGBOARD, process-launch watchdog transgression: com.go-jek.xxxxx.develop exhausted real (wall clock) time allowance of 20.00 seconds | ProcessVisibility: Unknown | ProcessState: Running | WatchdogEvent: process-launch | WatchdogVisibility: Foreground | WatchdogCPUStatistics: ( | "Elapsed total CPU time (seconds): 27.590 (user 27.590, system 0.000), 69% CPU", | "Elapsed application CPU time (seconds): 0.128, 0% CPU" | ) Triggered by Thread: 0
  3. Improving iOS App Launch Time A discussion about how we

    approached this problem
  4. Agenda • What affects launch timing (Measure and Improve) •

    Why did we faced the issue • How we zeroed on the approach to fix (Pre-Main only) • How we fixed this problem • Key takeaway from the journey
  5. App Launch Times • Cold Launch • Warm Launch •

    Hot Launch
  6. What affects launch timing • Pre-Main • Post-Main

  7. What affects launch timing • Pre-Main • Measure using DYLD_PRINT_STATISTICS

  8. What affects launch timing • Pre-Main • Measure using DYLD_PRINT_STATISTICS

    • dylib load time, rebase/binding, objc setup, Static Initializers
  9. What affects launch timing • Pre-Main • Measure using DYLD_PRINT_STATISTICS

    • dylib load time, rebase/binding, objc setup, Static Initializers across code • Post-Main • Measure using Instruments
  10. What affects launch timing • Pre-Main • Measure using DYLD_PRINT_STATISTICS

    • dylib load time, rebase/binding, objc setup, Static Initializers across code • Post-Main • Measure using Instruments • Time between call to main() and ability to handle touch
  11. What affects launch timing • iOS version, because DYLD version

    ships with OS
  12. What affects launch timing • iOS version, because DYLD version

    ships with OS • Battery Level (Power Saver mode)
  13. What affects launch timing • iOS version, because DYLD version

    ships with OS • Battery Level (Power Saver mode) • Hardware Capacity
  14. What affects launch timing • iOS version, because DYLD version

    ships with OS • Battery Level (Power Saver mode) • Hardware Capacity • Binary Size
  15. Post-Main • Time Spent In: • app(Will)DidFinishedLaunching methods • loadView/viewWill(Did)Appear/draw

    methods
  16. App(Will)DidFinishedLaunching • Singleton initializations (Facebook, Firebase etc?) •

  17. App(Will)DidFinishedLaunching • Singleton initializations (Facebook, Firebase etc?) • DB connection

    / Core Data/ Model Layer Initialization •
  18. App(Will)DidFinishedLaunching • Singleton initializations (Facebook, Firebase etc?) • DB connection

    / Core Data/ Model Layer Initialization • User Location?
  19. App(Will)DidFinishedLaunching • Singleton initializations (Facebook, Firebase etc?) • DB connection

    / Core Data/ Model Layer Initialization • User Location? • Analytics? •
  20. App(Will)DidFinishedLaunching • Singleton initializations (Facebook, Firebase etc?) • DB connection

    / Core Data/ Model Layer Initialization • User Location? • Analytics? • Setup Key Window • … many more
  21. ViewRendering (RootVC) • Setup Navigation Container (e.g. TabBarVC)

  22. ViewRendering (RootVC) • Setup Navigation Container (e.g. TabBarVC) • Prepare

    Datasource/ViewModel/VIPER stack •
  23. ViewRendering (RootVC) • Setup Navigation Container (e.g. TabBarVC) • Prepare

    Datasource/ViewModel/VIPER stack • Time require to layout any Complex View
  24. ViewRendering (RootVC) • Setup Navigation Container (e.g. TabBarVC) • Prepare

    Datasource/ViewModel/VIPER stack • Time require to setup View (complex layout) • Any other work done in ViewDid(Will)Load (Auth, StateRestoration etc)
  25. Post-Main Use os_signpost for taking accurate measures of a method

    which is contributing to launch time. Ex: ViewDidLoad of RootViewController
  26. Post-Main Use os_signpost for taking accurate measures of a method

    which is contributing to launch time. Ex: ViewDidLoad of RootViewController
  27. POI Details

  28. Post-Main Measurement Demo

  29. Skipped Post-Main • Overall gains < 10% • Efforts >>

  30. Focused on Pre-Main

  31. ~19 seconds Before Optimization (iPhone6s 12.3.1)

  32. ~7.5 Seconds After Optimization (iPhone6s 12.3.1)

  33. Pre-Main Time exec() to main() Better Guide: https://developer.apple.com/videos/play/wwdc2016/406

  34. Dyld: The WorkHorse • Load dylibs • Fix Ups •

    Objc Setup • Initializer
  35. Dyld: The WorkHorse • Load dylibs • Kernel loads Dyld

  36. Dyld: The WorkHorse • Load dylibs • Kernel loads Dyld

    • Dyld loads dylibs
  37. Dyld: The WorkHorse • Load dylibs • Fix Ups •

    Bind all dylibs together
  38. Dyld: The WorkHorse • Fix Ups • Rebase • Adjust

    pointers inside image
  39. Dyld: The WorkHorse • Rebase • Adjust pointers inside image

    • Slid = actual_address - preferred_address
  40. Dyld: The WorkHorse • Bindings • Adjust pointers outside image

    • Find symbol and malloc
  41. Dyld: The WorkHorse • Load dylibs • Fix Ups •

    Objc Setup
  42. Dyld: The WorkHorse • Load dylibs • Fix ups •

    Objc Setup • Initializer
  43. DYLD_PRINT_STATISTICS Total pre-main time: 249.96 milliseconds (100.0%) dylib loading time:

    191.67 milliseconds (76.6%) rebase/binding time: 15.74 milliseconds (6.2%) ObjC setup time: 9.54 milliseconds (3.8%) initializer time: 33.00 milliseconds (13.2%) slowest intializers : libSystem.B.dylib : 2.49 milliseconds (0.9%) libBacktraceRecording.dylib : 5.08 milliseconds (2.0%) libMainThreadChecker.dylib : 14.41 milliseconds (5.7%) libViewDebuggerSupport.dylib : 7.90 milliseconds (3.1%)
  44. Why did we faced this issue?

  45. Code Metric • 12000+ Types (Class, Struct, Enum…)

  46. Code Metric • 12000+ Types (Class, Struct, Enum…) • >

    8000 Swift files, ~1000 ObjC files (Pods)
  47. Code Metric • 12000+ Types (Class, Struct, Enum…) • >

    8000 Swift files, ~1000 ObjC files (Pods) • >80 Dynamic Libraries (and growing)
  48. Development Silos • Team Structure: independent development

  49. Development Silos • Team Structure: independent development • Abstract Target

    in Podfile to make it magical
  50. target 'Vendor' do dynamic_frameworks internal_dynamic_frameworks special_dynamic_frameworks inherit! :search_paths target 'Framework1'

    do inherit! :search_paths end target 'Framework2' do inherit! :search_paths end end target 'DemoApp' do static_frameworks host_only_static_libs dynamic_frameworks internal_dynamic_frameworks special_dynamic_frameworks end
  51. Development Silos • Team Structure: independent development • Abstract Target

    in Podfile to make it magical • Complex Inter Target dependancies
  52. Development Silos • Team Structure: independent development • Abstract Target

    in Podfile to make it magical • Complex Inter Target dependancies • Messed up Build Settings per product
  53. Finding a solution • IDEAL: • Reduce internal (by merging)

    and external (building inhouse) framework count
  54. Finding a solution • IDEAL: • Reduce internal (by merging)

    and external (building inhouse) framework count • Make dependancies as Static Frameworks and link that with Product
  55. Finding a solution • IDEAL: • Reduce internal (by merging)

    and external (building inhouse) framework count • Make dependancies as Static Frameworks and link that with Product • Create create an Umbrella Framework from all Dependancies (Dynamic Framework) •
  56. Finding a solution • IDEAL: • Reduce internal (by merging)

    and external (building inhouse) framework count • Make dependancies as Static Frameworks and link that with Product • Create create an Umbrella Framework from all Dependancies (Dynamic Framework) • All Deps: Static library?
  57. Fixing the problem • Find out dependancies in different targets

  58. Fixing the problem • Find out dependancies in different targets

    • Create a Lite app version to compare app launch time
  59. Fixing the problem • Find out dependancies in different targets

    • Create a Lite app version to compare app launch time • Prepare a checklist during the process of migration
  60. Fixing the problem • Find out dependancies in different targets

    • Create a Lite app version to compare app launch time • Prepare a checklist during the process of migration • Automate whatever we can from Checklist
  61. Problems Faced • Splitting Framework into Static Libs and Resource

    Bundle (DEMO) • Custom script to copy all resource bundles to app
  62. Problems Faced • CocoaPods + New Build System issues Build

    system information error: Multiple commands produce '/Users/Ben/Library/ Developer/Xcode/DerivedData/myApp- awbgyiqiegnrtdayecemnuyfqnbc/Build/Products/Debug- iphonesimulator/myApp.app': 1) Target 'myApp' has create directory command with output '/Users/Ben/Library/Developer/Xcode/DerivedData/myApp- awbgyiqiegnrtdayecemnuyfqnbc/Build/Products/Debug- iphonesimulator/myApp.app' 2) That command depends on command in Target 'myApp': script phase “[CP] Copy Pods Resources”
  63. Problems Faced • Implicit dependancies not working; so as the

    Parallel builds • That needs updating all target’s linker flags
  64. Problems Faced • As Implicit Dependancies were off, we need

    to add all imports explicitly • A script to automate inserting some must have imports
  65. Problems Faced • IntentDefinition was not getting compiled correctly (autogen

    classes were not generated) • Copied the generated files manually
  66. Problems Faced • R.Swift was not working with Static Libs

    + Resource Bundle model • Run the r.swift script from resource bundle and add generated files to library • Later replaced R.swift with inhouse code
  67. Problems Faced • Change in Framework name affected DLS components

    in IB files (ModuleName for a class) • XIB files using component from another framework, and now the name of framework is changed
  68. Problems Faced • Archive failed couple of times: • One

    header for static library was in copied in sources • ResourceBundle executable was not code signed (Demo the plist)
  69. Problems Faced • A few Crashes due to wrong scheme

    selected for archive (for some products)
  70. DEMO

  71. Open Issues • Required to Drop Support for Armv7 ld:

    section __bundle (address=0x06748000, size=735061037) would make the output executable exceed available address range for architecture armv7 • A bit longer build times even after switching to New Build System • Pods still add copy resources step in static lib • Incremental builds are not building Delta changes only
  72. Takeaways • Performance is equally important as a feature

  73. Takeaways • Performance is equally important as a feature •

    Should be a Continuous Process (Don’t wait till blast)
  74. Takeaways • Performance is equally important as a feature •

    Should be a Continuous Process (Don’t wait till blast) • Measure what matters (part of CI?) • Using XCUITest
  75. Takeaways • Performance is equally important as a feature •

    Should be a Continuous Process (Don’t wait till blast) • Measure what matters (part of CI?) • Using XCUITest • Automate things which developer won’t be touching • Deployment target, swift version, optimization level etc
  76. Thank You • https://developer.apple.com/videos/play/wwdc2016/406/ • https://useyourloaf.com/blog/slow-app-startup-times/ • https://github.com/leavez/cocoapods-static-swift-framework • https://blog.automatic.com/how-we-cut-our-ios-apps-

    launch-time-in-half-with-this-one-cool-trick-7aca2011e2ea • https://medium.com/@kolyuchiy/a-record-breaking-story-of- boosting-the-launch-time-of-mail-rus-email-app-for- ios-59a266dd288a