Since we moved parts of our codebase to swift, our compile times have effectively quadrupled. Trying to combat this, we've used the function and expression debug time flags to figure out if there's something to be saved by simplifying expressions.
Apart from the usual suspects (CGFloat / TimeInterval expressions) being slow, this one seems to be not making a lot of sense to us:
It seems like you found your answer. Use the negation operator.
I think the answer is because the equality operator has to be defined for types that are Equatable, so there are a lot of types to check to find the right implementation. Negation has a much smaller number of types to search, so the compiler finds the right method a lot quicker.
At runtime, it's not slow, unless dynamic dispatch comes into play, like any runtime method that can be overridden, and even then it's still pretty quick. Compile time is another matter. Also, testing a boolean for true/false is pretty simple. The equality operator can support some pretty complex equality conditions all bundled up in a simple syntax.
It seems just so unexpected (but very on-brand for swift) to have a simple boolean comparison to take 400ms in compilation time. I mean, the compiler knows both sides are bools so there should be only one candidate == operator implementation, right?
That second option with the coalescing ?? operator is even slower.
It the end it feels very weird to need these kinds of workarounds, just to make our project compile within a reasonable time. I honestly wished swift would focus on stability and performance rather than bolting on more and more features
Do most of these == trigger the flag? It wouldn't be weird for most of the "first appearance" to spend some time with type checking, but I don't think one-time 400ms really amount to much.
There are multiple instances of boolean comparisons being slow in our project. I can easily cut our overal compilation time by 10s by getting rid of explicit comparisons unfortunately
In fact, it does not know that both sides are of type Bool, because == can be implemented to compare heterogeneous types and false can be any type that's expressible by a Boolean literal. Therefore, it has to figure out every possible combination of implementations of == and types conforming to ExpressibleByBooleanLiteral that is available for use here to see if it's a better match.
struct S: ExpressibleByBooleanLiteral {
init(booleanLiteral value: Bool) { }
// Note that this type has no storage.
// It throws away the literal value.
}
func == (lhs: Bool, rhs: S) { print("Hello") }
// Note that this `==` doesn't even return any value.
typealias BooleanLiteralType = S
false == true
// Prints "Hello".
// Note that no actual comparison is being done here.
// `==` is literally just a function that prints "Hello".
For numeric types specifically, there is a hardcoded compiler shortcut to make compile times tolerable until a general solution is discovered. No such optimization is hardcoded for Boolean values because it's not idiomatic to write == true and == false. If you do, it'll still work, but the compiler will look through every possible implementation for the best match. As @jonprescott as said, you've discovered the solution: don't write this.
Even without that (since they're not overloading ==), there are already a few ExpressibleByBooleanLiteral; Bool, ObjCBool, NSDecimalNumber () and its entire hierarchy, including NSNumber, NSValue, and if we include toll-free bridges; Decimal, CFNumber, and CFBoolean. Except for ObjCBool (), all of them are Equatable. Sooo, yeah, that 's quite a few.
== is one of the operators with the most overloads in the standard library (over 70 overloads!), and currently the type checker is not very good at filtering out choices that won't work with the given argument types, so it ends up trying way too many of them even when it has enough information to narrow the choices down. Operator type checking performance is something we're actively working to improve.
@DeskA What is the type of items? If items is a type that conforms to a Collection, Array or Set type-checker should prefer (Bool, Bool) -> Bool overload because it's known that isEmpty is a Bool and default type for false is Bool as well. Since this is clearly not happening I suspect that it has something to do with items.
I certainly don't want to start a debate, but I want to stress out that prefixing a long boolean expression with ! is sometimes less clear than appending == false. Here I think there are several idioms that address several situations.
A fast path for boolean equality would thus be very welcome.
Bool and Bool.==, like all fundamental types, are defined in the standard library and not built into the compiler. The ideal is that anything required to design these fundamental types should be available to everyone.
So again, there is no hardcoded compiler shortcut for overload resolution of Boolean operators. It's a binary operator like any other operator, with a literal operand of unknown type like any other literal operand, and the compiler must evaluate available overloads at the point of use to see which matches best.
When @hborla makes the compiler better at resolving operator overloads generally, then this will get better. But the compiler has no magic here where it knows about which Boolean types exist and have matching operators without actually doing the work of overload resolution.
Just wanna add. There has been some, like preference for +/- with the same type on both sides. Though they are usually for the extremely-common-but-extremely-slow case. These usually are just a stopgap until better system is realized. Hard to say for Bool.== when there's also !.