mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
The case where Array stores class instances no longer requires an intermediate indirect buffer object. Also fixes <rdar://problem/17348939> These are the speed changes < 0.95x and > 1.05x, as I measured them. Although I don't have great confidence in these numbers, some are consistent with Arnold's measurements, FWIW. -O: 0.93 ArrayLiteral 1.56 Ary 1.39 Ary2 1.06 CaptureProp 1.93 ClassArrayGetter 1.08 DeltaBlue 1.08 DollarChain 1.13 InsertionSort 1.08 PrimeNum 1.11 RC4 0.93 Rectangles 1.08 SwiftStructuresBubbleSort -Onone: 1.06 ArrayLiteral 1.10 ArraySubscript 1.12 Ary 1.12 Ary2 1.10 Ary3 1.20 ClassArrayGetter 1.19 DeltaBlue 1.08 DollarChain 1.09 Hash 1.07 Havlak 1.10 HeapSort 1.11 ImageProc 1.17 InsertionSort 1.18 Memset 1.11 NBody 1.07 PrimeNum 1.11 QuickSort 1.12 RC4 1.08 Rectangles 1.07 SelectionSort 1.06 StringBuilder 1.12 SwiftStructuresBubbleSort 1.10 Walsh 1.12 XorLoop -Ounchecked: 0.91 ArrayLiteral 1.74 Ary 1.53 Ary2 2.08 ClassArrayGetter 1.19 DeltaBlue 1.06 DollarChain 1.07 Havlak 1.09 ImageProc 1.22 InsertionSort 0.89 PopFrontArrayGeneric 1.11 PrimeNum 0.85 QuickSort 1.10 RC4 1.12 Rectangles 1.06 StrToInt 1.11 SwiftStructuresBubbleSort Swift SVN r23551
514 lines
14 KiB
Swift
514 lines
14 KiB
Swift
%# -*- mode: swift -*-
|
|
//===--- Array.swift ------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
// RUN-DISABLED: %target-run-simple-swift | FileCheck %s
|
|
// RUN: rm -rf %t && mkdir -p %t && %S/../../utils/gyb %s -o %t/NewArray.swift
|
|
// RUN: %S/../../utils/line-directive %t/NewArray.swift -- %target-build-swift -module-cache-path %t/clang-module-cache %t/NewArray.swift -o %t/a.out -Xfrontend -disable-access-control
|
|
// RUN: %target-run %t/a.out 2>&1 | %S/../../utils/line-directive %t/NewArray.swift -- FileCheck %t/NewArray.swift
|
|
|
|
var xCount = 0
|
|
var xSerial = 0
|
|
|
|
import StdlibUnittest
|
|
|
|
// Instead of testing with Int elements, we use this wrapper class
|
|
// that can help us track allocations and find issues with object
|
|
// lifetime inside Array implementations.
|
|
final class X : ForwardIndexType, Comparable, Printable,
|
|
IntegerLiteralConvertible
|
|
{
|
|
required init(_ value: Int) {
|
|
++xCount
|
|
serial = ++xSerial
|
|
self.value = value
|
|
}
|
|
|
|
deinit {
|
|
assert(serial > 0, "double destruction!")
|
|
--xCount
|
|
serial = -serial
|
|
}
|
|
|
|
var description: String {
|
|
assert(serial > 0, "dead X!")
|
|
return value.description
|
|
}
|
|
|
|
func successor() -> Self {
|
|
return self.dynamicType(self.value.successor())
|
|
}
|
|
|
|
convenience init(integerLiteral value: Int) {
|
|
self.init(value)
|
|
}
|
|
|
|
var value: Int
|
|
var serial: Int
|
|
}
|
|
|
|
func == (x: X, y: X) -> Bool {
|
|
return x.value == y.value
|
|
}
|
|
|
|
func < (x: X, y: X) -> Bool {
|
|
return x.value < y.value
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
func printSequence<T: SequenceType>(x: T) {
|
|
print("[")
|
|
var prefix = ""
|
|
for a in x {
|
|
print(prefix)
|
|
print(a)
|
|
prefix = ", "
|
|
}
|
|
println("]")
|
|
}
|
|
|
|
typealias BufferID = UnsafePointer<Void>
|
|
|
|
func bufferID<T : _ArrayType>(x: T) -> BufferID {
|
|
return x._buffer.identity
|
|
}
|
|
|
|
func checkReallocation<T : _ArrayType>(
|
|
x: T, lastBuffer: BufferID, reallocationExpected: Bool
|
|
) -> BufferID {
|
|
let currentBuffer = bufferID(x)
|
|
if (currentBuffer != lastBuffer) != reallocationExpected {
|
|
let message = reallocationExpected ? "lack of" : ""
|
|
println("unexpected \(message) reallocation")
|
|
}
|
|
return currentBuffer
|
|
}
|
|
|
|
func checkEqual<
|
|
S1 : SequenceType, S2 : SequenceType
|
|
where
|
|
S1.Generator.Element == S2.Generator.Element,
|
|
S1.Generator.Element : Equatable
|
|
>(a1: S1, a2: S2, expected: Bool) {
|
|
if equal(a1, a2) != expected {
|
|
let un = expected ? "un" : ""
|
|
println("unexpectedly \(un)equal sequences!")
|
|
}
|
|
}
|
|
|
|
func test<
|
|
T: _ArrayType
|
|
where T.Generator.Element == T._Buffer.Element,
|
|
T._Buffer.Element == T.Element,
|
|
T.Element == X,
|
|
T.Index == Int
|
|
>(_: T.Type, label: String) {
|
|
print("test: \(label)...")
|
|
|
|
var x: T = [1, 2, 3, 4, 5]
|
|
|
|
checkEqual(x, 1...5, true)
|
|
|
|
x.reserveCapacity(x.count + 2)
|
|
checkEqual(x, 1...5, true)
|
|
|
|
let bufferId0 = bufferID(x)
|
|
|
|
// Append a range of integers
|
|
x += 0..<2
|
|
let bufferId1 = checkReallocation(x, bufferId0, false)
|
|
|
|
for i in x.count..<(x.capacity + 1) {
|
|
let bufferId1a = checkReallocation(x, bufferId1, false)
|
|
x.append(13)
|
|
}
|
|
let bufferId2 = checkReallocation(x, bufferId1, true)
|
|
|
|
let y = x
|
|
x[x.endIndex.predecessor()] = 17
|
|
let bufferId3 = checkReallocation(x, bufferId2, true)
|
|
checkEqual(x, y, false)
|
|
|
|
func checkReallocations(
|
|
var a: T, growthDescription: String, growBy1: (inout _: T)->()
|
|
) -> () {
|
|
var reallocations = 0
|
|
|
|
// Note: right now this test is dependent on a growth factor of 2.
|
|
// It's possible that factor will change, but (cursory) testing
|
|
// has shown that using 1.5, the other popular growth factor,
|
|
// slows things down.
|
|
for _ in a.count..<(a.capacity * 4) {
|
|
let oldId = bufferID(a)
|
|
growBy1(&a)
|
|
if oldId != bufferID(a) {
|
|
++reallocations
|
|
}
|
|
}
|
|
|
|
if reallocations > 3 {
|
|
println(
|
|
"Unexpectedly found \(reallocations) reallocations "
|
|
+ "of \(label) when growing via \(growthDescription)")
|
|
}
|
|
}
|
|
|
|
checkReallocations(x, "append") { (inout x: T)->() in x.append(42) }
|
|
checkReallocations(x, "+=") { (inout x: T)->() in x.append(42) }
|
|
println("done.")
|
|
}
|
|
|
|
println("testing...")
|
|
// CHECK: testing...
|
|
|
|
test(ContiguousArray<X>.self, "ContiguousArray")
|
|
// CHECK-NEXT: test: ContiguousArray...done
|
|
|
|
test(_UnitTestArray<X>.self, "_UnitTestArray")
|
|
// CHECK-NEXT: test: _UnitTestArray...done
|
|
|
|
test(Array<X>.self, "Array")
|
|
// CHECK-NEXT: test: Array...done
|
|
|
|
test(Slice<X>.self, "Slice")
|
|
// CHECK-NEXT: test: Slice...done
|
|
|
|
func testAsArray() {
|
|
println("== AsArray ==")
|
|
var w: ContiguousArray<X> = [4, 2, 1]
|
|
// CHECK: == AsArray ==
|
|
|
|
let x = ContiguousArray(w)
|
|
println(bufferID(w) == bufferID(x))
|
|
// CHECK-NEXT: true
|
|
|
|
let y = Array(x)
|
|
println(bufferID(x) == bufferID(y))
|
|
// CHECK-NEXT: true
|
|
|
|
// Because of their indirection, arrays of classes can share
|
|
// buffers.
|
|
let y1 = Array(y)
|
|
println(bufferID(y1) == bufferID(y))
|
|
// CHECK-NEXT: true
|
|
|
|
let z = Slice(y)
|
|
println(bufferID(y) == bufferID(z))
|
|
// CHECK-NEXT: true
|
|
|
|
w = ContiguousArray(z)
|
|
println(bufferID(w) == bufferID(z))
|
|
// CHECK-NEXT: true
|
|
|
|
let zz = y[0..<2]
|
|
println(bufferID(zz))
|
|
// CHECK-NEXT: 0x
|
|
}
|
|
testAsArray()
|
|
|
|
import Foundation
|
|
|
|
func nsArrayOfStrings() -> Array<NSString> {
|
|
let src: ContiguousArray<NSString> = ["foo", "bar", "baz"]
|
|
|
|
return src.withUnsafeBufferPointer {
|
|
let ns = NSArray(objects: UnsafePointer($0.baseAddress), count: $0.count)
|
|
return _convertNSArrayToArray(ns)
|
|
}
|
|
}
|
|
|
|
func testCocoa() {
|
|
println("== Cocoa ==")
|
|
// CHECK: == Cocoa ==
|
|
|
|
var a = nsArrayOfStrings()
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
a.append("qux")
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz, qux]
|
|
|
|
a = nsArrayOfStrings()
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
var b = a
|
|
|
|
a[1] = "garply"
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, garply, baz]
|
|
|
|
// Mutating an element in a has no effect on b
|
|
printSequence(b)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
a = nsArrayOfStrings()
|
|
a.insert("bag", atIndex:2)
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, bag, baz]
|
|
|
|
a = nsArrayOfStrings()
|
|
a.reserveCapacity(30)
|
|
printSequence(a)
|
|
// CHECK-NEXT: [foo, bar, baz]
|
|
|
|
println(a.capacity >= 30)
|
|
// CHECK-NEXT: true
|
|
|
|
// Prove that we create contiguous storage for an opaque NSArray
|
|
a.withUnsafeBufferPointer {
|
|
(p)->() in
|
|
println(p[0])
|
|
// CHECK-NEXT: foo
|
|
}
|
|
}
|
|
testCocoa()
|
|
|
|
extension Slice {
|
|
mutating func qsort(compare: (T,T)->Bool) {
|
|
_introSort(&self, indices(self), compare)
|
|
}
|
|
}
|
|
|
|
func testSlice() {
|
|
println("== Slice ==")
|
|
// CHECK: == Slice ==
|
|
|
|
// do some tests on the shared semantics
|
|
var b = ContiguousArray(X(0)..<X(7))
|
|
|
|
// Slice it
|
|
var bSlice = b[3..<5]
|
|
println("<\(bSlice.count)>")
|
|
// CHECK-NEXT: <2>
|
|
println("bSlice0: \(bSlice)") // CHECK-NEXT: bSlice0: [3, 4]
|
|
|
|
// bSlice += X(11)..<X(13)
|
|
|
|
// Writing into b does not change bSlice
|
|
b[4] = 41
|
|
println("bSlice1: \(bSlice)") // CHECK-NEXT: bSlice1: [3, 4]
|
|
|
|
// Writing into bSlice does not change b
|
|
bSlice[1] = 42
|
|
|
|
|
|
println("bSlice2: \(bSlice)") // CHECK-NEXT: bSlice2: [3, 42]
|
|
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6]
|
|
|
|
var c = b
|
|
b[4..<b.count].qsort(<)
|
|
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 5, 6, 41]
|
|
printSequence(c) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6]
|
|
|
|
// Now a bridged slice
|
|
var a = Array<NSString>(
|
|
_ArrayBuffer(nsArray: nsArrayOfStrings()._buffer._asCocoaArray()))
|
|
|
|
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
|
|
|
|
var aSlice = a[1..<3] // CHECK-NEXT: [bar, baz]
|
|
printSequence(aSlice)
|
|
|
|
// Writing into aSlice works
|
|
aSlice[0] = "buzz" // CHECK-NEXT: [buzz, baz]
|
|
printSequence(aSlice)
|
|
|
|
// ...and doesn't affect a
|
|
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
|
|
|
|
// Appending to aSlice works...
|
|
aSlice.append("fodder")
|
|
println("<\(aSlice.count)>") // CHECK-NEXT: <3>
|
|
printSequence(aSlice) // CHECK-NEXT: [buzz, baz, fodder]
|
|
|
|
// And doesn't change a
|
|
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
|
|
}
|
|
testSlice()
|
|
|
|
//===--- sub-range replacement --------------------------------------------===//
|
|
|
|
// Size of the array on which we're going to test "replace."
|
|
// testing time grows roughly as the cube of this constant
|
|
let testWidth = 11
|
|
|
|
%arrayTypes = ['ContiguousArray', '_UnitTestArray', 'Array', 'Slice']
|
|
%for A in arrayTypes:
|
|
|
|
func testReplace(make: ()->${A}<X>) {
|
|
|
|
checkRangeReplaceable(make, { X(100)..<X(100 + $0) })
|
|
}
|
|
|
|
func testReplace${A}(
|
|
makeOne: ()->${A}<X> = {
|
|
var x = ${A}<X>()
|
|
// make sure some - but not all - replacements will have to grow the buffer
|
|
x.reserveCapacity(testWidth * 3 / 2)
|
|
x += X(0)..<X(testWidth)
|
|
return x
|
|
}
|
|
) {
|
|
testReplace(makeOne)
|
|
|
|
// Create one that will not be uniquely-referenced so we can test
|
|
// the out-of-place code paths.
|
|
let r = makeOne()
|
|
testReplace({ r })
|
|
|
|
// This test should ensure r's retain isn't dropped before we start testing.
|
|
if (r.count != testWidth) {
|
|
println("something bad happened!")
|
|
}
|
|
}
|
|
|
|
println("testing subrange replacement in ${A}")
|
|
testReplace${A}()
|
|
%end
|
|
|
|
// Also test with a sub-slice of some larger buffer. The "trailing"
|
|
// case is interesting because when the buffer is uniquely referenced
|
|
// we can expect to expand the slice in-place
|
|
for (maxValue, label) in [(testWidth, "trailing"), (testWidth*2, "interior")] {
|
|
println("testing subrange replacement in \(label) Sub-Slice")
|
|
testReplaceSlice {
|
|
var a = ContiguousArray(X(-testWidth)..<X(maxValue))
|
|
a.reserveCapacity(a.count * 3 / 2)
|
|
return a[testWidth..<(2 * testWidth)]
|
|
}
|
|
}
|
|
|
|
// CHECK-NEXT: testing subrange replacement in ContiguousArray
|
|
// CHECK-NEXT: testing subrange replacement in _UnitTestArray
|
|
// CHECK-NEXT: testing subrange replacement in Array
|
|
// CHECK-NEXT: testing subrange replacement in Slice
|
|
// CHECK-NEXT: testing subrange replacement in trailing Sub-Slice
|
|
// CHECK-NEXT: testing subrange replacement in interior Sub-Slice
|
|
|
|
//===--- inout violations -------------------------------------------------===//
|
|
|
|
// The user has to obey certain rules when things are passed via
|
|
// inout, but in those cases we only guarantee memory-safety, not
|
|
// coherent semantics. Let's try to force a memory-safety problem
|
|
// here. This crashes when withUnsafeMutableBufferPointer is not
|
|
// sufficiently careful.
|
|
func testInoutViolation() {
|
|
var a: [X] = [
|
|
X(10), X(8), X(6), X(4), X(2), X(0), X(9), X(7), X(5), X(3), X(1)
|
|
]
|
|
|
|
%for A in arrayTypes:
|
|
if true {
|
|
var b = ${A}(a)
|
|
b.sort { x, y in
|
|
b.removeAll()
|
|
return x < y
|
|
}
|
|
}
|
|
%end
|
|
|
|
// An overload of sort for Arrays uses withUnsafeMutableBufferPointer,
|
|
// which disables bounds checks.
|
|
sort(&a) { x, y in
|
|
a = [] // Invalidate the whole array during sorting
|
|
return x < y
|
|
}
|
|
}
|
|
testInoutViolation()
|
|
|
|
//===--- single-element modifiers -----------------------------------------===//
|
|
|
|
%for A in arrayTypes:
|
|
|
|
func testSingleElementModifiers${A}() {
|
|
println("testing ${A} single-argument modifiers")
|
|
// CHECK-NEXT: testing ${A} single-argument modifiers
|
|
|
|
var a = ${A}(X(0)..<10)
|
|
println(a.removeLast().value) // CHECK-NEXT: 9
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
|
|
|
a.insert(42, atIndex: 4)
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
|
|
|
|
println(a.removeAtIndex(2).value) // CHECK-NEXT: 2
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
|
|
}
|
|
testSingleElementModifiers${A}()
|
|
%end
|
|
|
|
//===--- isEmpty, first, last ---------------------------------------------===//
|
|
|
|
%for A in arrayTypes:
|
|
|
|
func testIsEmptyFirstLast${A}() {
|
|
println("testing ${A} isEmpty, first, and last")
|
|
// CHECK-NEXT: testing ${A} isEmpty, first, and last
|
|
|
|
println(${A}<Int>().isEmpty) // CHECK-NEXT: true
|
|
println(${A}(42...42).isEmpty) // CHECK-NEXT: false
|
|
|
|
println("<\(${A}(3...42).first!)>") // CHECK-NEXT: <3>
|
|
println("<\(${A}(3...42).last!)>") // CHECK-NEXT: <42>
|
|
|
|
println("<\(${A}<Int>().first)>") // CHECK-NEXT: nil
|
|
println("<\(${A}<Int>().last)>") // CHECK-NEXT: nil
|
|
|
|
var a = ${A}(X(0)..<10)
|
|
println(a.removeLast().value) // CHECK-NEXT: 9
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
|
|
|
|
a.insert(42, atIndex: 4)
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
|
|
|
|
println(a.removeAtIndex(2).value) // CHECK-NEXT: 2
|
|
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
|
|
}
|
|
testIsEmptyFirstLast${A}()
|
|
%end
|
|
|
|
//===--- Regression Tests -------------------------------------------------===//
|
|
func rdar16958865() {
|
|
var a: [Int] = []
|
|
a += SequenceOf([ 42, 4242 ])
|
|
// CHECK-NEXT: [42, 4242]
|
|
println(a)
|
|
}
|
|
rdar16958865()
|
|
|
|
import SpriteKit
|
|
|
|
class Rdar16914909 : NSObject {
|
|
var basicColorSet = [SKColor]()
|
|
|
|
func doColorStuff() {
|
|
basicColorSet.append(SKColor.lightGrayColor())
|
|
println("appended")
|
|
}
|
|
}
|
|
|
|
Rdar16914909().doColorStuff()
|
|
// CHECK-NEXT: appended
|
|
|
|
// FIXME: should be "leaks = 0", if not for <rdar://problem/17892507>
|
|
println("leaks = \(xCount)")
|
|
// CHECK-NEXT: leaks =
|
|
|
|
// CHECK-NEXT: all done.
|
|
println("all done.")
|
|
|
|
// ${'Local Variables'}:
|
|
// eval: (read-only-mode 1)
|
|
// End:
|