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

Life under 200ms

Life under 200ms

In this talk we walk through how do we force our Swift build times to be at minimum and how do we optimise methods to be lower than the threshold.

Richard Szabo

October 26, 2017
Tweet

More Decks by Richard Szabo

Other Decks in Programming

Transcript

  1. Agenda ▪ Introduction ▪ Background ▪ Example problem ▪ Swift

    implementation ▪ Original ▪ Steps to refactor ▪ Refactored ▪ Conclusion
  2. Background ▪ We love building products that are fast and

    reliable ▪ we love the same thing about our environment ▪ Swift build times could skyrocket ▪ Code quality is our #1 priority ▪ … and we are not afraid to implement strict rules to enforce this
  3. Background - Build settings ▪ Treat Warnings as Errors ▪

    Turned “On” ▪ Other Swift Flags ▪ -Xfrontend ▪ -warn-long-function-bodies=200 ▪ -warn-long-expression-type-checking=150
  4. Example problem Implement a method that will create text based

    monograms from names. The rules are: ▪ Input ▪ name is a string ▪ first name, middle name, last name are separated by a space, if they exist ▪ Output ▪ should consist of 1 or 2 character ▪ should be without whitespaces Examples “Gabor Nagy Farkas” -> “GN” “Richard Szabo” -> “RS” “David” -> “D” “” -> “”
  5. Original Swift Implementation private func monogram(for name: String) -> String

    { let names: [Substring] = name.split(separator: " ") guard !names.isEmpty else { return “" } guard names.count > 1 else { return "\(String(describing: names[0].first!))” } return "\(String(describing: names[0].first!))\(String(describing: names[1].first!))" }
  6. Original Swift Implementation private func monogram(for name: String) -> String

    { let names: [Substring] = name.split(separator: " ") guard !names.isEmpty else { return “" } guard names.count > 1 else { return "\(String(describing: names[0].first!))” } return "\(String(describing: names[0].first!))\(String(describing: names[1].first!))" } Instance method 'monogram(for:)' took 231ms to type-check (limit: 200ms)
  7. Steps to refactor ▪ Eliminate string interpolation ▪ Create a

    protocol extension ▪ Extract string / numeric literals into constants ▪ Use higher-order functions
  8. Step 1 - Eliminate string interpolation private func monogram(for name:

    String) -> String { let names: [Substring] = name.split(separator: " ") guard !names.isEmpty else { return “" } guard names.count > 1 else { return String(describing: names[0].first!) } return String(describing: names[0].first!) + String(describing: names[1].first!) }
  9. Step 1 - Eliminate string interpolation private func monogram(for name:

    String) -> String { let names: [Substring] = name.split(separator: " ") guard !names.isEmpty else { return “" } guard names.count > 1 else { return String(describing: names[0].first!) } return String(describing: names[0].first!) + String(describing: names[1].first!) } Instance method 'monogram(for:)' took 210ms to type-check (limit: 200ms)
  10. Step 2 - Create a protocol extension protocol Monogramable {

    var name: String { get } func monogram() -> String } extension Monogramable { func monogram() -> String { let names: [Substring] = self.name.split(separator: " ") guard !names.isEmpty else { return "" } guard names.count > 1 else { return String(describing: names[0].first!) } return String(describing: names[0].first!) + String(describing: names[1].first!) } }
  11. Step 2 - Create a protocol extension protocol Monogramable {

    var name: String { get } func monogram() -> String } extension Monogramable { func monogram() -> String { let names: [Substring] = self.name.split(separator: " ") guard !names.isEmpty else { return "" } guard names.count > 1 else { return String(describing: names[0].first!) } return String(describing: names[0].first!) + String(describing: names[1].first!) } } Instance method 'monogram(for:)' took 180ms to type-check (limit: 100ms)
  12. Step 3 - Extract string / numeric literals into constants

    func fetch(success: TranslationSuccessClosure, failure: FailureClosure) { let mapper: LocalisationMapper = LocalisationMapper() let encryptor: EncryptorInterface = Encryptor(secureKeyStorage: SecureKeyStorage()) let decryptor: DecryptorInterface = Decryptor(secureKeyStorage: SecureKeyStorage()) let encryptedFileContainer: EncryptedFileContainerInterface = EncryptedFileContainer() let storage: StorageInterface = Storage(with: StorageParams(logger: Logger(), encryptedFileContainer: encryptedFileContainer, encryptor: encryptor, decryptor: decryptor)) let translationStorageParams = TranslationStorageParams(storage: storage) TranslationStorage(with: translationStorageParams).storeTranslations(mapper.map(json)) } fileprivate enum MonogramableConstants { fileprivate static let separator: Character = " " fileprivate static let empty: String = "" } 
 protocol Monogramable { var name: String { get } func monogram() -> String } extension Monogramable { func monogram() -> String { let names: [Substring] = self.name.split(separator: MonogramableConstants.separator) guard !names.isEmpty else { return MonogramableConstants.empty } guard names.count > 1 else { return String(describing: names[0].first!) } return String(describing: names[0].first!) + String(describing: names[1].first!) } }
  13. Step 3 - Extract string / numeric literals into constants

    func fetch(success: TranslationSuccessClosure, failure: FailureClosure) { let mapper: LocalisationMapper = LocalisationMapper() let encryptor: EncryptorInterface = Encryptor(secureKeyStorage: SecureKeyStorage()) let decryptor: DecryptorInterface = Decryptor(secureKeyStorage: SecureKeyStorage()) let encryptedFileContainer: EncryptedFileContainerInterface = EncryptedFileContainer() let storage: StorageInterface = Storage(with: StorageParams(logger: Logger(), encryptedFileContainer: encryptedFileContainer, encryptor: encryptor, decryptor: decryptor)) let translationStorageParams = TranslationStorageParams(storage: storage) TranslationStorage(with: translationStorageParams).storeTranslations(mapper.map(json)) } fileprivate enum MonogramableConstants { fileprivate static let separator: Character = " " fileprivate static let empty: String = "" } 
 protocol Monogramable { var name: String { get } func monogram() -> String } extension Monogramable { func monogram() -> String { let names: [Substring] = self.name.split(separator: MonogramableConstants.separator) guard !names.isEmpty else { return MonogramableConstants.empty } guard names.count > 1 else { return String(describing: names[0].first!) } return String(describing: names[0].first!) + String(describing: names[1].first!) } } Instance method 'monogram(for:)' took 120ms to type-check (limit: 100ms)
  14. Step 4 - Use higher-order functions fileprivate enum MonogramableConstants {

    fileprivate static let prefix: Int = 2 fileprivate static let separator: Character = " " fileprivate static let initialResult: String = "" } protocol Monogramable { var name: String { get } func monogram() -> String } extension Monogramable { func monogram() -> String { return self.name .split(separator: MonogramableConstants.separator) .prefix(MonogramableConstants.prefix) .reduce(MonogramableConstants.initialResult) { (result: String, substring: Substring) -> String in return result + String(describing: substring.first) } } }
  15. Step 4 - Use higher-order functions fileprivate enum MonogramableConstants {

    fileprivate static let prefix: Int = 2 fileprivate static let separator: Character = " " fileprivate static let initialResult: String = "" } protocol Monogramable { var name: String { get } func monogram() -> String } extension Monogramable { func monogram() -> String { return self.name .split(separator: MonogramableConstants.separator) .prefix(MonogramableConstants.prefix) .reduce(MonogramableConstants.initialResult) { (result: String, substring: Substring) -> String in return result + String(describing: substring.first) } } } Instance method 'monogram(for:)' took 56ms to type-check (limit: 50ms)
  16. Refactored Swift Implementation fileprivate enum MonogramableConstants { fileprivate static let

    prefix: Int = 2 fileprivate static let separator: Character = " " fileprivate static let initialResult: String = "" } protocol Monogramable { var name: String { get } func monogram() -> String } extension Monogramable { func monogram() -> String { return self.name .split(separator: MonogramableConstants.separator) .prefix(MonogramableConstants.prefix) .reduce(MonogramableConstants.initialResult) { (result: String, substring: Substring) -> String in return result + String(describing: substring.first) } } }
  17. Conclusion ▪ Although Swift compile times are not the best

    currently we can fight agains them with good practices ▪ Higher-order functions are the shit! ▪ There are a lot of tips and tricks over the internet, how to write more efficient code ▪ https://medium.com/@mimicatcodes/simple-higher- order-functions-in-swift-3-0-map-filter-reduce-and- flatmap-984fa00b2532 ▪ https://github.com/apple/swift/blob/master/docs/ OptimizationTips.rst ▪ http://detho.re/2016/01/21/writing-memory-efficient- swift-code/ ▪ https://www.objc.io/books/optimizing-collections/