Skip to content

Revert formatting performance improvements #744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 0 additions & 33 deletions Benchmarks/Benchmarks/Formatting/BenchmarkFormatting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@

import Benchmark
import func Benchmark.blackHole
import Dispatch

#if FOUNDATION_FRAMEWORK
import Foundation
#else
import FoundationEssentials
import FoundationInternationalization
#endif

let benchmarks = {
Expand Down Expand Up @@ -59,35 +57,4 @@ let benchmarks = {
}
}
}

Benchmark("parallel-number-formatting", configuration: .init(scalingFactor: .kilo)) { benchmark in
for _ in benchmark.scaledIterations {
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
let result = 10.123.formatted()
blackHole(result)
}
}
}

Benchmark("parallel-and-serialized-number-formatting", configuration: .init(scalingFactor: .kilo)) { benchmark in
for _ in benchmark.scaledIterations {
DispatchQueue.concurrentPerform(iterations: 10) { _ in
// Reuse the values on this thread a bunch
for _ in 0..<100 {
let result = 10.123.formatted()
blackHole(result)
}
}
}
}

Benchmark("serialized-number-formatting", configuration: .init(scalingFactor: .kilo)) { benchmark in
for _ in benchmark.scaledIterations {
for _ in 0..<1000 {
let result = 10.123.formatted()
blackHole(result)
}
}
}

}
2 changes: 1 addition & 1 deletion Sources/FoundationEssentials/Calendar/Calendar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public struct Calendar : Hashable, Equatable, Sendable {
///
/// - note: The autoupdating Calendar will only compare equal to another autoupdating Calendar.
public static var autoupdatingCurrent : Calendar {
Calendar(inner: CalendarCache.autoupdatingCurrent)
Calendar(inner: CalendarCache.cache.autoupdatingCurrent)
}

// MARK: -
Expand Down
138 changes: 85 additions & 53 deletions Sources/FoundationEssentials/Calendar/Calendar_Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import CoreFoundation
#endif

/// Singleton which listens for notifications about preference changes for Calendar and holds cached singletons for the current locale, calendar, and time zone.
struct CalendarCache : Sendable, ~Copyable {
struct CalendarCache : Sendable {

// MARK: - Concrete Classes

Expand All @@ -38,71 +38,103 @@ struct CalendarCache : Sendable, ~Copyable {
}
#endif
}

// MARK: - State

static let cache = CalendarCache()

// The values stored in these two locks do not depend upon each other, so it is safe to access them with separate locks. This helps avoids contention on a single lock.

private let _current = LockedState<(any _CalendarProtocol)?>(initialState: nil)
private let _fixed = LockedState<[Calendar.Identifier: any _CalendarProtocol]>(initialState: [:])

fileprivate init() {
}

var current: any _CalendarProtocol {
if let result = _current.withLock({ $0 }) {
return result
struct State : Sendable {
// If nil, the calendar has been invalidated and will be created next time State.current() is called
private var currentCalendar: (any _CalendarProtocol)?
private var autoupdatingCurrentCalendar: _CalendarAutoupdating?
private var fixedCalendars: [Calendar.Identifier: any _CalendarProtocol] = [:]

private var noteCount = -1
private var wasResetManually = false

mutating func check() {
#if FOUNDATION_FRAMEWORK
// On Darwin we listen for certain distributed notifications to reset the current Calendar.
let newNoteCount = _CFLocaleGetNoteCount() + _CFTimeZoneGetNoteCount() + Int(_CFCalendarGetMidnightNoteCount())
#else
let newNoteCount = 1
#endif
if newNoteCount != noteCount || wasResetManually {
// rdar://102017659
// Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
// calendar. Creating the current calendar gets the current locale, decodes a plist
// from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
// leads to a deadlock if we are also initializing a class on the current thread
currentCalendar = nil
fixedCalendars = [:]

noteCount = newNoteCount
wasResetManually = false
}
}

mutating func current() -> any _CalendarProtocol {
check()
if let currentCalendar {
return currentCalendar
} else {
let id = Locale.current._calendarIdentifier
// If we cannot create the right kind of class, we fail immediately here
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
let calendar = calendarClass.init(identifier: id, timeZone: nil, locale: Locale.current, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
currentCalendar = calendar
return calendar
}
}

let id = Locale.current._calendarIdentifier
// If we cannot create the right kind of class, we fail immediately here
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
let calendar = calendarClass.init(identifier: id, timeZone: nil, locale: Locale.current, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)

return _current.withLock {
if let current = $0 {
// Someone beat us to setting it - use the existing one
return current
mutating func autoupdatingCurrent() -> any _CalendarProtocol {
if let autoupdatingCurrentCalendar {
return autoupdatingCurrentCalendar
} else {
$0 = calendar
let calendar = _CalendarAutoupdating()
autoupdatingCurrentCalendar = calendar
return calendar
}
}

mutating func fixed(_ id: Calendar.Identifier) -> any _CalendarProtocol {
check()
if let cached = fixedCalendars[id] {
return cached
} else {
// If we cannot create the right kind of class, we fail immediately here
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
let new = calendarClass.init(identifier: id, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)
fixedCalendars[id] = new
return new
}
}

mutating func reset() {
wasResetManually = true
}
}


let lock: LockedState<State>

static let cache = CalendarCache()

fileprivate init() {
lock = LockedState(initialState: State())
}

func reset() {
// rdar://102017659
// Don't create `currentCalendar` here to avoid deadlocking when retrieving a fixed
// calendar. Creating the current calendar gets the current locale, decodes a plist
// from CFPreferences, and may call +[NSDate initialize] on a separate thread. This
// leads to a deadlock if we are also initializing a class on the current thread
_current.withLock { $0 = nil }
_fixed.withLock { $0 = [:] }
lock.withLock { $0.reset() }
}

var current: any _CalendarProtocol {
lock.withLock { $0.current() }
}

// MARK: Singletons

static let autoupdatingCurrent = _CalendarAutoupdating()

// MARK: -
var autoupdatingCurrent: any _CalendarProtocol {
lock.withLock { $0.autoupdatingCurrent() }
}

func fixed(_ id: Calendar.Identifier) -> any _CalendarProtocol {
if let existing = _fixed.withLock({ $0[id] }) {
return existing
}

// If we cannot create the right kind of class, we fail immediately here
let calendarClass = CalendarCache.calendarICUClass(identifier: id, useGregorian: true)!
let new = calendarClass.init(identifier: id, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil)

return _fixed.withLock {
if let existing = $0[id] {
return existing
} else {
$0[id] = new
return new
}
}
lock.withLock { $0.fixed(id) }
}

func fixed(identifier: Calendar.Identifier, locale: Locale?, timeZone: TimeZone?, firstWeekday: Int?, minimumDaysInFirstWeek: Int?, gregorianStartDate: Date?) -> any _CalendarProtocol {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

package struct FormatterCache<Format : Hashable & Sendable, FormattingType: Sendable>: Sendable {

let countLimit = 100

private let _lock: LockedState<[Format: FormattingType]>
Expand Down
6 changes: 3 additions & 3 deletions Sources/FoundationEssentials/Locale/Locale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public struct Locale : Hashable, Equatable, Sendable {
///
/// - note: The autoupdating Locale will only compare equal to another autoupdating Locale.
public static var autoupdatingCurrent : Locale {
Locale(inner: LocaleCache.autoupdatingCurrent)
Locale(inner: LocaleCache.cache.autoupdatingCurrent)
}

/// Returns the user's current locale.
Expand All @@ -75,12 +75,12 @@ public struct Locale : Hashable, Equatable, Sendable {

/// System locale.
internal static var system : Locale {
Locale(inner: LocaleCache.system)
Locale(inner: LocaleCache.cache.system)
}

/// Unlocalized locale (`en_001`).
internal static var unlocalized : Locale {
Locale(inner: LocaleCache.unlocalized)
Locale(inner: LocaleCache.cache.unlocalized)
}

#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ internal final class _LocaleAutoupdating : _LocaleProtocol, @unchecked Sendable
}

func bridgeToNSLocale() -> NSLocale {
LocaleCache.autoupdatingCurrentNSLocale
LocaleCache.cache.autoupdatingCurrentNSLocale()
}
#endif

Expand Down
Loading