Skip to content

Commit 767ea9e

Browse files
authored
[perf] Manually implement firstIndex(where:) in ByteBufferView (#3412)
### Motivation: `ByteBuffer.firsIndex` is of suboptimal performance. The default Collection implementations don't go through any "magic underscored" functions like `_customIndexOfEquatableElement`. ### Modifications: Manually implement `firstIndex(where:)`. ### Result: Basically free performance boost. 2x+ boost even for not big buffers of a few hundred bytes. There are some usage of this function in `BufferedReader`. Those will become much faster. Also this function is used in `ByteBufferView.trim(limitingElements:)`. Also makes #3411 stuff faster. See: #3411 (comment)
1 parent 86c5ead commit 767ea9e

File tree

2 files changed

+31
-0
lines changed

2 files changed

+31
-0
lines changed

Sources/NIOCore/ByteBuffer-views.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,23 @@ extension ByteBufferView: RangeReplaceableCollection {
242242
self._range = self._range.startIndex..<self._range.endIndex.advanced(by: additionalByteCount)
243243
}
244244
}
245+
246+
/// Returns the first index in which a byte satisfies the given predicate.
247+
///
248+
/// - Parameter predicate: A closure that takes a byte as its argument
249+
/// and returns a Boolean value that indicates whether the passed byte
250+
/// represents a match.
251+
/// - Returns: The index of the first byte for which `predicate` returns
252+
/// `true`. If no bytes in the collection satisfy the given predicate,
253+
/// returns `nil`.
254+
///
255+
/// - Complexity: O(*n*), where *n* is the length of the collection.
256+
@inlinable
257+
public func firstIndex(where predicate: (UInt8) throws -> Bool) rethrows -> Index? {
258+
try self.withUnsafeBytes { ptr in
259+
try ptr.firstIndex(where: predicate).map { $0 + self._range.lowerBound }
260+
}
261+
}
245262
}
246263

247264
extension ByteBuffer {

Tests/NIOCoreTests/ByteBufferTest.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2406,6 +2406,20 @@ class ByteBufferTest: XCTestCase {
24062406
XCTAssertNil(view?.firstIndex(of: UInt8(0x3F)))
24072407
}
24082408

2409+
func testBufferViewFirstIndexWithWhereClosure() {
2410+
self.buf.clear()
2411+
self.buf.writeBytes(Array(repeating: UInt8(0x4E), count: 1024))
2412+
self.buf.setBytes([UInt8(0x59)], at: 1000)
2413+
self.buf.setBytes([UInt8(0x59)], at: 1001)
2414+
self.buf.setBytes([UInt8(0x59)], at: 1022)
2415+
self.buf.setBytes([UInt8(0x59)], at: 3)
2416+
self.buf.setBytes([UInt8(0x3F)], at: 1023)
2417+
self.buf.setBytes([UInt8(0x3F)], at: 2)
2418+
let view = self.buf.viewBytes(at: 5, length: 1010)
2419+
XCTAssertEqual(1000, view?.firstIndex(where: { $0 == UInt8(0x59) }))
2420+
XCTAssertNil(view?.firstIndex(where: { $0 == UInt8(0x3F) }))
2421+
}
2422+
24092423
func testBufferViewLastIndex() {
24102424
self.buf.clear()
24112425
self.buf.writeBytes(Array(repeating: UInt8(0x4E), count: 1024))

0 commit comments

Comments
 (0)