Skip to content

Commit 2a16d3d

Browse files
authored
Use one allocation for the bespoke Mutex's storage. (#1584)
We currently use two allocations to store the contents of our bespoke Apple-only `Mutex` reimplementation: one for the `os_unfair_lock` and the other for the stored value. We can instead use a single allocation with a bit of careful pointer arithmetic to ensure there's enough storage for both and that it's well-aligned. This change has the effect of reducing the size of `Mutex` by a word. It also reduces the amount of memory allocated by some vague amount because on Darwin, the minimum allocation size is 16 but the size of `os_unfair_lock` is 4. I place the lock _after_ the value because its alignment will often be smaller, so fewer padding bytes will be needed between the value and the lock than between the lock and the value. At compile time in release mode, all specializations are known and the extra math is optimized away. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 5eb07e9 commit 2a16d3d

1 file changed

Lines changed: 23 additions & 7 deletions

File tree

Sources/Testing/Support/Additions/MutexAdditions.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,32 +40,48 @@ struct Mutex<Value>: Sendable, ~Copyable where Value: ~Copyable {
4040
private typealias _Lock = pthread_mutex_t
4141
#endif
4242

43+
/// Storage for both the lock and value.
44+
private nonisolated(unsafe) let _baseAddress: UnsafeMutableRawPointer
45+
46+
/// The offset of `_lockAddress` from `_baseAddress`.
47+
private static var _lockOffset: Int {
48+
let p = UnsafeRawPointer(bitPattern: MemoryLayout<Value>.stride)!
49+
return Int(bitPattern: p.alignedUp(for: _Lock.self))
50+
}
51+
4352
/// Storage for the underlying lock.
44-
private nonisolated(unsafe) let _lockAddress: UnsafeMutablePointer<_Lock>
53+
private var _lockAddress: UnsafeMutablePointer<_Lock> {
54+
(_baseAddress + Self._lockOffset).assumingMemoryBound(to: _Lock.self)
55+
}
4556

4657
/// Storage for the value this instance guards.
47-
private nonisolated(unsafe) let _valueAddress: UnsafeMutablePointer<Value>
58+
private var _valueAddress: UnsafeMutablePointer<Value> {
59+
_baseAddress.assumingMemoryBound(to: Value.self)
60+
}
61+
62+
63+
init(_ initialValue: consuming sending Value) {
64+
_baseAddress = .allocate(
65+
byteCount: Self._lockOffset + MemoryLayout<_Lock>.stride,
66+
alignment: max(MemoryLayout<Value>.alignment, MemoryLayout<_Lock>.alignment)
67+
)
4868

49-
public init(_ initialValue: consuming sending Value) {
50-
_lockAddress = .allocate(capacity: 1)
5169
#if !SWT_NO_OS_UNFAIR_LOCK
5270
_lockAddress.initialize(to: .init())
5371
#else
5472
_ = pthread_mutex_init(_lockAddress, nil)
5573
#endif
56-
_valueAddress = .allocate(capacity: 1)
5774
_valueAddress.initialize(to: initialValue)
5875
}
5976

6077
deinit {
6178
_valueAddress.deinitialize(count: 1)
62-
_valueAddress.deallocate()
6379
#if !SWT_NO_OS_UNFAIR_LOCK
6480
_lockAddress.deinitialize(count: 1)
6581
#else
6682
_ = pthread_mutex_destroy(_lockAddress)
6783
#endif
68-
_lockAddress.deallocate()
84+
_baseAddress.deallocate()
6985
}
7086

7187
/// Acquire the lock.

0 commit comments

Comments
 (0)