Skip to content

Commit 9b822ec

Browse files
micro-optimize UUID(::AbstractString) (#36049)
* microoptimize UUID(::AbstractString) * Update base/uuid.jl Co-authored-by: Jeff Bezanson <jeff.bezanson@gmail.com> Co-authored-by: Jeff Bezanson <jeff.bezanson@gmail.com>
2 parents 26f0f06 + f90afa6 commit 9b822ec

File tree

3 files changed

+65
-30
lines changed

3 files changed

+65
-30
lines changed

base/parse.jl

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ function parseint_preamble(signed::Bool, base::Int, s::AbstractString, startpos:
8989
return sgn, base, j
9090
end
9191

92+
@inline function __convert_digit(_c::UInt32, base)
93+
_0 = UInt32('0')
94+
_9 = UInt32('9')
95+
_A = UInt32('A')
96+
_a = UInt32('a')
97+
_Z = UInt32('Z')
98+
_z = UInt32('z')
99+
a::UInt32 = base <= 36 ? 10 : 36
100+
d = _0 <= _c <= _9 ? _c-_0 :
101+
_A <= _c <= _Z ? _c-_A+ UInt32(10) :
102+
_a <= _c <= _z ? _c-_a+a : UInt32(base)
103+
end
104+
105+
92106
function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int, base_::Integer, raise::Bool) where T<:Integer
93107
sgn, base, i = parseint_preamble(T<:Signed, Int(base_), s, startpos, endpos)
94108
if sgn == 0 && base == 0 && i == 0
@@ -115,19 +129,10 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::
115129
base == 16 ? div(typemax(T) - T(15), T(16)) :
116130
div(typemax(T) - base + 1, base)
117131
n::T = 0
118-
a::Int = base <= 36 ? 10 : 36
119-
_0 = UInt32('0')
120-
_9 = UInt32('9')
121-
_A = UInt32('A')
122-
_a = UInt32('a')
123-
_Z = UInt32('Z')
124-
_z = UInt32('z')
125132
while n <= m
126133
# Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80
127134
_c = reinterpret(UInt32, c) >> 24
128-
d::T = _0 <= _c <= _9 ? _c-_0 :
129-
_A <= _c <= _Z ? _c-_A+ UInt32(10) :
130-
_a <= _c <= _z ? _c-_a+a : base
135+
d::T = __convert_digit(_c, base)
131136
if d >= base
132137
raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))"))
133138
return nothing
@@ -145,9 +150,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::
145150
while !isspace(c)
146151
# Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80
147152
_c = reinterpret(UInt32, c) >> 24
148-
d::T = _0 <= _c <= _9 ? _c-_0 :
149-
_A <= _c <= _Z ? _c-_A+ UInt32(10) :
150-
_a <= _c <= _z ? _c-_a+a : base
153+
d::T = __convert_digit(_c, base)
151154
if d >= base
152155
raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))"))
153156
return nothing

base/uuid.jl

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,35 +30,56 @@ end
3030

3131
UInt128(u::UUID) = u.value
3232

33-
let groupings = [1:8; 10:13; 15:18; 20:23; 25:36]
34-
global UUID
35-
function UUID(s::AbstractString)
36-
s = lowercase(s)
37-
38-
if !occursin(r"^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$", s)
39-
throw(ArgumentError("Malformed UUID string: $(repr(s))"))
40-
end
33+
let
34+
@noinline throw_malformed_uuid(s) = throw(ArgumentError("Malformed UUID string: $(repr(s))"))
35+
@inline function uuid_kernel(s, i, u)
36+
_c = UInt32(@inbounds codeunit(s, i))
37+
d = __convert_digit(_c, UInt32(16))
38+
d >= 16 && throw_malformed_uuid(s)
39+
u <<= 4
40+
u | d
41+
end
4142

42-
u = UInt128(0)
43-
for i in groupings
44-
u <<= 4
45-
d = s[i] - '0'
46-
u |= 0xf & (d - 39*(d > 9))
47-
end
48-
return UUID(u)
43+
global UUID
44+
function UUID(s::AbstractString)
45+
u = UInt128(0)
46+
ncodeunits(s) != 36 && throw_malformed_uuid(s)
47+
for i in 1:8
48+
u = uuid_kernel(s, i, u)
49+
end
50+
@inbounds codeunit(s, 9) == UInt8('-') || @goto error
51+
for i in 10:13
52+
u = uuid_kernel(s, i, u)
4953
end
54+
@inbounds codeunit(s, 14) == UInt8('-') || @goto error
55+
for i in 15:18
56+
u = uuid_kernel(s, i, u)
57+
end
58+
@inbounds codeunit(s, 19) == UInt8('-') || @goto error
59+
for i in 20:23
60+
u = uuid_kernel(s, i, u)
61+
end
62+
@inbounds codeunit(s, 24) == UInt8('-') || @goto error
63+
for i in 25:36
64+
u = uuid_kernel(s, i, u)
65+
end
66+
return Base.UUID(u)
67+
@label error
68+
throw_malformed_uuid(s)
5069
end
70+
end
71+
5172

5273
let groupings = [36:-1:25; 23:-1:20; 18:-1:15; 13:-1:10; 8:-1:1]
5374
global string
5475
function string(u::UUID)
5576
u = u.value
5677
a = Base.StringVector(36)
5778
for i in groupings
58-
a[i] = hex_chars[1 + u & 0xf]
79+
@inbounds a[i] = hex_chars[1 + u & 0xf]
5980
u >>= 4
6081
end
61-
a[24] = a[19] = a[14] = a[9] = '-'
82+
@inbounds a[24] = a[19] = a[14] = a[9] = '-'
6283
return String(a)
6384
end
6485
end

stdlib/UUIDs/test/runtests.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,14 @@ u4 = uuid4()
6262
Random.seed!(Random.GLOBAL_RNG, 10)
6363
@test u1 != uuid1()
6464
@test u4 != uuid4()
65+
66+
@test_throws ArgumentError UUID("22b4a8a1ae548-4eeb-9270-60426d66a48e")
67+
@test_throws ArgumentError UUID("22b4a8a1-e548a4eeb-9270-60426d66a48e")
68+
@test_throws ArgumentError UUID("22b4a8a1-e548-4eeba9270-60426d66a48e")
69+
@test_throws ArgumentError UUID("22b4a8a1-e548-4eeb-9270a60426d66a48e")
70+
str = "22b4a8a1-e548-4eeb-9270-60426d66a48e"
71+
@test UUID(uppercase(str)) == UUID(str)
72+
73+
for r in rand(UInt128, 10^3)
74+
@test UUID(r) == UUID(string(UUID(r)))
75+
end

0 commit comments

Comments
 (0)