Почему Swift компилируется так медленно?

Я использую Xcode 6 Beta 6.

Это то, что меня раздражало уже какое-то время, но оно доходит до того момента, когда его едва можно использовать сейчас.

Мой проект начинает иметь приличный размер 65 файлов Swift и несколько мостовых файлов Objective-C (которые на самом деле не являются причиной проблемы).

Кажется, что любая небольшая модификация любого файла Swift (например, добавление простого пробела в класс, который почти не используется в приложении) приведет к перекомпиляции всех файлов Swift для указанной цели.

После более глубокого расследования я обнаружил, что на 100% времени компилятора приходится на фазу CompileSwift, где Xcode запускает команду swiftc для всех файлов Swift вашей цели.

Я провел некоторое исследование, и если я только поддерживаю делегат приложения с контроллером по умолчанию, компиляция выполняется очень быстро, но по мере того, как я добавлял все больше и больше моих файлов проектов, время компиляции начиналось очень медленно.

Теперь, когда всего 65 исходных файлов, каждый раз требуется компиляция. Не очень быстро.

Я не видел ни одного сообщения об этой проблеме, кроме этого, но это была старая версия Xcode 6. Поэтому мне интересно, m единственный в этом случае.

UPDATE

Я проверил несколько проектов Swift на GitHub, например Alamofire, Euler и CryptoSwift, но ни один из них не имел достаточно файлов Swift для сравнения. Единственный проект, который я нашел, который начинал иметь приличный размер, был SwiftHN, и хотя у него было только дюжины исходных файлов, я все еще был чтобы проверить одно и то же, одно простое пространство и весь проект нуждались в перекомпиляции, которая начиналась немного времени (2/3 секунды).

По сравнению с Objective-C кодом, где оба анализатора и компиляции быстро растут, это действительно похоже на то, что Swift никогда не сможет обрабатывать большие проекты, но, пожалуйста, скажите мне, что я ошибаюсь.

ОБНОВЛЕНИЕ С Xcode 6 Beta 7

Все еще никакого улучшения. Это начинает смеяться. С отсутствием #import в Swift я действительно не вижу, как Apple сможет оптимизировать это.

ОБНОВЛЕНИЕ С Xcode 6.3 и Swift 1.2

Apple добавила инкрементные сборки (и многие другие оптимизации компилятора). Вам нужно перенести свой код на Swift 1.2, чтобы увидеть эти преимущества, но Apple добавила инструмент в Xcode 6.3, чтобы помочь вам:

Enter image description here

ОДНАКО

Не радуйтесь слишком быстро, как я. Решатель графов, который они используют для создания инкрементальной сборки, еще не очень оптимизирован.

Во-первых, он не смотрит на изменения сигнатуры функции, поэтому, если вы добавите пробел в блок одного метода, все файлы в зависимости от этого класса будут перекомпилированы.

Во-вторых, создается дерево, основанное на файлах, которые были перекомпилированы, даже если изменение не влияет на них. Например, если вы перемещаете эти три класса в разные файлы

class FileA: NSObject {
    var foo:String?
}
class FileB: NSObject {
    var bar:FileA?
}
class FileC: NSObject {
    var baz:FileB?
}

Теперь, если вы измените FileA, компилятор, очевидно, отметит FileA, который будет перекомпилирован. Он также перекомпилирует FileB (это будет ОК на основе изменений в FileA), , но также FileC, потому что FileB перекомпилируется, и это довольно плохо, потому что FileC никогда не использует FileA здесь.

Поэтому я надеюсь, что они улучшат этот решатель дерева зависимостей... Я открыл radar с этим примером кода.

ОБНОВЛЕНИЕ С Xcode 7 beta 5 и Swift 2.0

Вчера Apple выпустила бета-версию 5 и в примечаниях к выпуску, которые мы могли видеть:

Язык и компилятор Swift • Инкрементальные сборки: изменение всего тела функции больше не должно перестраивать зависимые файлы. (15352929)

