Files
swift-nio/Sources/NIOCore/ByteBuffer-int.swift
Johannes Weiss 0b65385fad improve inEventLoop checks (#3302)
### Motivation:

`inEventLoop` is very much in the performance path of SwiftNIO,
especially these days with Concurrency, `NIOLoopBound` and friends.
Previously, we relied on `pthread_equal(pthread_self(), myPthread)`,
however, this could cause a number of issues.

1. Holding onto a `pthread_t` after `.join` is actually illegal (fixed
in #3297)
2. Potential ABA issues when `pthread_t` pointer are re-used for new
pthreads
3. Fix would require a lock around `myPthread` which makes things (2x
slower, even without contention)

### Modifications:

- New type `SelectableEventLoopUniqueID` which can be packed into a
`UInt`
- Attach them into a C thread local

### Result:

- Even faster than the old, incorrect version
- old: `measuring: el_in_eventloop_100M: 0.257395375, 0.241049208,
0.243188792, 0.259125916, 0.24843225, 0.229690125, 0.244281541,
0.225078834, 0.236395, 0.233305167`
- new: `measuring: el_in_eventloop_100M: 0.175561125, 0.187225625,
0.199269375, 0.19740975, 0.1922695, 0.179850958, 0.177612458,
0.17665125, 0.17897475, 0.18038775`
- More correct
- Groundwork to make #3297 not make things slower
2025-07-14 17:15:10 +01:00

222 lines
7.9 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
extension ByteBuffer {
@inlinable
func _toEndianness<T: FixedWidthInteger>(value: T, endianness: Endianness) -> T {
switch endianness {
case .little:
return value.littleEndian
case .big:
return value.bigEndian
}
}
/// Read an integer off this `ByteBuffer`, move the reader index forward by the integer's byte size and return the result.
///
/// - Parameters:
/// - endianness: The endianness of the integer in this `ByteBuffer` (defaults to big endian).
/// - as: the desired `FixedWidthInteger` type (optional parameter)
/// - Returns: An integer value deserialized from this `ByteBuffer` or `nil` if there aren't enough bytes readable.
@inlinable
public mutating func readInteger<T: FixedWidthInteger>(endianness: Endianness = .big, as: T.Type = T.self) -> T? {
guard let result = self.getInteger(at: self.readerIndex, endianness: endianness, as: T.self) else {
return nil
}
self._moveReaderIndex(forwardBy: MemoryLayout<T>.size)
return result
}
/// Get the integer at `index` from this `ByteBuffer`. Does not move the reader index.
/// The selected bytes must be readable or else `nil` will be returned.
///
/// - Parameters:
/// - index: The starting index of the bytes for the integer into the `ByteBuffer`.
/// - endianness: The endianness of the integer in this `ByteBuffer` (defaults to big endian).
/// - as: the desired `FixedWidthInteger` type (optional parameter)
/// - Returns: An integer value deserialized from this `ByteBuffer` or `nil` if the bytes of interest are not
/// readable.
@inlinable
public func getInteger<T: FixedWidthInteger>(
at index: Int,
endianness: Endianness = Endianness.big,
as: T.Type = T.self
) -> T? {
guard let range = self.rangeWithinReadableBytes(index: index, length: MemoryLayout<T>.size) else {
return nil
}
if T.self == UInt8.self {
assert(range.count == 1)
return self.withUnsafeReadableBytes { ptr in
ptr[range.startIndex] as! T
}
}
return self.withUnsafeReadableBytes { ptr in
var value: T = 0
withUnsafeMutableBytes(of: &value) { valuePtr in
valuePtr.copyMemory(from: UnsafeRawBufferPointer(rebasing: ptr[range]))
}
return _toEndianness(value: value, endianness: endianness)
}
}
/// Write `integer` into this `ByteBuffer`, moving the writer index forward appropriately.
///
/// - Parameters:
/// - integer: The integer to serialize.
/// - endianness: The endianness to use, defaults to big endian.
/// - as: the desired `FixedWidthInteger` type (optional parameter)
/// - Returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func writeInteger<T: FixedWidthInteger>(
_ integer: T,
endianness: Endianness = .big,
as: T.Type = T.self
) -> Int {
let bytesWritten = self.setInteger(integer, at: self.writerIndex, endianness: endianness)
self._moveWriterIndex(forwardBy: bytesWritten)
return Int(bytesWritten)
}
/// Write `integer` into this `ByteBuffer` starting at `index`. This does not alter the writer index.
///
/// - Parameters:
/// - integer: The integer to serialize.
/// - index: The index of the first byte to write.
/// - endianness: The endianness to use, defaults to big endian.
/// - as: the desired `FixedWidthInteger` type (optional parameter)
/// - Returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func setInteger<T: FixedWidthInteger>(
_ integer: T,
at index: Int,
endianness: Endianness = .big,
as: T.Type = T.self
) -> Int {
var value = _toEndianness(value: integer, endianness: endianness)
return Swift.withUnsafeBytes(of: &value) { ptr in
self.setBytes(ptr, at: index)
}
}
/// Returns the integer at the current reader index without advancing it.
///
/// This method is equivalent to calling `getInteger(at: readerIndex, ...)`
///
/// - Parameters:
/// - endianness: The endianness of the integer (defaults to big endian).
/// - as: The desired `FixedWidthInteger` type (optional parameter).
/// - Returns: An integer value deserialized from this `ByteBuffer` or `nil` if the bytes are not readable.
@inlinable
public func peekInteger<T: FixedWidthInteger>(
endianness: Endianness = .big,
as: T.Type = T.self
) -> T? {
self.getInteger(at: self.readerIndex, endianness: endianness, as: `as`)
}
}
extension FixedWidthInteger {
/// Returns the next power of two.
@inlinable
func nextPowerOf2() -> Self {
guard self != 0 else {
return 1
}
return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
}
/// Returns the previous power of 2, or self if it already is.
@inlinable
func previousPowerOf2() -> Self {
guard self != 0 else {
return 0
}
return 1 << ((Self.bitWidth - 1) - self.leadingZeroBitCount)
}
/// Initialize an integer from a byte buffer of exactly the right size.
/// The bytes will be read using the host system's endianness.
///
/// - Parameters:
/// - buffer: The ByteBuffer to read from
/// - endianness: The endianness to use when reading the integer, defaults to the host system's endianness.
@inlinable
public init?(buffer: ByteBuffer, endianness: Endianness = .host) {
var buffer = buffer
guard let value = buffer.readInteger(endianness: endianness, as: Self.self), buffer.readableBytes == 0 else {
return nil
}
self = value
}
}
extension UInt32 {
/// Returns the next power of two unless that would overflow, in which case UInt32.max (on 64-bit systems) or
/// Int32.max (on 32-bit systems) is returned. The returned value is always safe to be cast to Int and passed
/// to malloc on all platforms.
@inlinable
func nextPowerOf2ClampedToMax() -> UInt32 {
guard self > 0 else {
return 1
}
var n = self
#if arch(arm) || arch(i386) || arch(arm64_32) || arch(wasm32)
// on 32-bit platforms we can't make use of a whole UInt32.max (as it doesn't fit in an Int)
let max = UInt32(Int.max)
#else
// on 64-bit platforms we're good
let max = UInt32.max
#endif
n -= 1
n |= n >> 1
n |= n >> 2
n |= n >> 4
n |= n >> 8
n |= n >> 16
if n != max {
n += 1
}
return n
}
}
/// Endianness refers to the sequential order in which bytes are arranged into larger numerical values when stored in
/// memory or when transmitted over digital links.
public enum Endianness: Sendable {
/// The endianness of the machine running this program.
public static let host: Endianness = hostEndianness0()
private static func hostEndianness0() -> Endianness {
let number: UInt32 = 0x1234_5678
return number == number.bigEndian ? .big : .little
}
/// big endian, the most significant byte (MSB) is at the lowest address
case big
/// little endian, the least significant byte (LSB) is at the lowest address
case little
}