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

Reduce Build Times and Get Home Eariler

Reduce Build Times and Get Home Eariler

Swift is a robust language, but at the same time, it means that many things are done at build time. Swift is now the standard for iOS development, but as a result, we suffer longer builds and it deprives us of our precious time. Time is more valuable than anything.

In this session, I will show you how to improve Swift’s build times and gain productivity in three parts:

- Measurement
- Improvement
- Avoid building all together!

Yusei Nishiyama

April 20, 2017
Tweet

More Decks by Yusei Nishiyama

Other Decks in Technology

Transcript

  1. Reduce Build Times
    and Get Home Earlier
    Yusei Nishiyama
    @yuseinishiyama

    View full-size slide

  2. • iOS, Android, Web
    • 63 countries
    • 17 languages
    • 63M+ MAU (Japan)
    • 35M+ MAU (Outside Japan)
    https://info.cookpad.com/en
    https://cookpad.com/en

    View full-size slide

  3. Distributed Team

    View full-size slide

  4. Let’s improve the registration
    screen…

    View full-size slide

  5. Let’s improve the registration
    screen…
    • Implement

    View full-size slide

  6. Let’s improve the registration
    screen…
    • Implement
    • Tweak constraints

    View full-size slide

  7. Let’s improve the registration
    screen…
    • Implement
    • Tweak constraints
    • Test with older iOS
    versions

    View full-size slide

  8. Let’s improve the registration
    screen…
    • Implement
    • Tweak constraints
    • Test with older iOS
    versions
    • Fix tests

    View full-size slide

  9. Let’s improve the registration
    screen…
    • Implement
    • Tweak constraints
    • Test with older iOS
    versions
    • Fix tests
    • Wait for CI

    View full-size slide

  10. How many times 

    did you build so far?

    View full-size slide

  11. You are not efficient

    View full-size slide

  12. 3 steps to improve
    • Measurement
    • Visualization
    • Improvement

    View full-size slide

  13. What I will “not” talk about
    • Annotate all types explicitly
    • Write as much as code in a single file
    • Get your Mac Pro

    View full-size slide

  14. 3 steps to improve
    • Measurement
    • Visualization
    • Improvement

    View full-size slide

  15. “Premature optimization is
    the root of all evil.”


    — Donald Knuth —

    View full-size slide

  16. Display build duration
    $ defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES

    View full-size slide

  17. Build Process
    • Dependency Manager (CocoaPods, Carthage)
    • Compile source files
    • Run Scripts (Linter, Formatter)

    View full-size slide

  18. cookpad/dokumi
    • Open source
    • Advanced Lint tool
    • Comments issues on a pull request
    • Runs on CI server

    View full-size slide

  19. Violated linter rules
    Failed tests

    View full-size slide

  20. require 'benchmark'


    def measure(*args, &block)
    start_time = Time.now
    tms = Benchmark.measure(args, &block)
    @data << ExecutionLog.new(args, tms, start_time)
    end
    Benchmark

    View full-size slide

  21. Build Process
    • Dependency Manager (CocoaPods, Carthage)
    • Compile source files
    • Runs Scripts (Linter, Formatter)

    View full-size slide

  22. Build Process
    • Dependency Manager (CocoaPods, Carthage)
    • Compile source files
    • Function A
    • Function B
    • …
    • Runs Scripts (Linter, Formatter)

    View full-size slide

  23. Warn long function bodies

    View full-size slide

  24. Time function bodies
    $ pbpaste | grep '.[0-9]ms' | sort -nr | head -20

    View full-size slide

  25. RobertGummesson/
    BuildTimeAnalyzer-for-Xcode

    View full-size slide

  26. Is that data easy to manipulate?
    • Integrate data collection into our build
    process
    • Flexible format which doesn’t not restrict
    how we use it

    View full-size slide

  27. giginet/xcprofiler
    • A CLI tool to time body functions
    • Easy to integrate into build process
    • Flexible output (stdout, JSON, Custom)

    View full-size slide

  28. require 'xcprofiler'
    profiler = Xcprofiler::Profiler.by_product_name('MyApp')
    profiler.reporters = [
    Xcprofiler::StandardOutputReporter.new(limit: 20, order: :time),
    Xcprofiler::JSONReporter.new(output_path: 'result.json'),
    Xcprofiler::BlockReporter.new do |executions|
    do_something(executions)
    end,
    ]
    profiler.report!
    Custom Reporter

    View full-size slide

  29. xactivitylog
    • You can access build logs under DerivedData
    • xactivitylog is just gzip. You can check the
    content as a plain text just by unzipping it

    View full-size slide

  30. 3 steps to improve
    • Measurement
    • Visualization
    • Improvement

    View full-size slide

  31. CI Server
    cookpad/dokumi

    View full-size slide

  32. CI Server
    cookpad/dokumi

    View full-size slide

  33. CI Server
    cookpad/dokumi

    View full-size slide

  34. CI Server
    cookpad/dokumi

    View full-size slide

  35. CI Server
    cookpad/dokumi

    View full-size slide

  36. InfluxDB
    • Only purpose-built platform for any time-based data
    • Written in Go
    • Built-in HTTP API
    • SQL-like query language
    • Answer queries in real-time
    • Rich client libraries

    View full-size slide

  37. CI Server
    cookpad/dokumi

    View full-size slide

  38. Grafana
    • Rich metrics dashboard and editor
    • Fast rendering
    • Support InfluxDB as a data source
    • Query editor

    View full-size slide

  39. Build time visualization

    View full-size slide

  40. Other metrics

    View full-size slide

  41. 3 steps to improve
    • Measurement
    • Visualization
    • Improvement

    View full-size slide

  42. Test with Real Data
    $ cloc


    github.com/AlDanial/cloc v 1.72 T=3.40 s (153.3 files/s, 16646.6 lines/s)
    -------------------------------------------------------------------------------
    Language files blank comment code
    -------------------------------------------------------------------------------
    Swift 497 8944 3122 43831
    JSON 18 0 0 522
    Objective C 3 37 5 166
    C/C++ Header 4 19 6 27
    -------------------------------------------------------------------------------
    SUM: 522 9000 3133 44546
    -------------------------------------------------------------------------------

    View full-size slide

  43. Test with Real Data
    • 40,000 lines of Swift code
    • CocoaPods
    • 38 direct dependencies and 60 libraries
    • Linter in build phases
    • CI archives the app and distributes it for internal
    testing

    View full-size slide

  44. Machine specs
    • CI
    • Mac mini (Late 2014)
    • 3 GHz Intel Core i7
    • Memory 16GB
    • 256GB SSD
    • Local
    • MBP (Retina, 15-inch, Late 2013)
    • 2.3 GHz Intel Core i7
    • Memory 16 GB
    • 256GB SSD

    View full-size slide

  45. (Local clean build: 330s)

    View full-size slide

  46. Whole Module Optimization

    View full-size slide

  47. WMO without Optimization

    View full-size slide

  48. (Local clean build: 160s)

    View full-size slide

  49. https://swift.org/blog/whole-module-optimizations/

    View full-size slide

  50. post_install do |installer|
    installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
    config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Owholemodule'
    unless config.name == 'Release'
    other_swift_flags = config.build_settings['OTHER_SWIFT_FLAGS'] || ['$(inherited)']
    other_swift_flags << '-Onone'
    config.build_settings['OTHER_SWIFT_FLAGS'] = other_swift_flags
    end
    end
    end
    end

    View full-size slide

  51. (Local clean build: 118s)

    View full-size slide

  52. Time Function Bodies

    View full-size slide

  53. post_install do |installer|
    installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
    config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Owholemodule'
    unless config.name == 'Release'
    other_swift_flags = config.build_settings['OTHER_SWIFT_FLAGS'] || ['$(inherited)']
    other_swift_flags << '-Onone'
    other_swift_flags << '-Xfrontend'
    other_swift_flags << '-debug-time-function-bodies'
    config.build_settings['OTHER_SWIFT_FLAGS'] = other_swift_flags
    end
    end
    end
    end

    View full-size slide

  54. (Local clean build: 130s)

    View full-size slide

  55. Carthage
    + github "ReactiveX/RxSwift" ~> 3.4
    + github "RxSwiftCommunity/RxRealm"
    + github "realm/realm-cocoa" ~> 2.4
    + github "danielgindi/Charts" ~> 3.0.2
    - pod 'RxSwift', '3.3.1'
    - pod 'RxCocoa', '3.3.1'
    - pod 'RxRealm', '0.5.2'
    - pod 'RealmSwift', '2.4.4'
    Cartfile
    Podfile

    View full-size slide

  56. (Local clean build: 68s)

    View full-size slide

  57. Partial Lint
    lint() {
    while read filename; do
    if [[ "${filename##*.}" == "swift" ]]; then
    ${SWIFT_LINT} lint --path "${filename}" --quiet
    fi
    done
    }
    FORK_POINT=`git merge-base --fork-point origin/develop`
    git diff $FORK_POINT --name-only | lint

    View full-size slide

  58. (Local clean build: 55s)

    View full-size slide

  59. Do you really need to build
    your entire app?

    View full-size slide

  60. Real World Prototyping
    class ViewController: UITableViewController {
    let myView = MyView()
    override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .myColor
    view.addSubview(myView)
    }
    }

    View full-size slide

  61. You need “existing” code
    for prototyping

    View full-size slide

  62. Embedded Playground

    View full-size slide

  63. Framework for Playground

    View full-size slide

  64. How to use it in Playground
    import UIKit
    @testable import MyPlayground
    let color = UIColor.myGreen

    View full-size slide

  65. Code Modularization
    import UIKit
    @testable import YourUIKit
    let color = UIColor.myGreen

    View full-size slide

  66. Framework Oriented Architecture
    Feature A
    Design
    Feature B Feature C
    Network Persistence
    Core

    View full-size slide

  67. Microservice Architecture
    https://martinfowler.com/articles/microservices.html

    View full-size slide

  68. Easy to prototype

    View full-size slide

  69. High testability

    View full-size slide

  70. Autonomous Teams

    View full-size slide

  71. Independent Technology Stack 

    (Swift, Objc, React Native, Flux)

    View full-size slide

  72. Complex Workflow

    View full-size slide

  73. Dynamic Frameworks make
    your app slow to launch

    View full-size slide

  74. dylib Loading Time
    Total pre-main time: 4.2 seconds (100.0%)
    dylib loading time: 4.0 seconds (94.4%)
    rebase/binding time: 93.32 milliseconds (2.1%)
    ObjC setup time: 44.64 milliseconds (1.0%)
    initializer time: 98.30 milliseconds (2.3%)

    View full-size slide

  75. WWDC16 406 

    “Optimizing App Startup Time"

    View full-size slide

  76. Recap
    • You can get detailed build time information
    • Visualization helps you to identify the
    bottleneck
    • You can improve build time in many ways
    (WMO and Carthage are highly effective)
    • Modularization makes your prototyping easier

    View full-size slide

  77. References
    • http://techlife.cookpad.com/entry/2015/06/04/dokumi-en
    • http://developear.com/blog/2016/12/30/Speed-Swift-
    Compilation.html
    • https://swift.org/blog/whole-module-optimizations/
    • https://medium.com/@LogMaestro/adding-playgrounds-to-
    your-xcode-project-79d5ea0c7087
    • http://frameworkoriented.io/
    • https://developer.apple.com/videos/play/wwdc2016/406/

    View full-size slide

  78. I’ll summarize this talk on
    our engineering blog

    https://sourcediving.com/

    View full-size slide

  79. The presentation is available on


    https://speakerdeck.com/yuseinishiyama/reduce-build-times-
    and-get-home-eariler

    View full-size slide

  80. Any Question?

    View full-size slide