Skip to content

Commit 94adc6a

Browse files
Improve performance for calling Locale.identifier from NS/CFLocale (#1051)
- use @_effects(releasenone) to solemnly swear to the compiler that we won't release anything inside these getters, eliminating refcounting to call them - micro-optimize identifierDoesNotRequireSpecialCaseHandling, allowing us to delete the cache of it without regressing perf On the test app this goes from "so slow I didn't bother to see how long it takes to finish" to "finishes in around 1 second" Resolves 137885111 Co-authored-by: David (Swift) Smith <[email protected]>
1 parent 1fff4b7 commit 94adc6a

File tree

3 files changed

+19
-23
lines changed

3 files changed

+19
-23
lines changed

Sources/FoundationEssentials/Locale/Locale.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ public struct Locale : Hashable, Equatable, Sendable {
232232

233233
/// Returns the identifier of the locale.
234234
public var identifier: String {
235-
_locale.identifier
235+
@_effects(releasenone)
236+
get {
237+
_locale.identifier
238+
}
236239
}
237240

238241
/// Returns the language code of the locale, or nil if has none.
@@ -743,12 +746,15 @@ public struct Locale : Hashable, Equatable, Sendable {
743746
/// - "el": Greek
744747
/// For all other locales such as en_US, this is `false`.
745748
package static func identifierDoesNotRequireSpecialCaseHandling(_ identifier: String) -> Bool {
746-
guard identifier.count >= 2 else { return true }
747-
748-
let first = identifier.prefix(2)
749-
switch first {
750-
case "az", "lt", "tr", "nl", "el":
751-
return false // Does require special handling
749+
var byteIterator = identifier.utf8.makeIterator()
750+
switch (byteIterator.next(), byteIterator.next()) {
751+
case
752+
(UInt8(ascii: "a"), UInt8(ascii: "z")),
753+
(UInt8(ascii: "l"), UInt8(ascii: "t")),
754+
(UInt8(ascii: "t"), UInt8(ascii: "r")),
755+
(UInt8(ascii: "n"), UInt8(ascii: "l")),
756+
(UInt8(ascii: "e"), UInt8(ascii: "l")):
757+
return false
752758
default:
753759
return true // Does not require special handling
754760
}

Sources/FoundationEssentials/Locale/Locale_Unlocalized.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,6 @@ internal final class _LocaleUnlocalized : _LocaleProtocol, @unchecked Sendable {
257257
identifier
258258
}
259259

260-
var doesNotRequireSpecialCaseHandling: Bool {
261-
true
262-
}
263-
264260
#if FOUNDATION_FRAMEWORK
265261
func pref(for key: String) -> Any? {
266262
nil

Sources/FoundationInternationalization/Locale/Locale_ObjC.swift

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,6 @@ extension NSLocale {
218218

219219
@objc(_doesNotRequireSpecialCaseHandling)
220220
func _doesNotRequireSpecialCaseHandling() -> Bool {
221-
// Unable to use cached locale; create a new one. Subclass `_NSSwiftLocale` implements a better version
222221
Locale.identifierDoesNotRequireSpecialCaseHandling(localeIdentifier)
223222
}
224223
}
@@ -228,12 +227,9 @@ extension NSLocale {
228227
@objc(_NSSwiftLocale)
229228
internal class _NSSwiftLocale: _NSLocaleBridge, @unchecked Sendable {
230229
var locale: Locale
231-
var doesNotRequireSpecialHandling: Bool?
232230

233231
internal init(_ locale: Locale) {
234232
self.locale = locale
235-
// We cannot call `locale.identifier` to get the actual value here because that could trigger a recursive call into LocaleCache. If we enter from this `init` just lazily fetch the variable.
236-
self.doesNotRequireSpecialHandling = nil
237233

238234
// The superclass does not care at all what the identifier is. Avoid a potentially recursive call into the Locale cache here by just using an empty string.
239235
super.init(localeIdentifier: "")
@@ -251,7 +247,6 @@ internal class _NSSwiftLocale: _NSLocaleBridge, @unchecked Sendable {
251247

252248
override init(localeIdentifier string: String) {
253249
self.locale = Locale(identifier: string)
254-
self.doesNotRequireSpecialHandling = Locale.identifierDoesNotRequireSpecialCaseHandling(string)
255250
super.init(localeIdentifier: "")
256251
}
257252

@@ -271,7 +266,6 @@ internal class _NSSwiftLocale: _NSLocaleBridge, @unchecked Sendable {
271266
}
272267

273268
locale = Locale(identifier: ident)
274-
doesNotRequireSpecialHandling = Locale.identifierDoesNotRequireSpecialCaseHandling(ident)
275269

276270
// Must call a DI; this one does nothing so it's safe to call here.
277271
super.init(localeIdentifier: "")
@@ -366,7 +360,10 @@ internal class _NSSwiftLocale: _NSLocaleBridge, @unchecked Sendable {
366360
// MARK: -
367361

368362
override var localeIdentifier: String {
369-
locale.identifier
363+
@_effects(releasenone)
364+
get {
365+
return locale.identifier
366+
}
370367
}
371368

372369
@available(macOS, deprecated: 13) @available(iOS, deprecated: 16) @available(tvOS, deprecated: 16) @available(watchOS, deprecated: 9)
@@ -526,12 +523,9 @@ internal class _NSSwiftLocale: _NSLocaleBridge, @unchecked Sendable {
526523
return _NSSwiftLocale(copy)
527524
}
528525

526+
@_effects(releasenone)
529527
override func _doesNotRequireSpecialCaseHandling() -> Bool {
530-
if let doesNotRequireSpecialHandling {
531-
return doesNotRequireSpecialHandling
532-
}
533-
534-
return Locale.identifierDoesNotRequireSpecialCaseHandling(locale.identifier)
528+
Locale.identifierDoesNotRequireSpecialCaseHandling(locale.identifier)
535529
}
536530
}
537531

0 commit comments

Comments
 (0)