# 3x performance regression from Swift 4.2 to 5 — help needed

**URL:** <https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221>\
**Category:** Using Swift\
**Tags:** swift5, performance, regression\
**Created:** [July 23, 2019, 10:52am UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221 "2019-07-23T10:52:50Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![MrMage](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mrmage/32/3829_2.png) [@MrMage](https://forums.swift.org/u/MrMage)\
**Post date:** [July 23, 2019, 10:52am UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/1 "2019-07-23T10:52:50Z")

</div>

I am encountering an ~3-4x performance regression when switching from Swift 4.2 to Swift 5.

Here is an Instruments trace from Swift 4.2:

 ![47](https://global.discourse-cdn.com/swift/original/2X/1/16e9466681581a393a3b3cd39e9b9db619382959.png)

And one from Swift 5:

![29](https://global.discourse-cdn.com/swift/original/2X/c/c1eb03deec2fd0ca11f2a753a5ae85e9a3809496.png)

Notice that equality checks, hashing and object destruction (even for structs and enums) have become much slower with Swift 5, which I assume is related to extra overhead added by ABI stability.

I have tried to reproduce this slowdown with an isolated test case in [https://github.com/MrMage/Swift5PerformanceTest](https://github.com/MrMage/Swift5PerformanceTest) (the Swift5PerformanceTestApp target), but Swift 5 is actually _faster_ there.

The code's task is to take an array of elements combined with a list of associated groups (`ElementWithGroups`), and group that recursively by the list of associated groups. Essentially, it would convert `{element: XYZ, groups: [A, B, C]}` to `{A: {B: {C: [XYZ]}}}` (note that in reality there would be thousands of elements that all have the same set of groups `[A, B, C]`). The actual "group" type (`DetailedGroupSpecifier`) is an enum with lots of cases that have associated values (some of which are `Optional`s), and apparently comparing these enum instances with `==` as part of changing the dictionary's values in the grouping process is slow due to lots of type metadata lookups for the associated values.

A slightly edited version of this code can be found at [https://gist.github.com/MrMage/f96627e354c07cb8ba9246760576c9d9?ts=4](https://gist.github.com/MrMage/f96627e354c07cb8ba9246760576c9d9?ts=4). Note that replacing the custom `==` and `hash(into:)` with synthesized ones does _not_ affect performance. Changing `TaskActivityProperties` et al. to `structs` does not make a difference, either.

Any suggestions as to what I could try in order to improve performance (e.g. specializing the dictionary or avoiding the metadata lookups somehow) would be greatly appreciated.

Thanks,  
Daniel

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [July 23, 2019, 5:16pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/2 "2019-07-23T17:16:10Z")

</div>

In the Swift 5.0 trace, it looks like `Date` is being accessed resiliently, because it has a non-fragile layout in Swift 5. Is it any faster if you store the date locally in a more concrete format, such as an `Int`?

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jrose/32/408_2.png) [@jrose](https://forums.swift.org/u/jrose)\
**Post date:** [July 23, 2019, 6:52pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/3 "2019-07-23T18:52:20Z")

</div>

If it's `Date` specifically, `TimeInterval` is a round-trippable concrete format.

---

<div class="post-metadata">

**Author:** ![MrMage](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mrmage/32/3829_2.png) [@MrMage](https://forums.swift.org/u/MrMage)\
**Post date:** [July 24, 2019, 6:09am UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/4 "2019-07-24T06:09:53Z")

</div>

Wow, replacing `Date` immediately restored performance to almost the previous level. Thank you so much!

The problem is that my app is a time-tracking app and uses `Date`s pervasively throughout the app. Given that the ABI is now stable with Swift 5, I assume we can not expect that `Date` will ever return to be the blazing-fast type it used to be? Are there any "easy" workarounds for this?

Alternatively, I am thinking of creating a simple `FastDate` type that allows fast processing where needed and quick instantiation of the underlying date where needed:

```swift
public struct FastDate: Hashable {
	public let value: TimeInterval

	@inlinable public init(_ value: TimeInterval) {
		self.value = value
	}

	@inlinable public var dateValue: Date { return Date(timeIntervalSince1970: value) }
}

public extension Date {
	@inlinable var fastValue: FastDate { return FastDate(timeIntervalSince1970) }
}

```

I do have a few questions, about this approach:

1. Would using `timeIntervalSinceReferenceDate` instead of `timeIntervalSince1970` be slightly faster, given that Foundation `Dates` are stored relative to the reference date? Would that difference matter in practice?
2. Will the performance penalty be relevant only for the `==` and `hash` methods on `Date`, or for other methods on `Date` as well?
3. What would be the most idiomatic way to convert between `Date` and `FastDate`? `dateValue` and `fastValue` properties as above, or using constructors on `Date` and `FastDate` instead? Those would be slightly more annoying to use with optionals, though — `date.map { FastDate($0) }` instead of `date?.fastValue`.
4. Speaking of optionals: Now that I have more control over the structure of the `FastDate` type, I am thinking of using one of the bits in that type to indicate not being set, letting me avoid an extra byte in `Optional<FastDate>`. Is that at all possible, or are these details hidden by the compiler? Alternatively, I could create a custom `OptionalFastDate` type which uses that bit and only requires 8 bytes for storage. That type could then be converted to `Optional<FastDate>` when the actual value needs to be accessed.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [July 24, 2019, 4:48pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/5 "2019-07-24T16:48:43Z")

</div>

`timeIntervalSinceReferenceDate` is more likely to round-trip cleanly, since the floating-point math to adjust from `timeIntervalSince1970` and back can introduce error. Converting between `Date` and `FastDate` ought to be possible through the reference date.

It's worth asking the Foundation team how important it really is for `Date` to be resilient. @Philippe_Hausler or @millenomi, would we consider making Date frozen in a future release?

(There's also the general issue of compiler quality when it comes to resilient and unspecialized types. Because these currently use a less ideal representation in SIL, they miss out on a lot of optimization passes that would normally reduce copying overhead. That's something we still ought to improve over time; it shouldn't be necessary to make everything frozen just to avoid an order of magnitude performance hit.)

---

<div class="post-metadata">

**Author:** ![Michael\_Gottesman](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/michael_gottesman/32/280_2.png) [@Michael\_Gottesman](https://forums.swift.org/u/Michael_Gottesman)\
**Post date:** [July 24, 2019, 5:12pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/6 "2019-07-24T17:12:22Z")

</div>

@Joe_Groff yes. That overhead I think will go away (or be mitigated a lot) by opaque values.

---

<div class="post-metadata">

**Author:** ![Philippe\_Hausler](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/philippe_hausler/32/22569_2.png) [@Philippe\_Hausler](https://forums.swift.org/u/Philippe_Hausler)\
**Post date:** [July 24, 2019, 5:21pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/7 "2019-07-24T17:21:58Z")

</div>

I dont see a problem with making it frozen, however I this really seems like a compiler regression. @Tony_Parker what do you think? I doubt we can make any changes right now for it, but perhaps this is something that we can address later?

---

<div class="post-metadata">

**Author:** ![Tony\_Parker](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tony_parker/32/273_2.png) [@Tony\_Parker](https://forums.swift.org/u/Tony_Parker)\
**Post date:** [July 24, 2019, 5:24pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/8 "2019-07-24T17:24:20Z")

</div>

I don't have any issue making `Date` frozen. Most of its functions are also probably fine to inline.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [July 24, 2019, 5:27pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/9 "2019-07-24T17:27:15Z")

</div>

One thing we still need is a way to declare a type as frozen as of a specific version, so that the compiler preserves the resilient ABI for existing binaries. I agree that there's compiler work we need to do to make resilient code more efficient as well.

---

<div class="post-metadata">

**Author:** ![MrMage](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mrmage/32/3829_2.png) [@MrMage](https://forums.swift.org/u/MrMage)\
**Post date:** [July 25, 2019, 6:54am UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/10 "2019-07-25T06:54:58Z")

</div>

If/Once such improvements ship, what would be their availability? I am targeting macOS 10.11 and above; would these improvements be available for all my users or only to those running a hypothetical 10.16 and later containing those fixes, given that the Swift stdlib is stuck on 5.0 for users pre-10.15?

Also, is there any kind of bit fiddling I could do to "reserve" one bit inside Date to have an 8-byte "Optional Date"? The problem isn't the extra byte itself, but byte alignment rules actually make that into 16 bytes when used in an array.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [July 25, 2019, 3:21pm UTC](https://forums.swift.org/t/3x-performance-regression-from-swift-4-2-to-5-help-needed/27221/11 "2019-07-25T15:21:03Z")

</div>

You could wrap it in a computed property that manually converts nil to NaN or some other known-invalid value, maybe.
