Investigating slow builds

What is the best way to diagnose slow build times? I've got an all-Swift Xcode 15 project with around ~350 files and it has become very slow to build, even for incremental builds. The vast majority of files build in the first second or two but there's always 10 or so left over for around another 20 seconds.

I've done pretty much everything outlined in this medium article but I don't seem to see any new timing information anywhere, other than the overall build time, and in the Report seeing that the Comiling Foo.swift, Bar.swift, ... step took pretty much all of the build time. Have those flags been deprecated, or am I just doing something stupid?

Might be useful in identifying where the slowdowns are.

2 Likes

Sadly I've already tried everything in that link. Those compiler flags (-Xfrontend -warn-long-function-bodies=<limit> etc) don't seem to have any effect for me - I've tried reducing the threshold down to basically nothing but still don't see any warnings. I've also added the compiler flag to show build times for individual methods (-Xfrontend -debug-time-function-bodies) but I don't see any results anywhere for this either.

Has it become slow to build just recently? The first thing I would do in such case is to review changes prior to slowdown and look for complex types resolution, e.g. long chain of map/filter/etc. without type annotations, or array/dict literals.

1 Like

Try these settings:
-Xfrontend -warn-long-expression-type-checking=20
-Xfrontend -warn-long-function-bodies=50
-Xfrontend -debug-time-compilation
-Xfrontend -driver-time-compilation

Type checking can often take large amounts of time, e.g.:

  • complex expressions
  • implicit conversion between CGFloat / Double
let diff = abs(UIScreen.main.brightness - value)
let diff: CGFloat = abs(UIScreen.main.brightness - value) // much faster

  • generic functions
    ...

There may also be other reasons, such as assets compilation, build phases and codegen (like Sourcery / SwiftGen / R.Swift), LTO optimisation, build settings, 3d party dependencies, macro etc.

To investigate it faster and reduce the number of questions, is it possible to share build graph and build log?

1 Like

The logs are definitely reporting that it's the compilation step that is taking all the time, not asset catalogs or anything like that, also I don't have any external dependencies or any codegen going on.

This is the setup I've been using...
Imgur
...but I can't find timing information for any specific files or methods anywhere in the logs. Where should the timings appear? Do these settings work for you?

Is your code in one module or are you using packages to split them up? Those flags won't be passed to packages, so if you're modularized like that, you'll need to find a way to pass them to each package individually.

1 Like

It's just one module.

Some additional notes:

  • Storyboard / Xib can also increase time compilation
  • Making properties and Types private / file private
  • mark classes as final

App separation into several modules can also help to reduce build time, but I highly not recommend to do it:

  • unless the root reasons of slow compilation are found
  • if slow compilation is the only reason, as modularisation has its own costs

It's worth to mention that common recommendation is to not care about type checking and use type inference by default. Unfortunately sometimes because of it build time can increase significantly, e.g.:

let isLeftSide = (point + velocity) < (view.bounds.width / 2)
let isLeftSide: Bool = (point + velocity) < (view.bounds.width / CGFloat(2)) // faster

case .hidden: arrowOriginY = -ArrowConstants.arrowHeight
case .hidden: arrowOriginY = ArrowConstants.arrowHeight.negated() // faster

guard let filter = CIFilter(name: "CIPDF417BarcodeGenerator") else {
guard let filter: CIFilter = CIFilter(name: "CIPDF417BarcodeGenerator") else { // faster

1 Like

I've never understood why this form would be faster. Why does an explicit initializer require inference at all?

-Xfrontend warnings will appear in warnings section:
Снимок экрана 2024-04-08 в 18.00.45

This project is an indie game I've been writing, it's all SpriteKit so absolutely minimal storyboard/xib stuff going on. No modules, no external packages or dependencies, no codegen, just very boring, vanilla code!

Given how easy it is to trip the swift compiler into taking a long time to type check, it's a shame and kinda surprising that it's so hard to get diagnostics on build times for specific files or methods (happy to caveat that with it possibly being me being stupid!)

Yeah warnings are empty, even with really short thresholds on the -warn-long-... flags.

That one was unexpected point. I am curious why?

1 Like

Because then compiler can omit some subclass analysis checks as no subclasses are possible.

This recent thread may also be helpful: Needs some guidance in order to let this file compile faster

1 Like

Is there something strange in build graph? I mean this XCode feature


At what build time began slow? Is there a commit in repository with no build time degradation? Git diff can help with analysis direction.

(edited)

So it should be faster? I mean, compiler don’t need to run these optimizations in that case.

Two things jump out: processing the Info.plist shouldn't take 8s, and a 5s, blocking shell script seems suspicious as well. @Pampel you can try turning on the parallel script setting in the project, but really you should see what's going on there.

Yes, it is better to say compiler don’t need to run these checks. Optimisations are another topic, e.g. final classes have static method dispatch as there are no overloads.

Saying "one module" do you mean there is only App-target containing all of the code or there is an App-target that import a framework with the most of codebase?

1 Like