Я попробовал, и я должен сказать, что он действительно работает (действительно!). Они значительно оптимизировали инкрементные сборки в быстром режиме.

Я настоятельно рекомендую вам создать ветку swift2.0 и обновить свой код с помощью XCode 7 beta 5. Вы будете довольны усовершенствованиями компилятора (однако я бы сказал, что глобальное состояние XCode 7 по-прежнему медленный и багги)

ОБНОВЛЕНИЕ С Xcode 8.2

Прошло некоторое время со времени моего последнего обновления по этой проблеме, так что вот оно.

Наше приложение теперь содержит около 20 тыс. строк почти исключительно кода Swift, который является достойным, но не выдающимся. Он прошел быстрые 2 и быстрее, чем 3 миграции. Требуется около 5/6 м для компиляции в середине 2014 года Macbook pro (2,5 ГГц Intel Core i7), который в порядке с чистой сборкой.

Однако инкрементная сборка все еще шутка, несмотря на то, что Apple утверждает, что:

Xcode не будет восстанавливать целую цель, когда произошли только небольшие изменения. (28892475)

Очевидно, я думаю, что многие из нас просто рассмеялись после проверки этой глупости (добавив одно частное (личное!) свойство в любой файл моего проекта, перекомпилирует все это...)

Я хотел бы указать вам, ребята, на эту тему на форумах разработчиков Apple, у которых есть дополнительная информация об этой проблеме (а также оценены Яблоко Dev сообщение по этому вопросу время от времени)

В основном люди придумали несколько вещей, чтобы попытаться улучшить инкрементную сборку:

  • Добавьте параметр проекта HEADER_MAP_USES_VFS, установленный в true
  • Отключите Find implicit dependencies от вашей схемы.
  • Создайте новый проект и переместите иерархию файлов на новый.

Я попробую решение 3, но решение 1/2 не работает для нас.

Какая иронически смешная во всей этой ситуации заключается в том, что, глядя на первое сообщение по этому вопросу, мы использовали Xcode 6, я считаю, что быстрый 1 или быстрый код 1.1, когда мы достигли первой компиляции, и теперь примерно через два года, несмотря на фактические улучшения от Apple ситуация такая же плохая, как и с Xcode 6. Как иронично.

Я действительно ДЕЙСТВИТЕЛЬНО сожалею о выборе Swift над Obj/C для нашего проекта из-за ежедневного разочарования, которое он включает. (Я даже переключаюсь на AppCode, но это другая история)

В любом случае я вижу, что это сообщение SO имеет 32k + просмотров и 143 всплеска на момент написания, поэтому я думаю, что я не единственный. Повесьте туда ребята, несмотря на то, что они пессимистичны в этой ситуации, в конце туннеля может быть какой-то свет.

Если у вас есть время (и мужество!), я думаю, Apple приветствует радар об этом.

В следующий раз! Приветствия

ОБНОВЛЕНИЕ С Xcode 9

Напоминаем этот сегодня. Xcode спокойно представил новую систему сборки, чтобы улучшить текущую ужасную производительность. Вы должны включить его через настройки рабочей области.

введите описание изображения здесь

Попробовал еще, но будет обновлять этот пост после его завершения. Выглядит многообещающе.

Ответ 1

Ну, оказалось, что Роб Нейпир был прав. Это был один файл (фактически один метод), который заставлял компилятор идти berzek.

Теперь не поймите меня неправильно. Swift перекомпилирует все ваши файлы каждый раз, но сейчас замечательно, что Apple добавила обратную компиляцию в реальном времени по файлам, которые она компилирует, поэтому теперь Xcode 6 GM показывает, какие файлы Swift компилируются и статус компиляции в режиме реального времени как вы можете видеть на этом снимке экрана:

Enter image description here

Так что это очень удобно знать, какой из ваших файлов занимает так много времени. В моем случае это был фрагмент кода:

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : self.title ?? ""
        ])

return dic.copy() as NSDictionary

потому что свойство title имеет тип var title:String?, а не NSString. Компилятор сходил с ума, добавляя его к NSMutableDictionary.

Изменение его на:

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : NSString(string: self.title ?? "")
        ])

return dic.copy() as NSDictionary

сделал компиляцию с 10/15 секунд (может быть, даже больше) до одной секунды... удивительно.

Ответ 2

Мы попробовали немало вещей для борьбы с этим, поскольку у нас есть около 100 тыс. строк кода Swift и 300k строк кода ObjC.

Наш первый шаг состоял в том, чтобы оптимизировать все функции в соответствии с выходом времени компиляции функции (например, как описано здесь https://thatthinginswift.com/debug-long-compile-times-swift/)

Затем мы написали script, чтобы объединить все быстрые файлы в один файл, это сломает уровни доступа, но это привело к сокращению времени компиляции с 5-6 минут до ~ 1 минуты.

Это теперь не действует, потому что мы спросили об этом Apple, и они посоветовали нам сделать следующее:

  • Включите "оптимизацию всего модуля" в настройке сборки "Swift Compiler - Code Generation". Выберите 'Fast, Whole Module Optimization'

введите описание изображения здесь

  1. В "Swift Compiler - пользовательские флаги" для ваших разработок добавьте '-Onone'

введите описание изображения здесь введите описание изображения здесь

Когда эти флаги установлены, компилятор будет скомпилировать все файлы Swift за один шаг. Мы обнаружили, что с нашим слиянием script это намного быстрее, чем компиляция файлов по отдельности. Однако без переопределения -Onone' он также оптимизирует весь модуль, который работает медленнее. Когда мы устанавливаем флаг '-Onone' в других флажках Swift, он останавливает оптимизацию, но не останавливает компиляцию всех файлов Swift за один шаг.

Для получения дополнительной информации об оптимизации всего модуля ознакомьтесь здесь с сообщением о блоге Apple - https://swift.org/blog/whole-module-optimizations/

Мы обнаружили, что эти настройки позволяют скомпилировать наш код Swift за 30 секунд:-) У меня нет доказательств того, как он будет работать в других проектах, но я предлагаю попробовать, если время сборки Swift все еще остается проблемой для вас.

Примечание для ваших хранилищ в App Store, вы должны оставить флаг '-Onone', так как оптимизация рекомендуется для производственных сборок.

Ответ 3

Если вы пытаетесь определить определенные файлы, которые замедляют время компиляции, вы можете попробовать скомпилировать его из командной строки с помощью xctool, который даст вам время компиляции файла по файлу.

Следует отметить, что по умолчанию он строит 2 файла одновременно на каждое ядро ​​процессора и не даст вам "нетто" прошедшее время, но абсолютное "пользовательское" время. Таким образом, все тайминги выравниваются между параллельными файлами и выглядят очень похожими.

Чтобы преодолеть это, установите флаг -jobs в 1, чтобы он не распараллеливал сборки файлов. Это займет больше времени, но в конце вы получите "net" время компиляции, которое вы можете сравнить с файлом.

Это пример команды, которая должна выполнить трюк:

xctool -workspace <your_workspace> -scheme <your_scheme> -jobs 1 build

Вывод фазы "Компиляция Swift файлов" будет выглядеть примерно так:

...
   ✓ Compile EntityObserver.swift (1623 ms)
   ✓ Compile Session.swift (1526 ms)
   ✓ Compile SearchComposer.swift (1556 ms)
...

Из этого вывода вы можете быстро определить, какие файлы занимают больше времени, чем другие компилируются. Более того, вы можете с высокой точностью определить, будут ли ваши рефакторинги (явные приведения, подсказки типов и т.д.) Уменьшать время компиляции для определенных файлов или нет.

ПРИМЕЧАНИЕ. Технически вы также можете сделать это с помощью xcodebuild, но вывод невероятно подробный и трудно потреблять.

Ответ 4

Вероятно, это мало связано с размером вашего проекта. Вероятно, это определенная часть кода, возможно, даже одна строка. Вы можете проверить это, пытаясь скомпилировать один файл за раз, а не весь проект. Или попробуйте просмотреть журналы сборки, чтобы увидеть, какой файл занимает так много времени.

