Very long compilation time (with simple example)

While I modified my app today, I observed it suddenly took very long time to compile (it was so long that I thought Xcode hung, but the compilation succeeded eventually). I checked compilation log, but couldn't find any clue. After a lot of experiments I find I could reproduce the issue with the following code:

func sort4<Value,
           Property1: Comparable,
           Property2: Comparable,
           Property3: Comparable,
           Property4: Comparable>(
    _ a: Value,
    _ b: Value,
    prop1: KeyPath<Value, Property1>,
    comparator1: (Property1, Property1) -> Bool,
    prop2: KeyPath<Value, Property2>,
    comparator2: (Property2, Property2) -> Bool,
    prop3: KeyPath<Value, Property3>,
    comparator3: (Property3, Property3) -> Bool,
    prop4: KeyPath<Value, Property4>,
    comparator4: (Property4, Property4) -> Bool
) -> Bool {
    let aProp1 = a[keyPath: prop1]
    let bProp1 = b[keyPath: prop1]
    if aProp1 != bProp1 {
        return comparator1(aProp1, bProp1)
    }

    let aProp2 = a[keyPath: prop2]
    let bProp2 = b[keyPath: prop2]
    if aProp2 != bProp2 {
        return comparator2(aProp2, bProp2)
    }

    let aProp3 = a[keyPath: prop3]
    let bProp3 = b[keyPath: prop3]
    if aProp3 != bProp3 {
        return comparator3(aProp3, bProp3)
    }

    let aProp4 = a[keyPath: prop4]
    let bProp4 = b[keyPath: prop4]
    return comparator4(aProp4, bProp4)
}

struct Foo {
    static func sort(_ lhs: Self, _ rhs: Self) -> Bool {
        sort4(lhs, rhs,
              prop1: \.a, comparator1: <,
              prop2: \.b, comparator2: <,
              prop3: \.c, comparator1: <,
              prop4: \.d, comparator1: <,)
    }

    var a: Int
    var b: Int
    var c: Int
    var d: Int

    init(_ value: Int) {
        (a, b, c, d) = (value, value, value, value)
    }
}

let foos = [Foo(2), Foo(1)]

let sorted = foos.sorted(by: Foo.sort)
print(sorted)

On the other hand, however, if I reduce the property number to 2, it works fine. See code below.

func sort2<Value,
           Property1: Comparable,
           Property2: Comparable>(
    _ a: Value,
    _ b: Value,
    prop1: KeyPath<Value, Property1>,
    comparator1: (Property1, Property1) -> Bool,
    prop2: KeyPath<Value, Property2>,
    comparator2: (Property2, Property2) -> Bool
) -> Bool {
    let aProp1 = a[keyPath: prop1]
    let bProp1 = b[keyPath: prop1]
    if aProp1 != bProp1 {
        return comparator1(aProp1, bProp1)
    }

    let aProp2 = a[keyPath: prop2]
    let bProp2 = b[keyPath: prop2]
    return comparator2(aProp2, bProp2)
}

struct Foo {
    static func sort(_ lhs: Self, _ rhs: Self) -> Bool {
        sort2(lhs, rhs,
              prop1: \.a, comparator1: <,
              prop2: \.b, comparator2: <)
    }

    var a: Int
    var b: Int

    init(_ value: Int) {
        (a, b) = (value, value)
    }
}

let foos = [Foo(2), Foo(1)]

let sorted = foos.sorted(by: Foo.sort)
print(sorted)

I wonder why such a difference? Is it a bug? I'm using XCode 14.1, which has Swift 5.7.1.

1 Like

It looks like the type system was struggling to resolve the base type of the Keypaths. Annotating them explicitly with Foo (or even Self) resolves the slow compile time.

2 Likes

Can you file this as an issue on GitHub - apple/swift: The Swift Programming Language? This is a great reduced test case.

1 Like

Done. It's issue 62356.

BTW, the example code in my original post has typos in sort4() params. It's not intentional and is not required to reproduce this issue. I have fixed the typos in the bug report on GitHub.

Thanks. It works. I also suspected it was related to type inference and I actually tried it when investigating the issue in my app's code, but unfortunately there were multiple places in my code written this way and I only modified one place so I didn't realize it worked. I have added your workaround to the bug report.


A comment on the compilation log in Xcode. I'm not sure if it's because I don't know how to read the log (it was the first time I did it) or it's a stdout buffer flushing issue, I find the log shown in Xcode can't give accurate information on what exactly the compiler is doing. I end up by doing

$ ps -ef | grep -i swift | less

and searching for ".swift" to figure out which file is being compiled. That helped me to identify the file which slowed down the compilation. I automated the procedure with a one liner script:

$ while true; do pgrep swift | xargs ps -f -p | tr " " "\n" | grep -B 1 "\.swift"; sleep 1; done