Skip to content

Instantly share code, notes, and snippets.

@benasher44
Last active May 2, 2018 19:00
Show Gist options
  • Save benasher44/b6c908b465cb8f5c157123b91a10fd9c to your computer and use it in GitHub Desktop.
Save benasher44/b6c908b465cb8f5c157123b91a10fd9c to your computer and use it in GitHub Desktop.
Fast Swift implementation of UUID(uuidString:)
import Foundation
extension UUID {
/// Ranges within a UUID string that contain non-hyphen characters
private static let charRanges = [
(0..<8),
(9..<13),
(14..<18),
(19..<23),
(24..<36),
]
/// - Returns: Returns the UUID byte for a given UUID string character
private static func uuidByte(for char: CChar) -> UInt8? {
switch char {
case _ where char >= 65 && char < 71: // A-F
return UInt8(char).unsafeSubtracting(55)
case _ where char >= 97 && char < 103: // a-f
return UInt8(char).unsafeSubtracting(87)
case _ where char >= 48 && char < 58: // 0-9
return UInt8(char).unsafeSubtracting(48)
default:
return nil
}
}
/// Translates a UUID UTF-8 string into UUID bytes
private static func uuid(from cStringBuffer: UnsafeBufferPointer<CChar>) -> uuid_t? {
// Check that hyphens are in the right places
guard cStringBuffer[8] == 45,
cStringBuffer[13] == 45,
cStringBuffer[18] == 45,
cStringBuffer[23] == 45
else { return nil }
// Build the uuid_t
var uuid: uuid_t = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
let success = withUnsafeMutableBytes(of: &uuid) { (uuidBytes) -> Bool in
var offset = 0
for range in UUID.charRanges {
// For each sequence of non-hyphen UUID characters, construct a byte from each
// consecutive pair of UUID characters
var i = range.lowerBound
while i < range.upperBound {
guard let firstHalf = UUID.uuidByte(for: cStringBuffer[i]),
let secondHalf = UUID.uuidByte(for: cStringBuffer[i + 1]) else { return false }
uuidBytes[offset] = (firstHalf &<< 4) | secondHalf
i += 2
offset += 1
}
}
return true
}
guard success else { return nil }
return uuid
}
/// Initializes a `UUID` from a UTF8 C-String
///
/// - Parameters:
/// - cString: Pointer the start of the C string
/// - validateLength: If true, we use `strlen` to validate the length of the string; otherwise
/// we assume the length is already the expected 36 characters
public init?(from cString: UnsafePointer<CChar>, validateLength: Bool) {
// skip validating the length or check that the length is the 36 characters in a UUID string
guard !validateLength || strlen(cString) == 36 else { return nil }
guard let uuid = UUID.uuid(from: UnsafeBufferPointer(start: cString, count: 36)) else {
return nil
}
self.init(uuid: uuid)
}
/// Initializes a `UUID` from a `String`
///
/// - Parameter uuidString: The `UUID` string
/// - Note: This uses native `UUID` parsing
public init?(from uuidString: String) {
let cString = uuidString.utf8CString
// 36 characters + null terminating byte
guard cString.count == 37 else { return nil }
guard let uuid = cString.withUnsafeBufferPointer(UUID.uuid(from:)) else { return nil }
self.init(uuid: uuid)
}
}
@chrisbrandow
Copy link

thanks for posting this.

perhaps uuidByte(for:) might be a little cleaner with the following, which based on my tests (compiled with -O flag, test shown below) seems to be same speed:

switch char {
	case 65..<71: return UInt8(char).unsafeSubtracting(55)
	case 97..<103: return UInt8(char).unsafeSubtracting(87)
	case 48..<58: return UInt8(char).unsafeSubtracting(48)
	default: return nil
}

testing code:

let strings: [String] = (100000..<990000).map({ 
	let value = $0 + Int(arc4random_uniform(10))
	let this: String = String(value) + "81-c869-4b3f-810c-5ccf66b19831"
	return this
})

let start = Date()
for string in strings { 
	let uuid = UUID(uuidString: string)
	if uuid == nil {
		print("failed for \(string)")
	}
}
print(start.timeIntervalSinceNow)

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