Swift 中数组和链表的性能
我在Twitter上说过:
很多人觉得这句话很奇怪,这让我非常惊讶。相当一部分人建议将
而其他的反馈中,有一部分与我之前发的链表那篇文章有关,为什么去实现一个已经过时的数据结构?我们已经有数组了,它的存在还有什么意义? 所以,你就知道为什么我有时候会特别提到这不只是一个关于 Mac 和 iOS 编程的博客?这当然不是一个只关于 Mac 和 iOS 编程的博客!不要因为我偶然觉得一个包含枚举类型的链表有趣你就把它放到你的 app 里。因为我会对随之而来的性能问题产生兴趣,而你不会。尽管如此,我觉得链表的例子非常有意思,而且值得实现和把玩,它有可能会提升数组
言归正传——有时你会用 extension SequenceType {
func mapUsingReduce<T>(transform: Generator.Element->T) -> [T] {
return reduce([]) { $0 + [transform($1)] }
}
}
作为对比,可以创建可变数组然后使用 extension SequenceType {
func mapUsingFor<T>(transform: Generator.Element->T) -> [T] {
var result: [T] = []
for x in self { result.append(transform(x)) }
return result
}
}
不同点在于,
尽管如此,正常来说人们都不会去重新实现 但是这个和链表又有什么关系?因为你可以利用 上次链表的代码 来实现 extension SequenceType {
func mapToList<T>(transform: Generator.Element->T) -> List<T> {
return reduce(List()) { $0.cons(transform($1)) }.reverse()
}
}
结果就是这个版本的性能竟然只有数组版本的一半(因为 得到这个结果的原因是链表是连续的——旧的链表和新连接的链表之间永远都是用节点相连。所以不需要拷贝。但是代价是只能从头部增加链表的长度(所以才需要 所以数组在 ManagedBuffer
下面的代码实现了一个极简的自销毁数组缓冲区: private class MyArrayBuffer<Element>: ManagedBuffer<Int,Element> {
deinit {
self.withUnsafeMutablePointerToElements { elems->Void in
elems.destroy(self.value)
}
}
}
这样一来, 当缓冲区被析构时, 现在我们可以建立一个数组类型的结构体,这个结构体使用一个私有的缓冲区来进行存储: public struct MyArray<Element> {
private var _buf: MyArrayBuffer<Element>
public init() {
_buf = MyArrayBuffer<Element>.create(8) { _ in 0 } as! MyArrayBuffer<Element>
}
}
我们不直接使用 然后我们让 extension MyArray: CollectionType {
public var startIndex: Int { return 0 }
public var endIndex: Int { return _buf.value }
public subscript(idx: Int) -> Element {
guard idx < self.endIndex else { fatalError("Array index out of range") }
return _buf.withUnsafeMutablePointerToElements { $0[idx] }
}
}
接着,我们需要为缓冲区添加两个相当相似的方法,一个用来拷贝内存,另一个用来调整内存大小。拷贝方法会在检测到共享存储时调用,调整大小方法则会在需要更多内存时调用: extension MyArrayBuffer {
func clone() -> MyArrayBuffer<Element> {
return self.withUnsafeMutablePointerToElements { oldElems->MyArrayBuffer<Element> in
return MyArrayBuffer<Element>.create(self.allocatedElementCount) { newBuf in
newBuf.withUnsafeMutablePointerToElements { newElems->Void in
newElems.initializeFrom(oldElems,count: self.value)
}
return self.value
} as! MyArrayBuffer<Element>
}
}
func resize(newSize: Int) -> MyArrayBuffer<Element> {
return self.withUnsafeMutablePointerToElements { oldElems->MyArrayBuffer<Element> in
let elementCount = self.value
return MyArrayBuffer<Element>.create(newSize) { newBuf in
newBuf.withUnsafeMutablePointerToElements { newElems->Void in
newElems.moveInitializeFrom(oldElems,count: elementCount)
}
self.value = 0
return elementCount
} as! MyArrayBuffer<Element>
}
}
}
同时构建和填充缓冲区是有些苛刻的——首先我们需要获得指向已存在元素的非安全指针,然后调用 这两个方法最主要的不同点是 最后,我们给 extension MyArray {
public mutating func append(x: Element) {
if !isUniquelyReferencedNonObjC(&_buf) {
_buf = _buf.clone()
}
if _buf.allocatedElementCount == count {
_buf = _buf.resize(count*2)
}
_buf.withUnsafeMutablePointers { (val,elems)->Void in
(elems + val.memory++).initialize(x)
}
}
public mutating func extend<S: SequenceType where S.Generator.Element == Element>(seq: S) {
for x in seq { self.append(x) }
}
}
这只是一段样例代码。事实上,你可能会把唯一性判断代码和调整大小代码单独抽出来,这样你就可以在下标集和其他稍微有变化的方法中重用。我懒得写,所以就把他们都塞在 好了,下面就到操作符了。首先, func +=<Element,S: SequenceType where S.Generator.Element == Element>
(inout lhs: MyArray<Element>,rhs: S) { lhs.extend(rhs) }
最后是 func +<Element,S: SequenceType where S.Generator.Element == Element>
(lhs: MyArray<Element>,rhs: S) -> MyArray<Element> { var result = lhs result += rhs return result }
事实上你可以在 func +<Element,S: SequenceType where S.Generator.Element == Element>
(var lhs: MyArray<Element>,rhs: S) -> MyArray<Element> { lhs += rhs return lhs }
之所以有第二个版本是因为有人说 + 操作符可以优化吗?现在我们有了一个完全可用的写时拷贝数组的雏形,你可以对它做 func mapUsingMyReduce<T>(transform: Generator.Element->T) -> MyArray<T> {
return reduce([]) { $0 + [transform($1)] }
}
func mapUsingMyFor<T>(transform: Generator.Element->T) -> MyArray<T> {
var result = MyArray<T>()
for x in self { result.append(transform(x)) }
return result
}
如果你用图表对性能进行记录,你会发现这两段代码和使用数组实现的方式的表现完全类似。 所以,现在的情况是我们拥有一个完全受我们自己控制的实现,我们可以改变 来看一个更简单的例子: var a = MyArray<Int>()
a.extend(0..<3)
let b = a + [6,7,8]
我们可以改变这段代码让它不做拷贝么?很明显我们不能。 检查唯一引用也无济于事。
extension SequenceType {
func myReduce<T>(initial: T,combine: (T,Generator.Element)->T) -> T {
var result = initial
for x in self {
result = combine(result,x)
}
return result
}
}
假设这里的 还有一线希望就是如果每个数组刚好是它们自己的分片(然而并不是——而是有一个叫 也许有一种非常聪明的办法可以解决所有的问题,可能需要编译器的帮助也可能不需要。但尽管如此这仍然不是一个很好的主意。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |