Files
swift-mirror/stdlib/core/SliceBuffer.swift
Dave Abrahams 1fb11d623b [stdlib] Remove _buffer requirement from ArrayType
Until we get an optimizer pass to remove get/set pairs, passing a
property that is a protocol requirement as inout from generic code is
always going to cause an extra retain, causing many unintended Array
copies.

Because this dropped reference counts to 1 in some cases, it exercised
previously-untested code paths and uncovered bugs, particularly in the
handling of subrange replacement on Slice<T>.

There are still differences in speed for short arrays of CGPoint that bear
investigation, but at least as things scale up, the ratio of time goes
to 1.

Fixes <rdar://problem/17040913> append and += on an array have
completely different performance

Swift SVN r19228
2014-06-26 08:54:07 +00:00

264 lines
7.8 KiB
Swift

//===--- SliceBuffer.swift - Backing storage for Slice<T> -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// Buffer type for Slice<T>
@internal struct SliceBuffer<T> : ArrayBufferType {
typealias Element = T
typealias NativeStorage = ContiguousArrayStorage<T>
typealias NativeBuffer = ContiguousArrayBuffer<T>
init(owner: AnyObject?, start: UnsafePointer<T>, count: Int,
hasNativeBuffer: Bool) {
self.owner = owner
self.start = start
self._countAndFlags = (UInt(count) << 1) | (hasNativeBuffer ? 1 : 0)
}
init() {
owner = .None
start = nil
_countAndFlags = 0
_invariantCheck()
}
init(_ buffer: NativeBuffer) {
owner = buffer._storage
start = buffer.elementStorage
_countAndFlags = (UInt(buffer.count) << 1) | (owner ? 1 : 0)
_invariantCheck()
}
func _invariantCheck() {
let isNative = _hasNativeBuffer
_sanityCheck(
(owner as? NativeStorage).getLogicValue() == isNative
)
if isNative {
_sanityCheck(count <= nativeBuffer.count)
}
}
var _hasNativeBuffer: Bool {
_sanityCheck(
owner || (_countAndFlags & 1) == 0,
"Something went wrong: an unowned buffer cannot have a native buffer")
return (_countAndFlags & 1) != 0
}
var nativeBuffer: NativeBuffer {
_sanityCheck(_hasNativeBuffer)
return NativeBuffer(owner as? NativeStorage)
}
/// Replace the given subRange with the first newCount elements of
/// the given collection.
///
/// Requires: this buffer is backed by a uniquely-referenced
/// ContiguousArrayBuffer,
///
/// Requires: insertCount <= numericCast(countElements(newValues))
///
mutating func replace<C: Collection where C.GeneratorType.Element == T>(
#subRange: Range<Int>, with insertCount: Int, elementsOf newValues: C
) {
_invariantCheck()
// FIXME: <rdar://problem/17464946> with
// -DSWIFT_STDLIB_INTERNAL_CHECKS=OFF, enabling this sanityCheck
// actually causes leaks in the stdlib/NewArray.swift.gyb test
/* _sanityCheck(insertCount <= numericCast(countElements(newValues))) */
_sanityCheck(_hasNativeBuffer && isUniquelyReferenced())
var native = reinterpretCast(owner) as NativeBuffer
let offset = start - native.elementStorage
let eraseCount = countElements(subRange)
let growth = insertCount - eraseCount
let oldCount = count
_sanityCheck(native.count + growth <= native.capacity)
native.replace(
subRange: (subRange.startIndex+offset)..<(subRange.endIndex + offset),
with: insertCount,
elementsOf: newValues)
setLocalCount(oldCount + growth)
_invariantCheck()
}
/// A value that identifies first mutable element, if any. Two
/// arrays compare === iff they are both empty, or if their buffers
/// have the same identity and count.
var identity: Word {
return reinterpretCast(start)
}
/// An object that keeps the elements stored in this buffer alive
var owner: AnyObject?
var start: UnsafePointer<T>
var _countAndFlags: UInt
//===--- Non-essential bits ---------------------------------------------===//
func _asCocoaArray() -> _CocoaArray {
_sanityCheck(
isBridgedToObjectiveC(T.self),
"Array element type is not bridged to ObjectiveC")
_invariantCheck()
return _extractOrCopyToNativeArrayBuffer(self)._asCocoaArray()
}
mutating func requestUniqueMutableBackingBuffer(minimumCapacity: Int)
-> NativeBuffer?
{
_invariantCheck()
if _fastPath(_hasNativeBuffer && isUniquelyReferenced()) {
if capacity >= minimumCapacity {
// Since we have the last reference, drop any inaccessible
// trailing elements in the underlying storage. That will
// tend to reduce shuffling of later elements. Since this
// function isn't called for subscripting, this won't slow
// down that case.
var backing = reinterpretCast(owner) as NativeBuffer
let offset = self.elementStorage - backing.elementStorage
let backingCount = backing.count
let myCount = count
if _slowPath(backingCount > myCount + offset) {
backing.replace(
subRange: (myCount+offset)..<backingCount,
with: 0,
elementsOf: EmptyCollection())
}
_invariantCheck()
return backing
}
}
return nil
}
mutating func isMutableAndUniquelyReferenced() -> Bool {
return _hasNativeBuffer && isUniquelyReferenced()
}
/// If this buffer is backed by a ContiguousArrayBuffer, return it.
/// Otherwise, return nil. Note: the result's elementStorage may
/// not match ours, since we are a SliceBuffer.
func requestNativeBuffer() -> ContiguousArrayBuffer<Element>? {
_invariantCheck()
if _fastPath(_hasNativeBuffer) {
return reinterpretCast(owner) as NativeBuffer
}
return nil
}
func _uninitializedCopy(
subRange: Range<Int>, var target: UnsafePointer<T>
) -> UnsafePointer<T> {
_invariantCheck()
_sanityCheck(subRange.startIndex >= 0)
_sanityCheck(subRange.endIndex >= subRange.startIndex)
_sanityCheck(subRange.endIndex <= count)
for i in subRange {
target++.initialize(start[i])
}
return target
}
var elementStorage: UnsafePointer<T> {
return start
}
var count: Int {
get {
return Int(_countAndFlags >> 1)
}
set {
let growth = newValue - count
if growth != 0 {
nativeBuffer.count += growth
setLocalCount(newValue)
}
_invariantCheck()
}
}
/// Modify the count in this buffer without a corresponding change
/// in the underlying nativeBuffer. The implementation of replace()
/// uses this, because it does a wholesale replace in the underlying
/// buffer.
mutating func setLocalCount(newValue: Int) {
_countAndFlags = (UInt(newValue) << 1) | (_countAndFlags & 1)
}
var capacity: Int {
let count = self.count
if _slowPath(!_hasNativeBuffer) {
return count
}
let n = nativeBuffer
if (count + start) == (n.count + n.elementStorage) {
return count + (n.capacity - n.count)
}
return count
}
mutating func isUniquelyReferenced() -> Bool {
return Swift.isUniquelyReferenced(&owner)
}
subscript(i: Int) -> T {
get {
_sanityCheck(i >= 0, "negative slice index is out of range")
_sanityCheck(i < count, "slice index out of range")
return start[i]
}
nonmutating set {
_sanityCheck(i >= 0, "negative slice index is out of range")
_sanityCheck(i < count, "slice index out of range")
start[i] = newValue
}
}
subscript (subRange: Range<Int>) -> SliceBuffer {
_sanityCheck(subRange.startIndex >= 0)
_sanityCheck(subRange.endIndex >= subRange.startIndex)
_sanityCheck(subRange.endIndex <= count)
return SliceBuffer(
owner: owner, start: start + subRange.startIndex,
count: subRange.endIndex - subRange.startIndex,
hasNativeBuffer: _hasNativeBuffer)
}
//===--- Collection conformance -----------------------------------------===//
var startIndex: Int {
return 0
}
var endIndex: Int {
return count
}
func generate() -> IndexingGenerator<SliceBuffer> {
return IndexingGenerator(self)
}
//===--- misc -----------------------------------------------------------===//
func withUnsafePointerToElements<R>(body: (UnsafePointer<T>)->R) -> R {
let start = self.start
return owner ? withExtendedLifetime(owner!) { body(start) } : body(start)
}
}