如何在 Swift 内存中处理对 var 数组的更新(对 CoW 感到困惑)

How are updates to var arrays handled in Swift memory (confused about CoW)

提问人:Parth 提问时间:10/24/2023 最后编辑:Parth 更新时间:10/25/2023 访问量:65

问:

我有这个功能,可以在按降序对数字进行排序时进行快速选择分区。该函数是指在其范围之外定义的函数(分区是另一个函数中的嵌套函数)。arr

我的问题是,由于 Swift 中的数组是值类型,当我进行交换时,每次都会创建一个新副本吗?还是覆盖了相同的内存?

将函数定义为 更好,这样 arr 就可以通过引用传递?partition(start: Int, end: Int, arr: inout [(Int, Int)])

后续问题,当我们这样做时会发生什么?这会在内存中创建一个新副本吗?arr.append((4, 8))

func quickSelect(nums: [Int], k: Int) -> [Int] {

    // ... some logic 

    var arr = [(Int, Int)]() // nums frequencies eg: [(1, 2), (3, 5), (2, 8)]
    func partition(start: Int, end: Int) -> Int {
        var swap = start 

        for i in start..<end {
            if arr[i].1 > arr[end].1 {
                (arr[i], arr[swap]) = (arr[swap], arr[i]) // arr copied on write?
                swap += 1
            }
        }
        (arr[end], arr[swap]) = (arr[swap], arr[end]) // arr copied on write?
        return swap
    }

    // ... some logic 

}
数组 swift memory-leaks pass-by-reference

评论


答:

1赞 Sweeper 10/24/2023 #1

不会在每次交换时都创建副本。

在这种情况下,您似乎误解了 copy-on-write 的含义。我们说 Swift 数组是写入时复制的,并不是因为每次我们写入它时都会创建一个副本。

Copy-on-write 的意思是“写入时复制”,而不是“每当结构被复制时复制”。您可能将其误解为“写入时复制”,而不是“写入时继续使用相同的存储”。如果它以这种方式工作,它就不会被称为“优化”:)

考虑数组的 where 和 are。的内容不会复制到 ,因为没有写入。 并共享相同的内部缓冲区,直到写入其中任何一个,此时将创建一个副本。var foo = barfoobarbarfoofoobarbarfoo

另请参阅以下文档Array

数组,就像标准库中的所有可变大小集合一样, 使用写入时复制优化。阵列的多个副本共享 相同的存储,直到您修改其中一个副本。当这种情况发生时, 被修改的阵列将其存储替换为唯一拥有的 本身的副本,然后就地修改。优化包括 有时应用可以减少复制量。

这意味着,如果一个阵列与其他副本共享存储,则 该数组上的第一个突变操作会产生复制 数组。作为其存储的唯一所有者的阵列可以执行 更改操作到位。

如果像代码中一样,只有一个数组变量,则不需要副本。所有写入都可以写入同一阵列的内部缓冲区。数组的工作方式与任何其他结构体相同 - 其原理与分配结构体的属性 1000 次不会产生该结构体的 1000 个副本相同。

var s = SomeStruct()
for _ in 0..<1000 {
    s.property = ... // this won't copy s 1000 times
}

如果在调用 之前先分配给另一个变量,则在第一次交换时将创建一个副本,之后不会再创建副本。arrpartition

至于调用 append,数组可能需要增加其容量来附加新元素。如果 ,则需要分配新内存。这可能涉及重新定位数组的内容,您可以将其视为“复制”:count == capacity

当数组需要在追加之前重新分配存储或其存储与另一个副本共享时,追加为 O(n),其中 n 是数组的长度。

评论

0赞 Parth 10/25/2023
一个后续问题,每次调用的不是都会调用 get 创建的副本吗?在我的实验中,原始版本确实得到了更新(不使用 )。但我不确定对于值类型如何发生这种情况。它是否有效,因为定义了另一个函数中的函数?所以它有点像类中的函数更新其属性?arrpartitionarrinoutpartitionarr
0赞 Parth 10/25/2023
所以这是我的困惑点,“如果你开始,值类型和引用类型之间的差异就会出现......将其传递给函数”。我正在将 arr 从外部函数传递给分区函数。那么分区不会收到副本吗?我在分区函数内部进行的更新如何反映在它外部(在调用分区的外部函数中)?
0赞 Parth 10/25/2023
如果是引用类型,我不会有同样的混淆,因为会更新其外部范围对 arr 的相同引用。然后对我来说,所做的更新反映在外部范围内是有道理的。arrpartitionpartition
0赞 Sweeper 10/25/2023
@Parth啊,我明白了。不,你不是“传递给. 只接受 2 个 int 参数,没有一个参数是数组。好的,所以嵌套在另一个函数中。我没有意识到这一点。这只是意味着编译器必须秘密地为数组创建一个新参数,但这是一个实现细节。它可能通过传递数组 byref 来做到这一点,但它也可能做其他事情。在语言级别,其行为与数组是全局变量相同。arrpartitionpartitionpartition
1赞 Sweeper 10/25/2023
@Parth似乎您的问题不再只是关于数组,而是一般的值类型。请不要把这变成变色龙的问题