В качестве примера видов кода, которые могут вызвать проблемы, этот 38-строчный формат занимает более минуты, чтобы скомпилировать его в бета-версии. Все это вызвано этим одним блоком:

let pipeResult =
seq |> filter~~ { $0 % 2 == 0 }
  |> sorted~~ { $1 < $0 }
  |> map~~ { $0.description }
  |> joinedWithCommas

Упростите это только строкой или двумя, и она компилируется почти мгновенно. Проблема заключается в том, что это вызывает экспоненциальный рост (возможно, факторный рост) в компиляторе. Очевидно, что это не идеально, и если вы можете изолировать такие ситуации, вы должны открыть радары, чтобы помочь очистить эти проблемы.

Ответ 5

В моем случае Xcode 7 вообще не отличался. У меня было несколько функций, требующих нескольких секунд для компиляции.

Пример

// Build time: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

После разворачивания опций, время сборки снизилось на 99,4%.

// Build time: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
    padding += rightView.bounds.width
}

if let leftView = leftView {
    padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)

Подробнее в этот пост и этот пост,

Анализатор времени сборки для Xcode

I разработал подключаемый модуль Xcode, который может пригодиться любому, кто испытывает эти проблемы.

image

Похоже, в Swift 3 есть улучшения, поэтому, надеюсь, мы увидим, что наш код Swift быстрее компилируется.

Ответ 6

Решение ливается.

У меня было огромное количество тонны словарей, например:

