Skip to content

Commit a021c89

Browse files
Kevin Ballardlilyball
Kevin Ballard
authored andcommitted
Implement a custom Data.Iterator
This iterator uses an inline 32-byte buffer so it doesn't have to call copyBytes(to:count:) for every single byte. It results in an approximate 6x speedup on my computer.
1 parent 49b50bc commit a021c89

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

Diff for: stdlib/public/SDK/Foundation/Data.swift

+34-1
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,40 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
647647
///
648648
/// The iterator will increment byte-by-byte.
649649
public func makeIterator() -> Data.Iterator {
650-
return IndexingIterator(_elements: self)
650+
return Iterator(_data: self)
651+
}
652+
653+
public struct Iterator : IteratorProtocol {
654+
private let _data: Data
655+
private var _buffer: (
656+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
657+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
658+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
659+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
660+
private var _idx: Data.Index
661+
private let _endIdx: Data.Index
662+
663+
fileprivate init(_data: Data) {
664+
self._data = _data
665+
_buffer = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
666+
_idx = 0
667+
_endIdx = _data.endIndex
668+
}
669+
670+
public mutating func next() -> UInt8? {
671+
guard _idx < _endIdx else { return nil }
672+
defer { _idx += 1 }
673+
let bufferSize = sizeofValue(_buffer)
674+
return withUnsafeMutablePointer(to: &_buffer) { ptr_ in
675+
let ptr = UnsafeMutableRawPointer(ptr_).assumingMemoryBound(to: UInt8.self)
676+
let bufferIdx = _idx % bufferSize
677+
if bufferIdx == 0 {
678+
// populate the buffer
679+
_data.copyBytes(to: ptr, from: _idx..<(_endIdx - _idx > bufferSize ? _idx + bufferSize : _endIdx))
680+
}
681+
return ptr[bufferIdx]
682+
}
683+
}
651684
}
652685

653686
// MARK: -

Diff for: validation-test/stdlib/Data.swift

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
// REQUIRES: objc_interop
4+
5+
import StdlibUnittest
6+
import StdlibCollectionUnittest
7+
import Foundation
8+
9+
var DataTestSuite = TestSuite("Data")
10+
DataTestSuite.test("Data.Iterator semantics") {
11+
// Empty data
12+
checkSequence([], Data())
13+
14+
// Small data
15+
checkSequence([1,2,4,8,16], Data(bytes: [1,2,4,8,16]))
16+
17+
// Boundary conditions
18+
checkSequence([5], Data(bytes: [5]))
19+
checkSequence(1...31, Data(bytes: Array(1...31)))
20+
checkSequence(1...32, Data(bytes: Array(1...32)))
21+
checkSequence(1...33, Data(bytes: Array(1...33)))
22+
23+
// Large data
24+
var data = Data(count: 65535)
25+
data.withUnsafeMutableBytes { (ptr: UnsafeMutablePointer<UInt8>) -> () in
26+
for i in 0..<data.count {
27+
ptr[i] = UInt8(i % 23)
28+
}
29+
}
30+
checkSequence((0..<65535).lazy.map({ UInt8($0 % 23) }), data)
31+
}
32+
runAllTests()

0 commit comments

Comments
 (0)