Last active
May 6, 2024 18:30
-
-
Save dabrahams/9c5aec89c1daa2b64360782533f63118 to your computer and use it in GitHub Desktop.
How to efficiently create a mutable projection without inducing CoW
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Returns `(source, transform(source.pointee))` and destroys `source.pointee`. | |
/// | |
/// This is a low-level utility for creating mutable projections of values without | |
/// causing needless copy-on-write. | |
/// | |
/// Typical usage: | |
/// | |
/// func f(x: inout X) { // inout means we have exclusive access to `x`. | |
/// var (xAddress, xFrobnication) | |
/// = unsafeMoveMap(destroying: &x) { x.frobnication() } | |
/// | |
/// // `xFrobnication` is a mutable projection of `x` | |
/// somethingWith(&xfrobnication) // mutate it | |
/// | |
/// xAddress.initialize(to: X(defrobnicating: xFrobnication)) | |
/// } | |
/// | |
private func unsafeMoveMap<T, U>( | |
destroying source: UnsafeMutablePointer<T>, transform: (T)->U | |
) -> (address: UnsafeMutablePointer<T>, value: U) | |
{ | |
return (source, transform(source.move())) | |
} | |
/// A nominal version of the tuple type that is the `Element` of | |
/// `Swift.Dictionary`. | |
public struct KeyValuePair<Key, Value> { | |
/// Creates an instance with the given key and value. | |
public init(key: Key, value: Value) { | |
(self.key, self.value) = (key, value) | |
} | |
public var key: Key | |
public var value: Value | |
} | |
/// Interop with the corresponding non-nominal tuple | |
extension KeyValuePair { | |
/// Constructs a nominally-typed version of `x`. | |
public init(_ x: (key: Key, value: Value)) { | |
self.init(key: x.key, value: x.value) | |
} | |
/// A read/write projection of `self` | |
public var tuple: (key: Key, value: Value) { | |
get { (key, value) } | |
set { | |
key = newValue.key | |
value = newValue.value | |
} | |
// Comment out the _modify accessor to experience the CoW. | |
_modify { | |
// The LOE says nothing else can touch `self` while we're in _modify, so | |
// we can safely destroy it as long as we put it back before we're done. | |
var (p, kv) = unsafeMoveMap(destroying: &self) { $0.tuple } | |
defer { p.initialize(to: .init(kv)) } | |
yield &kv | |
} | |
} | |
} | |
/// Demonstration; optimization not required. | |
func address<T>(_ p: UnsafeMutablePointer<T>) -> Int { | |
return .init(bitPattern: p) | |
} | |
func test1(_ kv: inout KeyValuePair<Int, [Int]>) { | |
let before = kv.value.withUnsafeMutableBufferPointer { $0.baseAddress! } | |
kv.tuple.value[0] = 3 | |
let after = kv.value.withUnsafeMutableBufferPointer { $0.baseAddress! } | |
if before != after { print("CoW occurred.") } | |
else { print("CoW avoided") } | |
} | |
func test() { | |
var kv = KeyValuePair(key: 3, value: [1, 2, 3]) | |
test1(&kv) | |
if kv.value[0] != 3 { print("Something went wrong.") } | |
} | |
test() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment