compilation - loop - swift syntax
¿Por qué es tan lento el tiempo de compilación Swift? (21)
Probablemente no podamos arreglar el compilador Swift, ¡pero algo que podemos arreglar es nuestro código!
Hay una opción oculta en el compilador Swift que imprime los intervalos de tiempo exactos que el compilador toma para compilar cada función: -Xfrontend -debug-time-function-bodies
. Nos permite encontrar cuellos de botella en nuestro código y mejorar significativamente el tiempo de compilación.
Simplemente ejecuta lo siguiente en el terminal y analiza los resultados:
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
El increíble Brian Irace escribió un brillante artículo sobre el tema Perfilando tus tiempos de compilación Swift .
Estoy usando Xcode 6 Beta 6.
Esto es algo que me ha estado molestando desde hace algún tiempo, pero está llegando a un punto en el que apenas se puede usar ahora.
Mi proyecto está comenzando a tener un tamaño decente de 65 archivos Swift y algunos archivos Objective-C con puentes (que en realidad no son la causa del problema).
Parece que cualquier modificación leve en cualquier archivo Swift (como agregar un simple espacio en blanco en una clase que apenas se usa en la aplicación) hará que todos los archivos Swift para el objetivo especificado se vuelvan a compilar.
Después de una investigación más profunda, descubrí que lo que toma casi el 100% del tiempo del compilador es la fase CompileSwift
, donde Xcode ejecuta el comando swiftc
en todos los archivos Swift de su objetivo.
Investigué un poco más, y si solo mantengo el delegado de la aplicación con un controlador predeterminado, la compilación es muy rápida, pero a medida que iba agregando más y más archivos de mis proyectos, el tiempo de compilación comenzó a ser realmente lento.
Ahora, con solo 65 archivos de origen, se tarda aproximadamente 8/10 segundos en compilarse cada vez. No muy rápido en absoluto.
No he visto ninguna publicación que hable sobre este problema, excepto esta , pero era una versión antigua de Xcode 6. Así que me pregunto si soy la única en ese caso.
ACTUALIZAR
He comprobado algunos proyectos Swift en GitHub como Alamofire , Euler y CryptoSwift , pero ninguno de ellos tenía suficientes archivos Swift para compararlos. El único proyecto que encontré que estaba empezando a tener un tamaño decente era SwiftHN , y aunque solo tenía una docena de archivos de origen, aún podía verificar lo mismo, un espacio simple y todo el proyecto necesitaba una recompilación que estaba comenzando a tomar una Poco tiempo (2/3 segundos).
Comparado con el código de Objective-C, donde tanto el analizador como la compilación son muy rápidos, parece que Swift nunca podrá manejar grandes proyectos, pero, por favor, dígame que estoy equivocado.
ACTUALIZACIÓN con Xcode 6 Beta 7
Todavía no hay mejora alguna. Esto está empezando a ser ridículo. Con la falta de #import
en Swift, realmente no veo cómo Apple podrá optimizar esto alguna vez.
ACTUALIZACIÓN con Xcode 6.3 y Swift 1.2
Apple ha agregado compilaciones incrementales (y muchas otras optimizaciones del compilador). Tiene que migrar su código a Swift 1.2 para ver esos beneficios, pero Apple agregó una herramienta en Xcode 6.3 para ayudarlo a hacerlo:
SIN EMBARGO
No te regocijes demasiado rápido como lo hice yo. El solucionador de gráficos que utilizan para hacer la construcción incremental aún no está muy bien optimizado.
De hecho, en primer lugar, no observa los cambios en la firma de la función, por lo que si agrega un espacio en el bloque de un método, todos los archivos que dependan de esa clase serán recompilados.
En segundo lugar, parece crear el árbol basado en los archivos que fueron recompilados, incluso si un cambio no los afecta. Por ejemplo, si mueves estas tres clases a diferentes archivos
class FileA: NSObject {
var foo:String?
}
class FileB: NSObject {
var bar:FileA?
}
class FileC: NSObject {
var baz:FileB?
}
Ahora, si modifica FileA
, el compilador obviamente marcará FileA
para volver a compilarlo. También recompilará FileB
(que estaría bien en función de los cambios realizados en FileA
), pero también en FileC
porque FileB
está recompilado, y eso es bastante malo porque FileC
nunca usa FileA
aquí.
Así que espero que mejoren el solucionador de árboles de dependencias ... He abierto un radar con este código de muestra.
ACTUALIZACIÓN Con Xcode 7 beta 5 y Swift 2.0
Ayer Apple lanzó la beta 5 y dentro de las notas de lanzamiento pudimos ver:
Swift Language & Compiler • Compilaciones incrementales: cambiar solo el cuerpo de una función ya no debería hacer que los archivos dependientes sean reconstruidos. (15352929)
Lo he intentado y debo decir que está funcionando realmente (¡realmente!) Bien ahora. Ellos optimizaron enormemente las construcciones incrementales en swift.
Le recomiendo que cree una rama swift2.0
y mantenga su código actualizado con XCode 7 beta 5. Le complacerán las mejoras del compilador (sin embargo, diría que el estado global de XCode 7 sigue siendo lento y con errores) )
ACTUALIZACIÓN con Xcode 8.2
Ha pasado un tiempo desde mi última actualización sobre este tema, así que aquí está.
Nuestra aplicación ahora tiene alrededor de 20 mil líneas de código Swift casi exclusivamente, lo cual es decente pero no excepcional. Sufrió 2 rápidos y luego 3 rápidos de migración. Se tarda aproximadamente 5 / 6m en compilar en una Macbook pro de mediados de 2014 (Intel Core i7 a 2,5 GHz), lo que está bien en una versión limpia.
Sin embargo, la construcción incremental sigue siendo una broma a pesar de que Apple afirma que:
Xcode no reconstruirá un objetivo completo cuando solo se hayan producido pequeños cambios. (28892475)
Obviamente, creo que muchos de nosotros simplemente nos reímos después de revisar esta tontería (agregar una propiedad privada (¡privada!) A cualquier archivo de mi proyecto recompilará todo el asunto ...)
Quisiera señalarles este hilo en los foros de desarrolladores de Apple que tienen más información sobre el tema (así como también una apreciada comunicación de desarrollo de Apple de vez en cuando)
Básicamente, las personas han ideado algunas cosas para tratar de mejorar la construcción incremental:
- Agregue una
HEADER_MAP_USES_VFS
proyectoHEADER_MAP_USES_VFS
establecida entrue
- Deshabilita
Find implicit dependencies
de tu esquema - Cree un nuevo proyecto y mueva su jerarquía de archivos a la nueva.
Intentaré la solución 3 pero la solución 1/2 no funcionó para nosotros.
Lo que es irónicamente divertido en toda esta situación es que al mirar la primera publicación sobre este problema, estábamos usando Xcode 6 con el código Swift 1 o Swift 1.1 cuando alcanzamos la primera inactividad de las compilaciones y ahora, aproximadamente dos años después, a pesar de las mejoras reales de Apple. La situación es tan mala como lo fue con Xcode 6. Qué irónico.
De hecho, realmente lamento haber elegido Swift sobre Obj / C para nuestro proyecto debido a la frustración diaria que conlleva. (Incluso me cambio a AppCode pero esa es otra historia)
De todos modos, veo que esta publicación SO tiene más de 32k puntos de vista y 143 subidas al momento de escribir esto, así que supongo que no soy el único. Quédate ahí, a pesar de ser pesimistas sobre esta situación, podría haber algo de luz al final del túnel.
Si tienes tiempo (y coraje), supongo que Apple agradece al radar sobre esto.
Hasta la próxima! Aclamaciones
ACTUALIZACIÓN con Xcode 9
Tropezar con this hoy. Xcode introdujo silenciosamente un nuevo sistema de compilación para mejorar el horrible rendimiento actual. Tienes que habilitarlo a través de la configuración del espacio de trabajo.
Ya lo he intentado, pero actualizaré esta publicación una vez que haya terminado. Parece prometedor sin embargo.
Aquí hay otro caso que puede causar desaceleraciones masivas con inferencia de tipo. Operadores coalescentes .
Cambiando líneas como:
abs(some_optional_variable ?? 0)
a
abs((some_optional_variable ?? 0) as VARIABLE_TYPE)
Ayudó a traer mi tiempo de compilación de 70s a 13s
Bueno, resultó que Rob Napier tenía razón. Fue un solo archivo (en realidad un método) lo que causó que el compilador se fuera a berzek.
Ahora no me malinterpretes Swift recompila todos sus archivos cada vez, pero lo mejor ahora es que Apple agregó comentarios de compilación en tiempo real sobre los archivos que compila, por lo que Xcode 6 GM ahora muestra qué archivos Swift se están compilando y el estado de la compilación en tiempo real Como puedes ver en esta captura de pantalla:
Así que esto es muy útil para saber cuál de tus archivos está tardando tanto. En mi caso fue esta pieza de código:
var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
"url" : self.url?.absoluteString ?? "",
"title" : self.title ?? ""
])
return dic.copy() as NSDictionary
porque el title
la propiedad era de tipo var title:String?
y no NSString
. El compilador se estaba volviendo loco al agregarlo al NSMutableDictionary
.
Cambiándolo a
var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
"url" : self.url?.absoluteString ?? "",
"title" : NSString(string: self.title ?? "")
])
return dic.copy() as NSDictionary
hizo que la compilación pasara de 10/15 segundos (tal vez incluso más) a un solo segundo ... increíble.
Dado que todo esto está en Beta, y como el compilador Swift (al menos a partir de hoy) no está abierto, supongo que no hay una respuesta real a tu pregunta.
En primer lugar, comparar compilador de Objective-C con Swift es de alguna manera cruel. Swift aún se encuentra en Beta, y estoy seguro de que Apple está trabajando para brindar funcionalidad y corregir errores, más que proporcionar una velocidad de rayo (no empiezas a construir una casa comprando los muebles). Supongo que Apple optimizará el compilador a su debido tiempo.
Si, por algún motivo, todos los archivos de origen deben compilarse por completo, una opción podría ser crear módulos / bibliotecas separados. Pero esta opción aún no es posible, ya que Swift no puede permitir bibliotecas hasta que el idioma sea estable.
Mi conjetura es que van a optimizar el compilador. Por la misma razón que no podemos crear módulos precompilados, podría ser que el compilador necesite compilar todo desde cero. Pero una vez que el lenguaje llegue a una versión estable y el formato de los binarios ya no cambie, podremos crear nuestras bibliotecas, y quizás (?) El compilador también podrá optimizar su trabajo.
Sin embargo, solo adivinando, solo Apple lo sabe ...
Desafortunadamente, el compilador Swift todavía no está optimizado para una compilación rápida e incremental (a partir de Xcode 6.3 beta). Mientras tanto, puedes usar algunas de las siguientes técnicas para mejorar el tiempo de compilación de Swift:
Dividir la aplicación en marcos para reducir el impacto de recompilación. Pero tenga en cuenta que debe evitar las dependencias cíclicas en su aplicación. Para obtener más información sobre este tema, consulte esta publicación: http://bits.citrusbyte.com/improving-swift-compile-time/
Use Swift para partes de su proyecto que sean bastante estables y que no cambien a menudo. Para otras áreas en las que necesita cambiar con mucha frecuencia o áreas que requieren muchas iteraciones de compilación / ejecución para completarlas (casi cualquier elemento relacionado con la interfaz de usuario), use Objective-C mejor con un enfoque de combinación y combinación.
Pruebe la inyección de código en tiempo de ejecución con ''Injection for Xcode''
Utilice el método roopc: http://roopc.net/posts/2014/speeding-up-swift-builds/
Alivie el motor de inferencia de tipo rápido dando algunos consejos con conversiones explícitas.
El compilador pasa mucho tiempo inferiendo y revisando los tipos. Así que agregar anotaciones de tipo ayuda mucho al compilador.
Si tienes muchas llamadas a funciones encadenadas como
let sum = [1,2,3].map({String($0)}).flatMap({Float($0)}).reduce(0, combine: +)
Luego, el compilador toma un tiempo para averiguar cuál debería ser el tipo de sum
. Añadiendo el tipo de ayuda. Lo que también ayuda es jalar los pasos intermitentes en variables separadas.
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: +)
Especialmente para los tipos numéricos CGFloat
, Int
puede ayudar mucho. Un número literal como 2
puede representar muchos tipos numéricos diferentes. Así que el compilador necesita averiguar a partir del contexto cuál es.
Las funciones que tardan mucho tiempo en verse como +
también deben evitarse. El uso de varios +
para concatenar varios arreglos es lento porque el compilador necesita averiguar a qué implementación de +
debe llamar para cada +
. Así que usa una var a: [Foo]
con append()
en su lugar si es posible.
Puede agregar una advertencia para detectar qué funciones son lentas para compilar en Xcode .
En Configuración de compilación para su búsqueda de objetivos para Otros indicadores Swift y agregue
-Xfrontend -warn-long-function-bodies=100
para avisar por cada función que demore más de 100 ms en compilar.
El reinicio de mi Mac hizo maravillas para este problema. Pasé de compilaciones de 15 minutos a versiones de 30 segundos simplemente reiniciando.
El tiempo de compilación Swift fue mejorado en el nuevo Xcode 6.3
Mejoras del compilador
El compilador Swift 1.2 fue diseñado para ser más estable y mejorar el rendimiento en todos los aspectos. Estos cambios también proporcionan una mejor experiencia cuando se trabaja con Swift en Xcode. Algunas de las mejoras más visibles incluyen:
Construcciones incrementales
Los archivos de origen que no han cambiado ya no se volverán a compilar de forma predeterminada, lo que mejorará significativamente los tiempos de compilación para la mayoría de los casos comunes. Los cambios estructurales más grandes en su código pueden requerir que se reconstruyan múltiples archivos.
Ejecutables mas rapidos
Las compilaciones de depuración producen binarios que se ejecutan considerablemente más rápido, y las nuevas optimizaciones ofrecen un rendimiento de compilación de lanzamiento aún mejor.
Mejores diagnósticos del compilador.
Los mensajes de error y advertencia más claros, junto con los nuevos Fix-its, facilitan la escritura del código Swift 1.2 correcto.
Mejoras de estabilidad
Los fallos de compilador más comunes han sido corregidos. También deberías ver menos advertencias de SourceKit dentro del editor de Xcode.
En mi caso, Xcode 7 no hizo ninguna diferencia. Tuve múltiples funciones que requieren varios segundos para compilar.
Ejemplo
// Build time: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)
Después de desenvolver las opciones, el tiempo de construcción se redujo en un 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)
Ver más ejemplos en este post y este post .
Build Time Analyzer para Xcode
Desarrollé un complemento Xcode que puede ser útil para cualquier persona que experimente estos problemas.
Parece que hay mejoras en Swift 3, así que espero que podamos ver nuestro código Swift compilado más rápido.
Es probable que tenga poco que ver con el tamaño de su proyecto. Es probable que sea una pieza específica de código, posiblemente incluso una sola línea. Puede probar esto intentando compilar un archivo a la vez en lugar de todo el proyecto. O intente ver los registros de compilación para ver qué archivo está tomando tanto tiempo.
Como ejemplo de los tipos de código que pueden causar problemas, esta idea de 38 líneas demora más de un minuto en compilarse en beta7. Todo esto es causado por este bloque:
let pipeResult =
seq |> filter~~ { $0 % 2 == 0 }
|> sorted~~ { $1 < $0 }
|> map~~ { $0.description }
|> joinedWithCommas
Simplifica eso con solo una línea o dos y compila casi instantáneamente. El problema es que algo de esto está causando un crecimiento exponencial (posiblemente un crecimiento factorial) en el compilador. Obviamente, eso no es lo ideal, y si puede aislar tales situaciones, debe abrir radares para ayudar a solucionar esos problemas.
Esto ha estado funcionando como magia para mí: acelerar la compilación Swift . Redujo el tiempo de compilación a 3 minutos a partir de 10 minutos.
Dice que debe activar la Whole Module Optimization
al agregar -Onone
in Other Swift Flags
.
Estoy usando Swift 3
en Xcode 8.3
/ Xcode 8.2
.
Hemos intentado bastantes cosas para combatir esto, ya que tenemos alrededor de 100 mil líneas de código Swift y 300 mil líneas de código ObjC.
Nuestro primer paso fue optimizar todas las funciones de acuerdo con la función de compilación de salida (por ejemplo, como se describe aquí https://thatthinginswift.com/debug-long-compile-times-swift/ )
Luego, escribimos un script para fusionar todos los archivos swift en un solo archivo, esto rompe los niveles de acceso pero trajo nuestro tiempo de compilación de 5-6min a ~ 1minute.
Esto ya no existe porque le preguntamos a Apple sobre esto y nos aconsejaron que hiciéramos lo siguiente:
- Active la ''optimización del módulo completo'' en la configuración de compilación ''Swift Compiler - Code Generation''. Seleccione
''Fast, Whole Module Optimization''
- En ''Swift Compiler - Custom Flags'', para sus compilaciones de desarrollo, agregue
''-Onone''
Cuando se establecen estos indicadores, el compilador compilará todos los archivos Swift en un solo paso. Encontramos que con nuestro script de combinación esto es mucho más rápido que compilar archivos individualmente. Sin embargo, sin la -Onone''
'' -Onone''
, también optimizará todo el módulo, que es más lento. Cuando establecemos el ''-Onone''
en los otros indicadores Swift, detiene la optimización, pero no deja de compilar todos los archivos Swift en un solo paso.
Para obtener más información sobre la optimización del módulo completo, consulte la publicación del blog de Apple aquí: https://swift.org/blog/whole-module-optimizations/
Hemos encontrado que esta configuración permite que nuestro código Swift se compile en 30 segundos :-) No tengo evidencia de cómo funcionaría en otros proyectos, pero sugiero intentarlo si los tiempos de compilación Swift siguen siendo un problema para usted.
Tenga en cuenta que para las compilaciones de la App Store, debe dejar el ''-Onone''
, ya que se recomienda la optimización para las compilaciones de producción.
La solución está echando.
Tenía una gran cantidad de toneladas de diccionarios, como este:
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
.....
Le tomó aproximadamente 40 minutos compilarlo. Hasta que lancé los diccionarios como este:
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
....
Esto funcionó para casi todos los demás problemas que encontré con respecto a los tipos de datos que codifiqué en mi aplicación.
Los arreglos rápidos y la construcción de diccionarios parecen ser una causa bastante popular para esto (especialmente para ustedes que provienen de un fondo Ruby ), es decir,
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"]
Probablemente sea la causa donde esto debería arreglarlo:
var a = NSMutableDictionary()
a["a"] = "b"
a["c"] = "d"
... and so on
Nada me funcionó en Xcode 6.3.1: cuando agregué alrededor de 100 archivos Swift Xcode colgado aleatoriamente en la compilación y / o la indexación. He intentado una opción modular sin éxito.
Instalar y usar Xcode 6.4 Beta realmente funcionó para mí.
Para Xcode 8, vaya a la configuración del proyecto, luego Editor> Agregar configuración de compilación> Agregar configuración definida por el usuario, y agregue lo siguiente:
SWIFT_WHOLE_MODULE_OPTIMIZATION = YES
Al agregar este indicador, nuestros tiempos de compilación de construcción limpia se redujeron de 7 minutos a 65 años para un proyecto swift de 40KLOC, milagrosamente. También se puede confirmar que 2 amigos han visto mejoras similares en proyectos empresariales.
Solo puedo asumir que esto es algún tipo de error en Xcode 8.0
EDIT: Parece que ya no funciona en Xcode 8.3 para algunas personas.
Para la depuración y la prueba, asegúrese de usar la siguiente configuración para reducir el tiempo de compilación de unos 20 minutos a menos de 2 minutos,
- En la configuración de compilación del proyecto, busque "Optimización". Gire la depuración a "Más rápido [-O3]" o superior.
- Set Build para Active Architecture: YES
- Formato de información de depuración: DWARF
- Optimización del módulo completo: NO
Perdí innumerables horas esperando que el proyecto se construyera solo para darme cuenta de que tenía que hacer un pequeño cambio y tuve que esperar otros 30 minutos para probarlo. Estos son los ajustes que funcionaron para mí. (Todavía estoy experimentando con la configuración)
Pero, asegúrese de al menos configurar "DWARF con dSYM" (si desea monitorear su aplicación) y Construir la Arquitectura Activa en "NO" para que la versión / archivo se presione en iTunes Connect (también recuerdo haber perdido algunas horas aquí).
Para los proyectos que combinan código de Objective-C y Swift, podemos establecer -enable-bridging-pch
en Other Swift Flags
. Con esto, el encabezado puente se analiza solo una vez, y el resultado (un "encabezado precompilado" o archivo "PCH" temporal) se almacena en caché y se reutiliza en todos los archivos Swift en el destino. Apple afirmó que reduce el tiempo de construcción en un 30%. Link de referencia:
NOTA: Esto funciona solo para Swift 3.1 y superior.
Si está intentando identificar archivos específicos que ralentizan su tiempo de compilación, puede intentar compilarlos desde su línea de comandos a través de xctool que le dará tiempos de compilación archivo por archivo.
Lo que hay que tener en cuenta es que, de forma predeterminada, genera 2 archivos al mismo tiempo por cada núcleo de CPU, y no le dará el tiempo "neto" transcurrido, sino el tiempo absoluto de "usuario". De esta manera todos los tiempos se igualan entre archivos paralelizados y se ven muy similares.
Para superar esto, establezca el indicador -jobs
en 1 , para que no -jobs
las -jobs
archivos. Tomará más tiempo, pero al final tendrá tiempos de compilación "netos" que puede comparar archivo por archivo.
Este es un comando de ejemplo que debería hacer el truco:
xctool -workspace <your_workspace> -scheme <your_scheme> -jobs 1 build
La salida de la fase "Compilar archivos Swift" sería algo como:
...
✓ Compile EntityObserver.swift (1623 ms)
✓ Compile Session.swift (1526 ms)
✓ Compile SearchComposer.swift (1556 ms)
...
Desde esta salida, puede identificar rápidamente qué archivos demoran más que otros en compilar. Además, puede determinar con alta precisión si sus refactorizaciones (conversiones explícitas, sugerencias de tipo, etc.) están reduciendo los tiempos de compilación para archivos específicos o no.
NOTA: técnicamente también podría hacerlo con xcodebuild
pero la salida es increíblemente detallada y difícil de consumir.
También asegúrese de que al compilar para la depuración (ya sea Swift o Objective-C), establezca Generar solo arquitectura activa:
Una cosa a tener en cuenta es que el motor de inferencia de tipos Swift puede ser muy lento con los tipos anidados. Puede obtener una idea general acerca de lo que está causando la lentitud al observar el registro de compilación para las unidades de compilación individuales que llevan mucho tiempo y luego copiar y pegar el comando completo generado por Xcode en una ventana de Terminal y luego presionar CTRL- para obtener Algunos diagnósticos. Eche un vistazo a http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times para ver un ejemplo completo.