["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
.....

Прошло около 40 минут, чтобы скомпилировать его. Пока я не произнес словарей вроде этого:

["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
....

Это работало практически для любой другой проблемы, с которой я столкнулся в отношении типов данных, которые я жестко закодировал в своем приложении.

Ответ 7

Следует отметить, что механизм вывода типа Swift может быть очень медленным с вложенными типами. Вы можете получить общее представление о том, что вызывает медленность, наблюдая за журналом сборки для отдельных единиц компиляции, которые занимают много времени, а затем копируют и вставляют полную команду, генерируемую Xcode, в окно терминала, а затем нажимают CTRL- \, чтобы получить некоторые диагностические. Взгляните на http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times для полного примера.

Ответ 8

Вероятно, мы не можем исправить компилятор Swift, но мы можем исправить наш код!

В компиляторе Swift есть скрытая опция, которая выводит точные временные интервалы, которые компилятор использует для компиляции каждой отдельной функции: -Xfrontend -debug-time-function-bodies. Это позволяет нам находить узкие места в нашем коде и значительно улучшать время компиляции.

Простой запуск в терминале и анализ результатов:

xcodebuild -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep [1-9].[0-9]ms | sort -nr > culprits.txt

Удивительный Брайан Ирейс написал блестящую статью об этом Профилирование ваших скоростей компиляции Swift.

Ответ 9

Также убедитесь, что при компиляции для отладки (либо Swift, либо Objective-C) вы устанавливаете только для создания активной архитектуры:

enter image description here

Ответ 10

Поскольку все это в Бета, и поскольку компилятор Swift (по крайней мере, на сегодняшний день) не открыт, я думаю, что нет никакого реального ответа на ваш вопрос.

Прежде всего, сравнение Objective-C с компилятором Swift каким-то жестоким. Swift все еще находится в бета-версии, и я уверен, что Apple работает над обеспечением функциональности и исправлением ошибок, более чем обеспечивая молниеносную скорость (вы не начинаете строить дом, покупая мебель). Я предполагаю, что Apple будет своевременно оптимизировать компилятор.

Если по какой-то причине все исходные файлы должны быть скомпилированы полностью, возможно, для создания отдельных модулей/библиотек. Но этот параметр пока невозможен, так как Swift не разрешает библиотеки до тех пор, пока язык не станет стабильным.

Я предполагаю, что они оптимизируют компилятор. По той же причине, что мы не можем создавать предварительно скомпилированные модули, вполне возможно, что компилятор должен скомпилировать все с нуля. Но как только язык достигнет стабильной версии, а формат двоичных файлов больше не меняется, мы сможем создать наши библиотеки, и, возможно, (?) Компилятор также сможет оптимизировать свою работу.

Просто догадываюсь, что только Apple знает...

Ответ 11

Для Xcode 8 перейдите к настройкам проекта, затем выберите "Редактор" > "Добавить настройку сборки" > "Добавить пользовательские настройки" и добавьте следующее:

SWIFT_WHOLE_MODULE_OPTIMIZATION = YES

Добавление этого флага удалило наше время компиляции чистой сборки с 7 минут до 65 секунд для быстрого проекта 40KLOC, чудом. Также можно подтвердить, что у двух друзей были аналогичные улучшения в корпоративных проектах.

Я могу только предположить, что это некоторая ошибка в Xcode 8.0

EDIT: для некоторых людей он больше не работает в Xcode 8.3.

Ответ 12

К сожалению, компилятор Swift по-прежнему не оптимизирован для быстрой и инкрементной компиляции (как в Xcode 6.3 beta). Тем временем вы можете использовать некоторые из следующих методов для улучшения времени компиляции Swift:

  • Разделите приложение в Framework, чтобы уменьшить эффект перекомпиляции. Но имейте в виду, что вы должны избегать циклических зависимостей в своем приложении. Для дальнейшей информации по этой теме проверьте это сообщение: http://bits.citrusbyte.com/improving-swift-compile-time/

  • Используйте Swift для части вашего проекта, которые достаточно стабильны и не меняются часто. Для других областей, где вам нужно очень часто меняться или областей, для которых требуется много итераций компиляции/запуска, чтобы они были полными (почти любые материалы, связанные с UI), лучше использовать Objective-C с помощью метода mix-and-match.

    /li >
  • Попробуйте выполнить инъекцию кода времени исполнения с помощью "Injection for Xcode"

  • Использовать метод roopc: http://roopc.net/posts/2014/speeding-up-swift-builds/

  • Освободите механизм вывода с быстрым типом, предложив некоторые подсказки с явными нажатиями.

Ответ 13

Быстрое создание массивов и словарей кажется довольно популярной причиной для этого (специально для вас, кто пришел из фона Ruby), то есть

var a = ["a": "b",
         "c": "d",
         "e": "f",
         "g": "h",
         "i": "j",
         "k": "l",
         "m": "n",
         "o": "p",
         "q": "r",
         "s": "t",
         "u": "v",
         "x": "z"]

вероятно, станет причиной, по которой это должно исправить:

var a = NSMutableDictionary()
a["a"] = "b"
a["c"] = "d"
... and so on

Ответ 14

Для отладки и тестирования не забудьте использовать следующие настройки, чтобы сократить время компиляции с 20 минут до менее чем 2 минут,

  • В настройках сборки проекта найдите "Оптимизация", Включите Debug на "Самый быстрый [-O3]" или выше.
  • Установить сборку для активной архитектуры: ДА
  • Формат отладочной информации: DWARF
  • Оптимизация всего модуля: НЕТ

Я потратил немало времени на то, чтобы проект был построен только для того, чтобы понять, что мне пришлось сделать это, и мне пришлось ждать еще 30 минут, чтобы проверить его. Это настройки, которые работали на меня. (Я все еще экспериментирую с настройками)

Но убедитесь, что вы, по крайней мере, установили "DWARF с dSYM" (если вы хотите контролировать приложение) и Build Active Architecture для "NO" для выпуска/архивации, чтобы нажать на iTunes Connect (я помню, что тратил несколько часов здесь слишком).

Ответ 15

Перезагрузка моего Mac сделала чудеса для этой проблемы. Я пошел с 15-минутной сборки до 30 секунд, просто перезагрузив.

Ответ 16

Компилятор тратит много времени на вывод и проверку типов. Таким образом, добавление аннотаций типов помогает компилятору.

Если у вас много целых вызовов функций, таких как

let sum = [1,2,3].map({String($0)}).flatMap({Float($0)}).reduce(0, combine: +)

Затем компилятор занимает некоторое время, чтобы выяснить, какой должен быть тип sum. Добавление типа помогает. То, что также помогает, - это перемещение прерывистых шагов на отдельные переменные.

let numbers: [Int] = [1,2,3]
let strings: [String] = sum.map({String($0)})
let floats: [Float] = strings.flatMap({Float($0)})
let sum: Float = floats.reduce(0, combine: +)

Специально для числовых типов CGFloat, Int он может многое помочь. Литеральное число, подобное 2, может представлять множество различных числовых типов. Поэтому компилятор должен выяснить из контекста, какой он есть.

Также следует избегать функций, требующих много времени для поиска, например, +. Использование нескольких + для конкатенации нескольких массивов происходит медленно, потому что компилятор должен выяснить, какую реализацию + следует вызывать для каждого +. Поэтому используйте var a: [Foo] с append(), если это возможно.

Вы можете добавить предупреждение, чтобы обнаружить какие функции медленно компилируются в Xcode.

В Настройки сборки для целевого поиска Другие быстрые флаги и добавьте

-Xfrontend -warn-long-function-bodies=100

для предупреждения для каждой функции, которая требуется для компиляции более 100 мс.

Ответ 17

Для проектов, которые смешивают Objective-C и код Swift, мы можем установить -enable-bridging-pch в Other Swift Flags. При этом заголовок моста анализируется только один раз, а результат (временный "предварительно скомпилированный заголовок" или "файл PCH" ) кэшируется и повторно используется во всех файлах Swift в целевом объекте. Apple заявила, что сокращает время сборки на 30%. Ссылка ссылки:

ПРИМЕЧАНИЕ. Это работает только для Swift 3.1 и выше.

Ответ 18

Быстрое время компиляции было улучшено в новом Xcode 6.3

Улучшения компилятора

Компилятор Swift 1.2 был спроектирован так, чтобы быть более стабильным и улучшать производительность во всех отношениях. Эти изменения также обеспечивают опыт работы с Swift в Xcode. Некоторые из наиболее заметных улучшения включают:

Инкрементные сборки

Исходные файлы, которые были изменены, больше не будут повторно скомпилированы по умолчанию, что значительно улучшит время сборки для наиболее распространенных случаев. Большие структурные изменения в вашем коде могут по-прежнему требовать несколько файлов, которые нужно перестроить.

Быстрые исполняемые файлы

Отладочные сборки создают двоичные файлы, которые работают значительно быстрее, и новые оптимизация обеспечивает еще более высокую производительность сборки.

Лучшая диагностика компилятора

Более четкие сообщения об ошибках и предупреждения вместе с новым Fix-its, сделайте это проще написать правильный код Swift 1.2.

Улучшения стабильности

Наиболее распространенные сбои компилятора были исправлены. Вы также должны увидеть меньше предупреждений SourceKit в редакторе Xcode.

Ответ 19

Ничто не работало для меня в Xcode 6.3.1 - когда я добавил arround 100 Swift файлов Xcode, случайно зависавших при сборке и/или индексировании. Я пробовал модульную опцию без успеха.

Установка и использование Xcode 6.4 Beta​​strong > действительно сработала для меня.

Ответ 20

Здесь другой случай, который может вызвать массовые спады с типом вывода. Операторы коалесценции.

Изменение строк, таких как:

abs(some_optional_variable ?? 0)

к

abs((some_optional_variable ?? 0) as VARIABLE_TYPE)

помогло мне скомпилировать время от 70 до 13 секунд

Ответ 21

Это работало как магия для меня - Speed ​​Up Swift Compilation. Это сократило время компиляции до 3 минут с 10 минут.

В нем говорится, что вы должны включить Whole Module Optimization, добавив -Onone в Other Swift Flags.

Я использую Swift 3 на Xcode 8.3/Xcode 8.2.