In cases like this, I always try to reduce it into a minimal complete/self-contained (command line) program that clearly demonstrates the issue.
Your current test depends on an (to readers of this forum) unknown file called: "/Users/me/dev/swift/SwiftGraphs/data/indptrvecs-4m-30m.0based.bin". It would be better if the test just produced some random data instead of this, which would probably also mean that LineReader could be skipped.
Having all the code in a single post, being able to just copy paste it in order to reproduce the issue, makes it much easier for people who wish to look into it.
(Also, by "adding a a property" I assume you mean "adding conformance to a protocol (RandomAccessCollection)"?)
Below is such a test program, measuring the performance of your BitVector's testAndSet() method (which calls its set and get subscript internally).
import AppKit
public struct BitVector {
private var bits: Array<Int>
private let blockSize = Int.bitWidth // NOTE: Should probably be static
public init(repeating: Bool, count: Int) {
let fillInt = repeating ? ~0 : 0
let nBlocks: Int = (count / blockSize) + 1
bits = [Int](repeating: fillInt, count: nBlocks)
}
// Added this for testing:
init(randomBitBlocks blockCount: Int) {
let byteCount = (Int.bitWidth / 8) * blockCount
bits = [Int](repeating: 0, count: blockCount)
let rc = SecRandomCopyBytes(nil, byteCount, &bits)
precondition(rc == errSecSuccess)
}
private func getBlockAndOffset(of bitIndex: Int) -> (Int, Int) {
return (bitIndex / blockSize, bitIndex % blockSize)
}
public var startIndex: Int { return 0 }
public var endIndex: Int { return bits.count * blockSize }
public subscript(_ bitIndex: Int) -> Bool {
get {
let (block, offset) = getBlockAndOffset(of: bitIndex)
let mask = 1 << offset
return mask & bits[block] != 0
}
set {
let (block, offset) = getBlockAndOffset(of: bitIndex)
let mask = 1 << offset
if newValue {
bits[block] |= mask
} else {
bits[block] &= ~mask
}
}
}
public mutating func testAndSet(_ bitIndex: Int) -> Bool {
let (block, offset) = getBlockAndOffset(of: bitIndex)
let mask = 1 << offset
let oldval = mask & bits[block] != 0
if !oldval {
bits[block] |= mask
}
return oldval
}
}
// Comment/uncomment to see if it makes any difference in timings of test below:
// extension BitVector : RandomAccessCollection {}
func test() {
for _ in 0 ..< 5 {
var bv = BitVector(randomBitBlocks: 1_000_000 / Int.bitWidth)
var setCount = 0
let t0 = CACurrentMediaTime()
for i in bv.startIndex ..< bv.endIndex {
setCount += bv.testAndSet(i) ? 1 : 0
}
let t1 = CACurrentMediaTime()
print("time:", t1 - t0, "seconds. (\(setCount))")
}
}
test()
Using Xcode 9.3, and compiling with -O will result in output like the following on my MBP:
time: 0.00946520201978274 seconds. (499910)
time: 0.00749993498902768 seconds. (500297)
time: 0.00683424001908861 seconds. (500176)
time: 0.00753337901551276 seconds. (500429)
time: 0.00689123698975891 seconds. (498780)
And the timings are the same no matter if I comment or uncomment this line:
// extension BitVector : RandomAccessCollection {}
So unfortunately this didn't reproduce the slowdown that you're seeing, but perhaps you can devise a similar little (command line) program that does?