├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .swiftformat ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── ExtrasUUID │ └── XUUID.swift └── PerfTests │ ├── Alternatives.swift │ └── main.swift ├── Tests └── ExtrasUUIDTests │ ├── SIMDTests.swift │ └── XUUIDTests.swift ├── codecov.yml └── scripts └── validity.sh /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - "*" 9 | 10 | jobs: 11 | 12 | "validity-Tests": 13 | runs-on: macOS-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Install swiftformat 18 | run: brew install swiftformat 19 | - name: Run validity 20 | run: ./scripts/validity.sh . 21 | 22 | "tuxOS-Tests": 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | images: 28 | - swift:5.1 29 | - swift:5.2 30 | - swift:5.3 31 | - swiftlang/swift:nightly-bionic 32 | container: 33 | image: ${{ matrix.images }} 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v2 37 | - name: Test 38 | run: swift test --enable-code-coverage --enable-test-discovery 39 | - name: Convert coverage files 40 | run: | 41 | llvm-cov export -format="lcov" \ 42 | .build/debug/swift-extras-uuidPackageTests.xctest \ 43 | -ignore-filename-regex="\/Tests\/" \ 44 | -instr-profile .build/debug/codecov/default.profdata > info.lcov 45 | - name: Install curl 46 | run: apt-get update && apt-get install -y curl # required by the codecov action. 47 | - name: Upload to codecov.io 48 | uses: codecov/codecov-action@v1 49 | with: 50 | file: info.lcov 51 | 52 | "tuxOS-Performance-Tests": 53 | runs-on: ubuntu-latest 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | images: 58 | - swift:5.1 59 | - swift:5.2 60 | - swift:5.3 61 | - swiftlang/swift:nightly-bionic 62 | container: 63 | image: ${{ matrix.images }} 64 | steps: 65 | - name: Checkout 66 | uses: actions/checkout@v2 67 | - name: Build and run test 68 | run: swift run -c release --enable-test-discovery 69 | 70 | "macOS-Tests": 71 | runs-on: macOS-latest 72 | strategy: 73 | fail-fast: false 74 | matrix: 75 | xcode: 76 | - Xcode_11.1.app 77 | - Xcode_11.6.app 78 | - Xcode_12.2.app 79 | steps: 80 | - name: Checkout 81 | uses: actions/checkout@v2 82 | - name: Show all Xcode versions 83 | run: ls -an /Applications/ | grep Xcode* 84 | - name: Change Xcode command line tools 85 | run: sudo xcode-select -s /Applications/${{ matrix.xcode }}/Contents/Developer 86 | - name: Swift version 87 | run: swift --version 88 | - name: Xcode Tests 89 | run: | 90 | swift package generate-xcodeproj --skip-extra-files --enable-code-coverage 91 | xcodebuild -quiet -parallel-testing-enabled YES -scheme swift-extras-uuid-Package -enableCodeCoverage YES build test 92 | - name: Codecov 93 | run: bash <(curl -s https://codecov.io/bash) -f info.lcov 94 | 95 | "macOS-Performance-Tests": 96 | runs-on: macOS-latest 97 | strategy: 98 | fail-fast: false 99 | matrix: 100 | xcode: 101 | - Xcode_11.1.app 102 | - Xcode_11.6.app 103 | - Xcode_12.2.app 104 | steps: 105 | - name: Checkout 106 | uses: actions/checkout@v2 107 | - name: Change Xcode command line tools 108 | run: sudo xcode-select -s /Applications/${{ matrix.xcode }}/Contents/Developer 109 | - name: Swift version 110 | run: swift --version 111 | - name: Build and run test 112 | run: swift run -c release 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # file options 2 | 3 | --swiftversion 5.1 4 | --exclude .build 5 | 6 | # format options 7 | 8 | --self insert 9 | --patternlet inline 10 | --stripunusedargs unnamed-only 11 | --ifdef no-indent 12 | 13 | # rules 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Swift Extras 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "swift-extras-uuid", 8 | products: [ 9 | .library(name: "ExtrasUUID", targets: ["ExtrasUUID"]), 10 | ], 11 | targets: [ 12 | .target(name: "PerfTests", dependencies: ["ExtrasUUID"]), 13 | .target(name: "ExtrasUUID", dependencies: []), 14 | .testTarget(name: "ExtrasUUIDTests", dependencies: ["ExtrasUUID"]), 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-extras-uuid 2 | 3 | A reimplementation of UUID in Swift without the use of Foundation or any other dependency. 4 | 5 | [![Swift 5.1](https://img.shields.io/badge/Swift-5.1-blue.svg)](https://swift.org/download/) 6 | [![github-actions](https://github.com/swift-extras/swift-extras-uuid/workflows/CI/badge.svg)](https://github.com/swift-extras/swift-extras-uuid/actions) 7 | [![codecov](https://codecov.io/gh/swift-extras/swift-extras-uuid/branch/main/graph/badge.svg)](https://codecov.io/gh/swift-extras/swift-extras-uuid) 8 | ![macOS](https://img.shields.io/badge/os-macOS-green.svg?style=flat) 9 | ![tuxOS](https://img.shields.io/badge/os-tuxOS-green.svg?style=flat) 10 | 11 | ### Motivation 12 | 13 | Foundation on non Darwin platforms (Linux, Windows) has often worse performance and/or missing features compared to its Darwin counterpart. That's why I like to limit the use of Foundation classes and structs to the absolute minimum when writing server side or cross platform Swift code. This package offers the same UUID functionality that Foundation provides, without actually using Foundation. The performance is slightly better on Linux compared to the Foundation implementation and comparable on macOS. 14 | 15 | ### How to use it? 16 | 17 | Add dependency to your `Package.swift`. 18 | 19 | ```swift 20 | dependencies: [ 21 | .package(url: "https://github.com/swift-extras/swift-extras-uuid.git", .upToNextMajor(from: "0.1.0")), 22 | ], 23 | ``` 24 | 25 | Add `ExtrasUUID` to the target you want to use it in. 26 | 27 | ```swift 28 | targets: [ 29 | .target(name: "MyFancyTarget", dependencies: [ 30 | .product(name: "ExtrasUUID", package: "swift-extras-uuid"), 31 | ]) 32 | ] 33 | ``` 34 | 35 | Import the library and use it. This package's UUID is prefixed with an X to avoid naming conflicts in cases where you import Foundation in the same file. 36 | 37 | ```swift 38 | import ExtrasUUID 39 | 40 | let uuid = XUUID() 41 | ``` 42 | 43 | ### Interop with Foundation 44 | 45 | You can easily convert between a Foundation UUID and an swift-extras XUUID. 46 | 47 | From Foundation: 48 | 49 | ```swift 50 | let fduuid = UUID() 51 | let xuuid = XUUID(uuid: fduuid.uuid) 52 | ``` 53 | 54 | To Foundation: 55 | ```swift 56 | let xuuid = XUUID() 57 | let fduuid = UUID(uuid: xuuid.uuid) 58 | ``` 59 | 60 | ### Performance 61 | 62 | To run simple performance tests, check out this repository and run: 63 | 64 | ``` 65 | swift run -c release --enable-test-discovery 66 | ``` 67 | 68 | The performance tests will 69 | 70 | - create 1m uuids 71 | - parse 1m uuids 72 | - create 1m uuid strings 73 | - compare 10k uuids 74 | 75 | #### Linux - Swift 5.3 76 | 77 | | | Creating Random | Parsing | Stringify | Comparing | 78 | |:--|:--|:--|:--|:--| 79 | | Foundation | 3.94s | 1.01s | 0.8s | 0.16s | 80 | | ExtrasUUID | 2.06s | 0.34s | 0.16s | 0.13s | 81 | | Speedup | ~2x | ~3x | ~5x | ~1x | 82 | 83 | -------------------------------------------------------------------------------- /Sources/ExtrasUUID/XUUID.swift: -------------------------------------------------------------------------------- 1 | public typealias xuuid_t = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) 2 | 3 | /// Represents UUIDs as defined in [RFC 4122](https://tools.ietf.org/html/rfc4122). 4 | public struct XUUID { 5 | private let _uuid: xuuid_t 6 | 7 | public var uuid: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) { 8 | _uuid 9 | } 10 | 11 | static let null: xuuid_t = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 12 | 13 | /// Creates a random [v4](https://tools.ietf.org/html/rfc4122#section-4.1.3) UUID. 14 | public init() { 15 | self = Self.generateRandom() 16 | } 17 | 18 | /// Creates a UUID from a string such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F". 19 | /// 20 | /// Returns nil for invalid strings. 21 | public init?(uuidString string: String) { 22 | guard let uuid = Self.fromUUIDString(string) else { 23 | return nil 24 | } 25 | 26 | self = uuid 27 | } 28 | 29 | /// Creates a UUID from a `xuuid_t`. 30 | public init(uuid: xuuid_t) { 31 | self._uuid = uuid 32 | } 33 | 34 | /// Returns a string representation for the UUID, such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" 35 | public var uuidString: String { 36 | self.uppercased 37 | } 38 | 39 | /// Returns a lowercase string representation for the UUID, such as "e621e1f8-c36c-495a-93fc-0c247a3e6e5f" 40 | public var lowercased: String { 41 | self.toString(characters: Self.lowercaseLookup) 42 | } 43 | 44 | /// Returns an uppercase string representation for the UUID, such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" 45 | public var uppercased: String { 46 | self.toString(characters: Self.uppercaseLookup) 47 | } 48 | } 49 | 50 | // MARK: - Protocol extensions - 51 | 52 | extension XUUID: Hashable { 53 | public func hash(into hasher: inout Hasher) { 54 | var value = self._uuid 55 | withUnsafeBytes(of: &value) { ptr in 56 | hasher.combine(bytes: ptr) 57 | } 58 | } 59 | } 60 | 61 | extension XUUID: Equatable { 62 | public static func == (lhs: Self, rhs: Self) -> Bool { 63 | lhs._uuid.0 == rhs._uuid.0 && 64 | lhs._uuid.1 == rhs._uuid.1 && 65 | lhs._uuid.2 == rhs._uuid.2 && 66 | lhs._uuid.3 == rhs._uuid.3 && 67 | lhs._uuid.4 == rhs._uuid.4 && 68 | lhs._uuid.5 == rhs._uuid.5 && 69 | lhs._uuid.6 == rhs._uuid.6 && 70 | lhs._uuid.7 == rhs._uuid.7 && 71 | lhs._uuid.8 == rhs._uuid.8 && 72 | lhs._uuid.9 == rhs._uuid.9 && 73 | lhs._uuid.10 == rhs._uuid.10 && 74 | lhs._uuid.11 == rhs._uuid.11 && 75 | lhs._uuid.12 == rhs._uuid.12 && 76 | lhs._uuid.13 == rhs._uuid.13 && 77 | lhs._uuid.14 == rhs._uuid.14 && 78 | lhs._uuid.15 == rhs._uuid.15 79 | } 80 | } 81 | 82 | extension XUUID: CustomStringConvertible { 83 | public var description: String { 84 | self.uuidString 85 | } 86 | } 87 | 88 | extension XUUID: CustomDebugStringConvertible { 89 | public var debugDescription: String { 90 | self.uuidString 91 | } 92 | } 93 | 94 | extension XUUID: CustomReflectable { 95 | public var customMirror: Mirror { 96 | let children = [(label: String?, value: Any)]() 97 | return Mirror(self, children: children, displayStyle: .struct) 98 | } 99 | } 100 | 101 | extension XUUID: Decodable { 102 | public init(from decoder: Decoder) throws { 103 | let container = try decoder.singleValueContainer() 104 | let uuidString = try container.decode(String.self) 105 | 106 | guard let uuid = XUUID(uuidString: uuidString) else { 107 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "Attempted to decode UUID from invalid UUID string.") 108 | } 109 | 110 | self = uuid 111 | } 112 | } 113 | 114 | extension XUUID: Encodable { 115 | public func encode(to encoder: Encoder) throws { 116 | var container = encoder.singleValueContainer() 117 | try container.encode(self.uuidString) 118 | } 119 | } 120 | 121 | // MARK: - SIMD - 122 | 123 | public extension XUUID { 124 | /// Creates a UUID from a SIMD vector 125 | init(vector: SIMD16) { 126 | self._uuid = (vector[0], vector[1], vector[2], vector[3], 127 | vector[4], vector[5], vector[6], vector[7], 128 | vector[8], vector[9], vector[10], vector[11], 129 | vector[12], vector[13], vector[14], vector[15]) 130 | } 131 | 132 | /// Creates a simd vector from the UUID 133 | var vector: SIMD16 { 134 | SIMD16(self._uuid.0, self._uuid.1, self._uuid.2, self._uuid.3, 135 | self._uuid.4, self._uuid.5, self._uuid.6, self._uuid.7, 136 | self._uuid.8, self._uuid.9, self._uuid.10, self._uuid.11, 137 | self._uuid.12, self._uuid.13, self._uuid.14, self._uuid.15) 138 | } 139 | 140 | /// Creates a UUID from a string such as "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" using SIMD instructions. 141 | /// 142 | /// **warning**: As of now (Swift 5.3) this is rather slow ([SR-13353](https://bugs.swift.org/browse/SR-13353)) and should not be used in production. 143 | static func fromUUIDStringUsingSIMD(_ string: String) -> XUUID? { 144 | guard string.utf8.count == 36 else { 145 | // invalid length 146 | return nil 147 | } 148 | 149 | guard let values = string.utf8.withContiguousStorageIfAvailable({ (ptr) -> SIMD32? in 150 | guard ptr[8] == UInt8(ascii: "-"), ptr[13] == UInt8(ascii: "-"), ptr[18] == UInt8(ascii: "-"), ptr[23] == UInt8(ascii: "-") else { 151 | return nil 152 | } 153 | 154 | return SIMD32(UInt8(ptr[0]), UInt8(ptr[1]), UInt8(ptr[2]), UInt8(ptr[3]), 155 | UInt8(ptr[4]), UInt8(ptr[5]), UInt8(ptr[6]), UInt8(ptr[7]), // dash 156 | UInt8(ptr[9]), UInt8(ptr[10]), UInt8(ptr[11]), UInt8(ptr[12]), // dash 157 | UInt8(ptr[14]), UInt8(ptr[15]), UInt8(ptr[16]), UInt8(ptr[17]), // dash 158 | UInt8(ptr[19]), UInt8(ptr[20]), UInt8(ptr[21]), UInt8(ptr[22]), // dash 159 | UInt8(ptr[24]), UInt8(ptr[25]), UInt8(ptr[26]), UInt8(ptr[27]), 160 | UInt8(ptr[28]), UInt8(ptr[29]), UInt8(ptr[30]), UInt8(ptr[31]), 161 | UInt8(ptr[32]), UInt8(ptr[33]), UInt8(ptr[34]), UInt8(ptr[35])) 162 | }) else { 163 | var string = string 164 | string.makeContiguousUTF8() 165 | return self.fromUUIDStringUsingSIMD(string) 166 | } 167 | 168 | guard var vector = values else { 169 | return nil 170 | } 171 | 172 | let maskGreaterThanZero = vector .>= UInt8(ascii: "0") 173 | let maskSmallerThanNine = vector .<= UInt8(ascii: "9") 174 | let asciiNumber = maskGreaterThanZero .& maskSmallerThanNine 175 | 176 | let maskGreaterThanSmallA = vector .>= UInt8(ascii: "a") 177 | let maskSmallerThanSmallF = vector .<= UInt8(ascii: "f") 178 | let smallCharacter = maskGreaterThanSmallA .& maskSmallerThanSmallF 179 | 180 | let maskGreaterThanCapitalA = vector .>= UInt8(ascii: "A") 181 | let maskSmallerThanCapitalF = vector .<= UInt8(ascii: "F") 182 | let capitalCharacter = maskGreaterThanCapitalA .& maskSmallerThanCapitalF 183 | 184 | var subtractNumber = SIMD32.zero 185 | subtractNumber.replace(with: UInt8(ascii: "0"), where: asciiNumber) 186 | 187 | var subtractLowercaseChar = SIMD32.zero 188 | subtractLowercaseChar.replace(with: UInt8(ascii: "a") - 10, where: smallCharacter) 189 | 190 | var subtractUppercaseChar = SIMD32.zero 191 | subtractUppercaseChar.replace(with: UInt8(ascii: "A") - 10, where: capitalCharacter) 192 | 193 | vector &-= subtractNumber 194 | vector &-= subtractLowercaseChar 195 | vector &-= subtractUppercaseChar 196 | 197 | let xor = asciiNumber .^ (smallCharacter .^ capitalCharacter) 198 | guard all(xor) else { return nil } 199 | 200 | vector.evenHalf &<<= 4 201 | vector.evenHalf &+= vector.oddHalf 202 | 203 | let _uuid = (vector[0], vector[2], vector[4], vector[6], 204 | vector[8], vector[10], vector[12], vector[14], 205 | vector[16], vector[18], vector[20], vector[22], 206 | vector[24], vector[26], vector[28], vector[30]) 207 | 208 | return Self(uuid: _uuid) 209 | } 210 | } 211 | 212 | // MARK: - Implementation details - 213 | 214 | extension XUUID { 215 | /// thread safe secure random number generator. 216 | private static var generator = SystemRandomNumberGenerator() 217 | static func generateRandom() -> XUUID { 218 | var _uuid: xuuid_t = Self.null 219 | // https://tools.ietf.org/html/rfc4122#page-14 220 | 221 | // o Set all the other bits to randomly (or pseudo-randomly) chosen 222 | // values. 223 | withUnsafeMutableBytes(of: &_uuid) { ptr in 224 | ptr.storeBytes(of: Self.generator.next(), toByteOffset: 0, as: UInt64.self) 225 | ptr.storeBytes(of: Self.generator.next(), toByteOffset: 8, as: UInt64.self) 226 | } 227 | 228 | // o Set the four most significant bits (bits 12 through 15) of the 229 | // time_hi_and_version field to the 4-bit version number from 230 | // Section 4.1.3. 231 | _uuid.6 = (_uuid.6 & 0x0F) | 0x40 232 | 233 | // o Set the two most significant bits (bits 6 and 7) of the 234 | // clock_seq_hi_and_reserved to zero and one, respectively. 235 | _uuid.8 = (_uuid.8 & 0x3F) | 0x80 236 | return XUUID(uuid: _uuid) 237 | } 238 | 239 | static func fromUUIDString(_ string: String) -> XUUID? { 240 | guard string.utf8.count == 36 else { 241 | // invalid length 242 | return nil 243 | } 244 | 245 | let _uuid = string.utf8.withContiguousStorageIfAvailable { (ptr) -> xuuid_t? in 246 | var uuid = Self.null 247 | 248 | let success = withUnsafeMutableBytes(of: &uuid) { (uuid) -> (Bool) in 249 | func newIndex(index: Int) -> (Int, Bool) { 250 | var index = index 251 | switch index { 252 | case 0 ..< 8: 253 | break 254 | case 9 ..< 13: 255 | index -= 1 256 | case 14 ..< 18: 257 | index -= 2 258 | case 19 ..< 23: 259 | index -= 3 260 | case 24 ..< 36: 261 | index -= 4 262 | default: 263 | preconditionFailure() 264 | } 265 | 266 | return (index / 2, index % 2 == 0) 267 | } 268 | 269 | loop: for index in 0 ... 35 { 270 | let value = ptr[index] 271 | 272 | switch (index, value) { 273 | case (8, UInt8(ascii: "-")), (13, UInt8(ascii: "-")), (18, UInt8(ascii: "-")), (23, UInt8(ascii: "-")): 274 | continue loop 275 | case (8, _), (13, _), (18, _), (23, _): 276 | // invalid syntax 277 | return false 278 | case (_, UInt8(ascii: "0") ... UInt8(ascii: "9")): 279 | var v = value - UInt8(ascii: "0") 280 | let (nIndex, shift) = newIndex(index: index) 281 | if shift { 282 | v = v << 4 283 | } 284 | uuid[nIndex] += v 285 | case (_, UInt8(ascii: "a") ... UInt8(ascii: "f")): 286 | var v = value - UInt8(ascii: "a") + 10 287 | let (nIndex, shift) = newIndex(index: index) 288 | if shift { 289 | v = v << 4 290 | } 291 | uuid[nIndex] += v 292 | case (_, UInt8(ascii: "A") ... UInt8(ascii: "F")): 293 | var v = value - UInt8(ascii: "A") + 10 294 | let (nIndex, shift) = newIndex(index: index) 295 | if shift { 296 | v = v << 4 297 | } 298 | uuid[nIndex] += v 299 | default: 300 | return false 301 | } 302 | } 303 | return true 304 | } 305 | 306 | return success ? uuid : nil 307 | } 308 | 309 | guard let u = _uuid, let uuid = u else { 310 | return nil 311 | } 312 | 313 | return .init(uuid: uuid) 314 | } 315 | 316 | private static let lowercaseLookup: [UInt8] = [ 317 | UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), UInt8(ascii: "3"), 318 | UInt8(ascii: "4"), UInt8(ascii: "5"), UInt8(ascii: "6"), UInt8(ascii: "7"), 319 | UInt8(ascii: "8"), UInt8(ascii: "9"), UInt8(ascii: "a"), UInt8(ascii: "b"), 320 | UInt8(ascii: "c"), UInt8(ascii: "d"), UInt8(ascii: "e"), UInt8(ascii: "f"), 321 | ] 322 | 323 | private static let uppercaseLookup: [UInt8] = [ 324 | UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), UInt8(ascii: "3"), 325 | UInt8(ascii: "4"), UInt8(ascii: "5"), UInt8(ascii: "6"), UInt8(ascii: "7"), 326 | UInt8(ascii: "8"), UInt8(ascii: "9"), UInt8(ascii: "A"), UInt8(ascii: "B"), 327 | UInt8(ascii: "C"), UInt8(ascii: "D"), UInt8(ascii: "E"), UInt8(ascii: "F"), 328 | ] 329 | 330 | /// Use this type to create the backing store of a UUID String on stack. 331 | /// 332 | /// Using this type we ensure to only have one allocation for creating a String 333 | /// even before Swift 5.3 334 | private typealias xuuid_string_t = ( 335 | UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, 336 | UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, 337 | UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8 338 | ) 339 | 340 | private static let nullString: xuuid_string_t = ( 341 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 342 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 343 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 344 | ) 345 | 346 | func toString(characters: [UInt8]) -> String { 347 | var string: xuuid_string_t = Self.nullString 348 | // to get the best performance we access the lookup table's unsafe buffer pointer 349 | // since the lookup table has 16 elements and we shift the byte values in such a way 350 | // that the max value is 15 (last 4 bytes = 16 values). For this reason the lookups 351 | // are safe and we don't need Swifts safety guards. 352 | return characters.withUnsafeBufferPointer { (lookup) -> String in 353 | withUnsafeMutableBytes(of: &string) { (ptr) -> String in 354 | ptr[0] = lookup[Int(uuid.0 >> 4)] 355 | ptr[1] = lookup[Int(uuid.0 & 0x0F)] 356 | ptr[2] = lookup[Int(uuid.1 >> 4)] 357 | ptr[3] = lookup[Int(uuid.1 & 0x0F)] 358 | ptr[4] = lookup[Int(uuid.2 >> 4)] 359 | ptr[5] = lookup[Int(uuid.2 & 0x0F)] 360 | ptr[6] = lookup[Int(uuid.3 >> 4)] 361 | ptr[7] = lookup[Int(uuid.3 & 0x0F)] 362 | ptr[8] = UInt8(ascii: "-") 363 | ptr[9] = lookup[Int(uuid.4 >> 4)] 364 | ptr[10] = lookup[Int(uuid.4 & 0x0F)] 365 | ptr[11] = lookup[Int(uuid.5 >> 4)] 366 | ptr[12] = lookup[Int(uuid.5 & 0x0F)] 367 | ptr[13] = UInt8(ascii: "-") 368 | ptr[14] = lookup[Int(uuid.6 >> 4)] 369 | ptr[15] = lookup[Int(uuid.6 & 0x0F)] 370 | ptr[16] = lookup[Int(uuid.7 >> 4)] 371 | ptr[17] = lookup[Int(uuid.7 & 0x0F)] 372 | ptr[18] = UInt8(ascii: "-") 373 | ptr[19] = lookup[Int(uuid.8 >> 4)] 374 | ptr[20] = lookup[Int(uuid.8 & 0x0F)] 375 | ptr[21] = lookup[Int(uuid.9 >> 4)] 376 | ptr[22] = lookup[Int(uuid.9 & 0x0F)] 377 | ptr[23] = UInt8(ascii: "-") 378 | ptr[24] = lookup[Int(uuid.10 >> 4)] 379 | ptr[25] = lookup[Int(uuid.10 & 0x0F)] 380 | ptr[26] = lookup[Int(uuid.11 >> 4)] 381 | ptr[27] = lookup[Int(uuid.11 & 0x0F)] 382 | ptr[28] = lookup[Int(uuid.12 >> 4)] 383 | ptr[29] = lookup[Int(uuid.12 & 0x0F)] 384 | ptr[30] = lookup[Int(uuid.13 >> 4)] 385 | ptr[31] = lookup[Int(uuid.13 & 0x0F)] 386 | ptr[32] = lookup[Int(uuid.14 >> 4)] 387 | ptr[33] = lookup[Int(uuid.14 & 0x0F)] 388 | ptr[34] = lookup[Int(uuid.15 >> 4)] 389 | ptr[35] = lookup[Int(uuid.15 & 0x0F)] 390 | 391 | return String(decoding: ptr, as: Unicode.UTF8.self) 392 | } 393 | } 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /Sources/PerfTests/Alternatives.swift: -------------------------------------------------------------------------------- 1 | import ExtrasUUID 2 | 3 | #if canImport(Darwin) 4 | import Darwin 5 | 6 | extension XUUID { 7 | static func fromUUIDStringUsingUUIDParse(_ string: String) -> XUUID? { 8 | // This is the base implementation... I guess this is what is done for 9 | // Foundation.UUID 10 | let _uuid = string.withCString { (cString) -> uuid_t? in 11 | var _uuid: xuuid_t = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 12 | guard uuid_parse(cString, &_uuid.0) == 0 else { 13 | return nil 14 | } 15 | 16 | return _uuid 17 | } 18 | 19 | guard let uuid = _uuid else { 20 | return nil 21 | } 22 | 23 | return Self(uuid: uuid) 24 | } 25 | 26 | func lowercasedUsingUUID() -> String { 27 | var value: xuuid_t = self.uuid 28 | let target = UnsafeMutablePointer.allocate(capacity: 37) 29 | uuid_unparse_lower(&value.0, target) 30 | return String(cString: target) 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/PerfTests/main.swift: -------------------------------------------------------------------------------- 1 | import ExtrasUUID 2 | import Foundation 3 | 4 | let runs = 1_000_000 5 | 6 | @discardableResult 7 | func timing(name: String, execute: () throws -> Void) rethrows -> TimeInterval { 8 | let start = Date() 9 | try execute() 10 | let time = -start.timeIntervalSinceNow 11 | print("\(name) | took: \(String(format: "%.5f", time))s") 12 | return time 13 | } 14 | 15 | print("------------------------------------------") 16 | print("Creating UUIDs: \(runs)") 17 | 18 | let psCreate = timing(name: "Random Generator") { 19 | var result = [XUUID]() 20 | result.reserveCapacity(runs) 21 | for _ in 0 ..< runs { 22 | result.append(XUUID()) 23 | } 24 | } 25 | 26 | let foundationCreate = timing(name: "Foundation ") { 27 | var result = [UUID]() 28 | result.reserveCapacity(runs) 29 | for _ in 0 ..< runs { 30 | result.append(UUID()) 31 | } 32 | } 33 | 34 | let strings = (0 ..< runs).map { _ in UUID().uuidString } 35 | 36 | print("------------------------------------------") 37 | print("Parsing UUIDs: \(runs)") 38 | 39 | #if canImport(Darwin) 40 | let uuidParse = timing(name: "uuid_parse ") { 41 | let uuids = strings.compactMap { XUUID.fromUUIDStringUsingUUIDParse($0) } 42 | precondition(uuids.count == strings.count, "expected to parse all") 43 | } 44 | #endif 45 | 46 | var xuuids = [XUUID]() 47 | let loopParse = timing(name: "ExtrasUUID ") { 48 | xuuids = strings.compactMap { XUUID(uuidString: $0) } 49 | precondition(xuuids.count == strings.count, "expected to parse all") 50 | } 51 | 52 | let simdParse = timing(name: "ExtrasUUID SIMD ") { 53 | let uuids = strings.compactMap { XUUID.fromUUIDStringUsingSIMD($0) } 54 | precondition(uuids.count == strings.count, "expected to parse all") 55 | } 56 | 57 | var fduuids = [UUID]() 58 | let foundationParse = timing(name: "Foundation ") { 59 | fduuids = strings.compactMap { UUID(uuidString: $0) } 60 | precondition(fduuids.count == strings.count, "expected to parse all") 61 | } 62 | 63 | print("------------------------------------------") 64 | print("Create UUID Strings: \(runs)") 65 | 66 | #if canImport(Darwin) 67 | let uuidUnparse = timing(name: "uuid_unparse ") { 68 | let strings = xuuids.compactMap { $0.lowercasedUsingUUID() } 69 | precondition(strings.count == xuuids.count, "expected to unparse all") 70 | } 71 | #endif 72 | 73 | let simpleUnparse = timing(name: "ExtrasUUID ") { 74 | let strings = xuuids.compactMap { $0.lowercased } 75 | precondition(strings.count == xuuids.count, "expected to unparse all") 76 | } 77 | 78 | let foundationUnparse = timing(name: "Foundation ") { 79 | let strings = fduuids.compactMap { $0.uuidString } 80 | precondition(strings.count == fduuids.count, "expected to unparse all") 81 | } 82 | 83 | print("------------------------------------------") 84 | print("Compare UUIDs: \(runs)") 85 | 86 | let xuuidslice = xuuids[0 ..< 10000] 87 | 88 | let xuuidCompare = timing(name: "UUIDKit ") { 89 | xuuidslice.forEach { uuid in 90 | let first = xuuidslice.first { $0 == uuid } 91 | precondition(first != nil, "expected to find something") 92 | } 93 | } 94 | 95 | let fduuidslice = fduuids[0 ..< 10000] 96 | let foundationCompare = timing(name: "Foundation ") { 97 | fduuidslice.forEach { uuid in 98 | let first = fduuidslice.first { $0 == uuid } 99 | precondition(first != nil, "expected to find something") 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/ExtrasUUIDTests/SIMDTests.swift: -------------------------------------------------------------------------------- 1 | import ExtrasUUID 2 | import XCTest 3 | 4 | final class SIMDTests: XCTestCase { 5 | func testInitFromStringSuccess() { 6 | let string = "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" 7 | let uuid = XUUID.fromUUIDStringUsingSIMD(string) 8 | XCTAssertEqual(uuid, XUUID(uuidString: string)) 9 | 10 | XCTAssertEqual(uuid?.lowercased, string.lowercased()) 11 | } 12 | 13 | func testInitFromStringMissingCharacterAtEnd() { 14 | let string = "E621E1F8-C36C-495A-93FC-0C247A3E6E5" 15 | let uuid = XUUID.fromUUIDStringUsingSIMD(string) 16 | XCTAssertNil(uuid) 17 | } 18 | 19 | func testInitFromStringUsingSIMDInvalidSeparatorCharacter() { 20 | // illegal character: _ 21 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F8-C36C-495A-93FC_0C247A3E6E5F")) 22 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F8-C36C-495A_93FC-0C247A3E6E5F")) 23 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F8-C36C_495A-93FC-0C247A3E6E5F")) 24 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F8_C36C-495A-93FC-0C247A3E6E5F")) 25 | 26 | // illegal character: 0 27 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F8-C36C-495A-93FC00C247A3E6E5F")) 28 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F8-C36C-495A093FC-0C247A3E6E5F")) 29 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F8-C36C0495A-93FC-0C247A3E6E5F")) 30 | XCTAssertNil(XUUID.fromUUIDStringUsingSIMD("E621E1F80C36C-495A-93FC-0C247A3E6E5F")) 31 | } 32 | 33 | func testInitWithSIMDVector() { 34 | let uuid = XUUID() 35 | let simd = uuid.vector 36 | let new = XUUID(vector: simd) 37 | 38 | XCTAssertEqual(uuid, new) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/ExtrasUUIDTests/XUUIDTests.swift: -------------------------------------------------------------------------------- 1 | import ExtrasUUID 2 | import XCTest 3 | 4 | final class XUUIDTests: XCTestCase { 5 | func testInitFromStringSuccess() { 6 | let string = "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" 7 | let uuid = XUUID(uuidString: string) 8 | XCTAssertEqual(uuid?.uuidString, UUID(uuidString: string)?.uuidString) 9 | 10 | XCTAssertEqual(uuid?.uppercased, string) 11 | } 12 | 13 | func testInitFromLowercaseStringSuccess() { 14 | let string = "E621E1F8-C36C-495A-93FC-0C247A3E6E5F".lowercased() 15 | let uuid = XUUID(uuidString: string) 16 | XCTAssertEqual(uuid?.uuidString, UUID(uuidString: string)?.uuidString) 17 | 18 | XCTAssertEqual(uuid?.lowercased, string) 19 | } 20 | 21 | func testInitFromNSStringSuccess() { 22 | let nsString = NSMutableString(capacity: 16) 23 | nsString.append("E621E1F8") 24 | nsString.append("-") 25 | nsString.append("C36C") 26 | nsString.append("-") 27 | nsString.append("495A") 28 | nsString.append("-") 29 | nsString.append("93FC") 30 | nsString.append("-") 31 | nsString.append("0C247A3E6E5F") 32 | 33 | // TODO: I would love to enforce that the nsstring is not contiguous 34 | // here to enforce a special code path. I have no idea how to 35 | // achieve this though at the moment 36 | // XCTAssertFalse((nsString as String).isContiguousUTF8) 37 | let uuid = XUUID(uuidString: nsString as String) 38 | XCTAssertEqual(uuid?.uuidString, UUID(uuidString: nsString as String)?.uuidString) 39 | 40 | XCTAssertEqual(uuid?.uppercased, nsString as String) 41 | } 42 | 43 | func testInitFromStringMissingCharacterAtEnd() { 44 | let string = "E621E1F8-C36C-495A-93FC-0C247A3E6E5" 45 | let uuid = XUUID(uuidString: string) 46 | XCTAssertNil(uuid) 47 | } 48 | 49 | func testInitFromStringInvalidCharacterAtEnd() { 50 | let string = "E621E1F8-C36C-495A-93FC-0C247A3E6E5H" 51 | let uuid = XUUID(uuidString: string) 52 | XCTAssertNil(uuid) 53 | } 54 | 55 | func testInitFromStringInvalidSeparatorCharacter() { 56 | // illegal character: _ 57 | XCTAssertNil(XUUID(uuidString: "E621E1F8-C36C-495A-93FC_0C247A3E6E5F")) 58 | XCTAssertNil(XUUID(uuidString: "E621E1F8-C36C-495A_93FC-0C247A3E6E5F")) 59 | XCTAssertNil(XUUID(uuidString: "E621E1F8-C36C_495A-93FC-0C247A3E6E5F")) 60 | XCTAssertNil(XUUID(uuidString: "E621E1F8_C36C-495A-93FC-0C247A3E6E5F")) 61 | 62 | // illegal character: 0 63 | XCTAssertNil(XUUID(uuidString: "E621E1F8-C36C-495A-93FC00C247A3E6E5F")) 64 | XCTAssertNil(XUUID(uuidString: "E621E1F8-C36C-495A093FC-0C247A3E6E5F")) 65 | XCTAssertNil(XUUID(uuidString: "E621E1F8-C36C0495A-93FC-0C247A3E6E5F")) 66 | XCTAssertNil(XUUID(uuidString: "E621E1F80C36C-495A-93FC-0C247A3E6E5F")) 67 | } 68 | 69 | func testUnparse() { 70 | let string = "E621E1F8-C36C-495A-93FC-0C247A3E6E5F" 71 | let uuid = XUUID(uuidString: string) 72 | XCTAssertEqual(string.lowercased(), uuid?.lowercased) 73 | } 74 | 75 | func testDescription() { 76 | let xuuid = XUUID() 77 | let fduuid = UUID(uuid: xuuid.uuid) 78 | 79 | XCTAssertEqual(fduuid.description, xuuid.description) 80 | XCTAssertEqual(fduuid.debugDescription, xuuid.debugDescription) 81 | } 82 | 83 | func testFoundationInteropFromFoundation() { 84 | let fduuid = UUID() 85 | let xuuid = XUUID(uuid: fduuid.uuid) 86 | 87 | XCTAssertEqual(fduuid.uuid.0, xuuid.uuid.0) 88 | XCTAssertEqual(fduuid.uuid.1, xuuid.uuid.1) 89 | XCTAssertEqual(fduuid.uuid.2, xuuid.uuid.2) 90 | XCTAssertEqual(fduuid.uuid.3, xuuid.uuid.3) 91 | XCTAssertEqual(fduuid.uuid.4, xuuid.uuid.4) 92 | XCTAssertEqual(fduuid.uuid.5, xuuid.uuid.5) 93 | XCTAssertEqual(fduuid.uuid.6, xuuid.uuid.6) 94 | XCTAssertEqual(fduuid.uuid.7, xuuid.uuid.7) 95 | XCTAssertEqual(fduuid.uuid.8, xuuid.uuid.8) 96 | XCTAssertEqual(fduuid.uuid.9, xuuid.uuid.9) 97 | XCTAssertEqual(fduuid.uuid.10, xuuid.uuid.10) 98 | XCTAssertEqual(fduuid.uuid.11, xuuid.uuid.11) 99 | XCTAssertEqual(fduuid.uuid.12, xuuid.uuid.12) 100 | XCTAssertEqual(fduuid.uuid.13, xuuid.uuid.13) 101 | XCTAssertEqual(fduuid.uuid.14, xuuid.uuid.14) 102 | XCTAssertEqual(fduuid.uuid.15, xuuid.uuid.15) 103 | } 104 | 105 | func testFoundationInteropToFoundation() { 106 | let xuuid = XUUID() 107 | let fduuid = UUID(uuid: xuuid.uuid) 108 | 109 | XCTAssertEqual(fduuid.uuid.0, xuuid.uuid.0) 110 | XCTAssertEqual(fduuid.uuid.1, xuuid.uuid.1) 111 | XCTAssertEqual(fduuid.uuid.2, xuuid.uuid.2) 112 | XCTAssertEqual(fduuid.uuid.3, xuuid.uuid.3) 113 | XCTAssertEqual(fduuid.uuid.4, xuuid.uuid.4) 114 | XCTAssertEqual(fduuid.uuid.5, xuuid.uuid.5) 115 | XCTAssertEqual(fduuid.uuid.6, xuuid.uuid.6) 116 | XCTAssertEqual(fduuid.uuid.7, xuuid.uuid.7) 117 | XCTAssertEqual(fduuid.uuid.8, xuuid.uuid.8) 118 | XCTAssertEqual(fduuid.uuid.9, xuuid.uuid.9) 119 | XCTAssertEqual(fduuid.uuid.10, xuuid.uuid.10) 120 | XCTAssertEqual(fduuid.uuid.11, xuuid.uuid.11) 121 | XCTAssertEqual(fduuid.uuid.12, xuuid.uuid.12) 122 | XCTAssertEqual(fduuid.uuid.13, xuuid.uuid.13) 123 | XCTAssertEqual(fduuid.uuid.14, xuuid.uuid.14) 124 | XCTAssertEqual(fduuid.uuid.15, xuuid.uuid.15) 125 | } 126 | 127 | func testCustomMirror() { 128 | let xuuid = XUUID() 129 | let fduuid = UUID(uuid: xuuid.uuid) 130 | 131 | XCTAssertEqual(String(reflecting: fduuid), String(reflecting: xuuid)) 132 | 133 | let psmirror = xuuid.customMirror 134 | XCTAssert(psmirror.subjectType is XUUID.Type) 135 | XCTAssertEqual(psmirror.description, "Mirror for XUUID") 136 | XCTAssertEqual(psmirror.displayStyle, .struct) 137 | } 138 | 139 | func testHashing() { 140 | let xuuid = XUUID() 141 | let fduuid = UUID(uuid: xuuid.uuid) 142 | XCTAssertEqual(fduuid.hashValue, xuuid.hashValue) 143 | 144 | var _uuid = xuuid.uuid 145 | _uuid.0 = _uuid.0 > 0 ? _uuid.0 - 1 : 1 146 | XCTAssertNotEqual(UUID(uuid: _uuid).hashValue, xuuid.hashValue) 147 | } 148 | 149 | func testEncoding() { 150 | struct Test: Codable { 151 | let uuid: XUUID 152 | } 153 | let uuid = XUUID() 154 | let test = Test(uuid: uuid) 155 | 156 | var data: Data? 157 | XCTAssertNoThrow(data = try JSONEncoder().encode(test)) 158 | XCTAssertEqual(try String(decoding: XCTUnwrap(data), as: Unicode.UTF8.self), #"{"uuid":"\#(uuid.uuidString)"}"#) 159 | } 160 | 161 | func testDecodingSuccess() { 162 | struct Test: Codable { 163 | let uuid: XUUID 164 | } 165 | let uuid = XUUID() 166 | let data = #"{"uuid":"\#(uuid.uuidString)"}"#.data(using: .utf8) 167 | 168 | var result: Test? 169 | XCTAssertNoThrow(result = try JSONDecoder().decode(Test.self, from: XCTUnwrap(data))) 170 | XCTAssertEqual(result?.uuid, uuid) 171 | } 172 | 173 | func testDecodingFailure() { 174 | struct Test: Codable { 175 | let uuid: XUUID 176 | } 177 | let uuid = XUUID() 178 | var uuidString = uuid.uuidString 179 | _ = uuidString.removeLast() 180 | let data = #"{"uuid":"\#(uuidString)"}"#.data(using: .utf8) 181 | 182 | XCTAssertThrowsError(_ = try JSONDecoder().decode(Test.self, from: XCTUnwrap(data))) { error in 183 | XCTAssertNotNil(error as? DecodingError) 184 | } 185 | } 186 | 187 | func testStructSize() { 188 | XCTAssertEqual(MemoryLayout.size, 16) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Tests" 3 | - ".build" -------------------------------------------------------------------------------- /scripts/validity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | 7 | printf "=> Checking format... " 8 | FIRST_OUT="$(git status --porcelain)" 9 | swiftformat . > /dev/null 2>&1 10 | SECOND_OUT="$(git status --porcelain)" 11 | if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then 12 | printf "\033[0;31mformatting issues!\033[0m\n" 13 | git --no-pager diff 14 | exit 1 15 | else 16 | printf "\033[0;32mokay.\033[0m\n" 17 | fi 18 | 19 | --------------------------------------------------------------------------------