Skip to content

Instantly share code, notes, and snippets.

@macguru
Last active September 27, 2024 14:41
Show Gist options
  • Save macguru/ec5769e2c6bcfd83b6a584f73d85df0f to your computer and use it in GitHub Desktop.
Save macguru/ec5769e2c6bcfd83b6a584f73d85df0f to your computer and use it in GitHub Desktop.
Using SIMD instructions inside a buffer iteration
extension UnsafeMutablePointer<UInt8> {
/// Returns the index of the first element in the buffer that is not `0`
func firstIndexNotZero(size bufferSize: Int) -> Int? {
let stride = MemoryLayout<SIMD16<UInt8>>.stride
let simdSize = bufferSize / stride
// Iterate buffer using 16-component SIMD vectors
let simdIndex: Int? = withMemoryRebound(to: SIMD16<UInt8>.self, capacity: simdSize) { buffer in
let zero = SIMD16<UInt8>(repeating: 0)
return (0..<simdSize).first { buffer[$0] != zero }
}
// At least one byte is non-zero, find it within the chunk
if let simdIndex {
let index = simdIndex * stride
return (index..<index + stride).first { self[$0] != 0 }!
}
// Check any remaining bytes
let simdEnd = simdSize * stride
return (simdEnd..<bufferSize).first { self[$0] != 0 }
}
}
@macguru
Copy link
Author

macguru commented Sep 27, 2024

This function is called like this:

buffer.firstIndexNotZero(size: size)

And returns the same as

(0..<size).first(where: { buffer[$0] != 0 })

The implementation used SIMD instructions to speed up an otherwise really slow operation.

Note: If buffer was an Array not an UnsafeMutablePointer, a more natural way to write this would be buffer.firstIndex { $0 != 0 }. But this has them same runtime penalty of iterating every item separately...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment