Skip to content

Commit 69c3d71

Browse files
author
Kevin Ballard
committed
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 742f2b4 commit 69c3d71

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

stdlib/public/SDK/Foundation/Data.swift

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

655688
// MARK: -

validation-test/stdlib/Data.swift

Lines changed: 32 additions & 0 deletions
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)