Skip to content

Commit 2a97ddf

Browse files
committed
improve performance and reduce allocations of most UUID methods
This commit improves the performance and reduces the number of allocs of most UUID methods and adds a new UUID.Parse() method for parsing string encoded UUIDs. Parsing string encoded UUIDs is now 2x faster and no longer allocates. The NullUUID MarshalJSON() and UnmarshalJSON() methods have also been improved and no longer call out to json.Unmarshal. The UUID.Format method has been improved for common cases. Benchmark results: ``` goos: linux goarch: amd64 pkg: github.com/gofrs/uuid cpu: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz name old time/op new time/op delta UnmarshalText/canonical-16 47.7ns ± 0% 23.1ns ± 1% -51.56% (p=0.000 n=8+10) UnmarshalText/urn-16 47.8ns ± 1% 23.0ns ± 1% -51.85% (p=0.000 n=10+10) UnmarshalText/braced-16 47.8ns ± 1% 23.1ns ± 1% -51.66% (p=0.000 n=9+10) ParseV4-16 86.5ns ±10% 23.0ns ± 5% -73.44% (p=0.000 n=9+10) NullMarshalJSON/Valid-16 308ns ±21% 64ns ±23% -79.18% (p=0.000 n=10+10) NullMarshalJSON/Invalid-16 41.8ns ± 2% 1.5ns ± 4% -96.51% (p=0.000 n=10+10) Format/s-16 151ns ± 3% 132ns ± 3% -12.65% (p=0.000 n=10+10) Format/S-16 305ns ± 2% 157ns ± 4% -48.70% (p=0.000 n=10+10) Format/q-16 217ns ±12% 139ns ± 6% -35.71% (p=0.000 n=10+10) Format/x-16 170ns ±13% 120ns ± 2% -29.41% (p=0.000 n=10+10) Format/X-16 324ns ±11% 142ns ± 0% -56.24% (p=0.000 n=10+10) Format/v-16 156ns ± 4% 143ns ± 9% -8.51% (p=0.000 n=10+10) Format/+v-16 155ns ± 3% 141ns ± 8% -9.11% (p=0.000 n=10+10) Format/#v-16 894ns ± 1% 860ns ± 0% -3.84% (p=0.000 n=10+10) String-16 70.1ns ±36% 71.9ns ±22% ~ (p=0.393 n=10+10) FromBytes-16 1.81ns ± 0% 1.82ns ± 2% ~ (p=0.369 n=8+10) FromString/canonical-16 94.3ns ±20% 22.1ns ± 0% -76.53% (p=0.000 n=10+10) FromString/urn-16 93.7ns ±11% 22.4ns ± 0% -76.07% (p=0.000 n=10+10) FromString/braced-16 87.1ns ± 5% 22.6ns ± 0% -74.04% (p=0.000 n=9+10) MarshalBinary-16 0.20ns ± 3% 0.20ns ± 1% ~ (p=0.515 n=10+10) MarshalText-16 115ns ±25% 19ns ± 3% -83.09% (p=0.000 n=10+9) name old alloc/op new alloc/op delta UnmarshalText/canonical-16 0.00B 0.00B ~ (all equal) UnmarshalText/urn-16 0.00B 0.00B ~ (all equal) UnmarshalText/braced-16 0.00B 0.00B ~ (all equal) ParseV4-16 48.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) NullMarshalJSON/Valid-16 160B ± 0% 48B ± 0% -70.00% (p=0.000 n=10+10) NullMarshalJSON/Invalid-16 8.00B ± 0% 0.00B -100.00% (p=0.000 n=10+10) Format/s-16 48.0B ± 0% 48.0B ± 0% ~ (all equal) Format/S-16 96.0B ± 0% 48.0B ± 0% -50.00% (p=0.000 n=10+10) Format/q-16 96.0B ± 0% 48.0B ± 0% -50.00% (p=0.000 n=10+10) Format/x-16 64.0B ± 0% 32.0B ± 0% -50.00% (p=0.000 n=10+10) Format/X-16 112B ± 0% 32B ± 0% -71.43% (p=0.000 n=10+10) Format/v-16 48.0B ± 0% 48.0B ± 0% ~ (all equal) Format/+v-16 48.0B ± 0% 48.0B ± 0% ~ (all equal) Format/#v-16 128B ± 0% 16B ± 0% -87.50% (p=0.000 n=10+10) String-16 48.0B ± 0% 48.0B ± 0% ~ (all equal) FromBytes-16 0.00B 0.00B ~ (all equal) FromString/canonical-16 48.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) FromString/urn-16 48.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) FromString/braced-16 48.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) MarshalBinary-16 0.00B 0.00B ~ (all equal) MarshalText-16 96.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) name old allocs/op new allocs/op delta UnmarshalText/canonical-16 0.00 0.00 ~ (all equal) UnmarshalText/urn-16 0.00 0.00 ~ (all equal) UnmarshalText/braced-16 0.00 0.00 ~ (all equal) ParseV4-16 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) NullMarshalJSON/Valid-16 4.00 ± 0% 1.00 ± 0% -75.00% (p=0.000 n=10+10) NullMarshalJSON/Invalid-16 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) Format/s-16 1.00 ± 0% 1.00 ± 0% ~ (all equal) Format/S-16 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=10+10) Format/q-16 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=10+10) Format/x-16 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=10+10) Format/X-16 3.00 ± 0% 1.00 ± 0% -66.67% (p=0.000 n=10+10) Format/v-16 1.00 ± 0% 1.00 ± 0% ~ (all equal) Format/+v-16 1.00 ± 0% 1.00 ± 0% ~ (all equal) Format/#v-16 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=10+10) String-16 1.00 ± 0% 1.00 ± 0% ~ (all equal) FromBytes-16 0.00 0.00 ~ (all equal) FromString/canonical-16 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) FromString/urn-16 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) FromString/braced-16 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) MarshalBinary-16 0.00 0.00 ~ (all equal) MarshalText-16 2.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) ```
1 parent e1079f3 commit 2a97ddf

File tree

6 files changed

+372
-160
lines changed

6 files changed

+372
-160
lines changed

codec.go

+111-88
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
package uuid
2323

2424
import (
25-
"bytes"
26-
"encoding/hex"
25+
"errors"
2726
"fmt"
2827
)
2928

@@ -45,11 +44,77 @@ func FromBytesOrNil(input []byte) UUID {
4544
return uuid
4645
}
4746

47+
var errInvalidFormat = errors.New("uuid: invalid UUID format")
48+
49+
func fromHexChar(c byte) byte {
50+
switch {
51+
case '0' <= c && c <= '9':
52+
return c - '0'
53+
case 'a' <= c && c <= 'f':
54+
return c - 'a' + 10
55+
case 'A' <= c && c <= 'F':
56+
return c - 'A' + 10
57+
}
58+
return 255
59+
}
60+
61+
// Parse parses the UUID stored in the string text. Parsing and supported
62+
// formats are the same as UnmarshalText.
63+
func (u *UUID) Parse(s string) error {
64+
switch len(s) {
65+
case 32: // hash
66+
case 36: // canonical
67+
case 34, 38:
68+
if s[0] != '{' || s[len(s)-1] != '}' {
69+
return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
70+
}
71+
s = s[1 : len(s)-1]
72+
case 41, 45:
73+
if s[:9] != "urn:uuid:" {
74+
return fmt.Errorf("uuid: incorrect UUID format in string %q", s[:9])
75+
}
76+
s = s[9:]
77+
default:
78+
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(s), s)
79+
}
80+
// canonical
81+
if len(s) == 36 {
82+
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
83+
return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
84+
}
85+
for i, x := range [16]byte{
86+
0, 2, 4, 6,
87+
9, 11,
88+
14, 16,
89+
19, 21,
90+
24, 26, 28, 30, 32, 34,
91+
} {
92+
v1 := fromHexChar(s[x])
93+
v2 := fromHexChar(s[x+1])
94+
if v1|v2 == 255 {
95+
return errInvalidFormat
96+
}
97+
u[i] = (v1 << 4) | v2
98+
}
99+
return nil
100+
}
101+
// hash like
102+
for i := 0; i < 32; i += 2 {
103+
v1 := fromHexChar(s[i])
104+
v2 := fromHexChar(s[i+1])
105+
if v1|v2 == 255 {
106+
return errInvalidFormat
107+
}
108+
u[i/2] = (v1 << 4) | v2
109+
}
110+
return nil
111+
}
112+
48113
// FromString returns a UUID parsed from the input string.
49114
// Input is expected in a form accepted by UnmarshalText.
50-
func FromString(input string) (UUID, error) {
51-
u := UUID{}
52-
err := u.UnmarshalText([]byte(input))
115+
func FromString(text string) (UUID, error) {
116+
var u UUID
117+
err := u.Parse(text)
53118
return u, err
54119
}
55120

@@ -66,7 +131,9 @@ func FromStringOrNil(input string) UUID {
66131
// MarshalText implements the encoding.TextMarshaler interface.
67132
// The encoding is the same as returned by the String() method.
68133
func (u UUID) MarshalText() ([]byte, error) {
69-
return []byte(u.String()), nil
134+
var buf [36]byte
135+
encodeCanonical(buf[:], u)
136+
return buf[:], nil
70137
}
71138

72139
// UnmarshalText implements the encoding.TextUnmarshaler interface.
@@ -103,96 +170,52 @@ func (u UUID) MarshalText() ([]byte, error) {
103170
// braced := '{' plain '}' | '{' hashlike '}'
104171
// urn := URN ':' UUID-NID ':' plain
105172
//
106-
func (u *UUID) UnmarshalText(text []byte) error {
107-
switch len(text) {
108-
case 32:
109-
return u.decodeHashLike(text)
173+
func (u *UUID) UnmarshalText(b []byte) error {
174+
switch len(b) {
175+
case 32: // hash
176+
case 36: // canonical
110177
case 34, 38:
111-
return u.decodeBraced(text)
112-
case 36:
113-
return u.decodeCanonical(text)
178+
if b[0] != '{' || b[len(b)-1] != '}' {
179+
return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
180+
}
181+
b = b[1 : len(b)-1]
114182
case 41, 45:
115-
return u.decodeURN(text)
183+
if string(b[:9]) != "urn:uuid:" {
184+
return fmt.Errorf("uuid: incorrect UUID format in string %q", b[:9])
185+
}
186+
b = b[9:]
116187
default:
117-
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(text), text)
118-
}
119-
}
120-
121-
// decodeCanonical decodes UUID strings that are formatted as defined in RFC-4122 (section 3):
122-
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
123-
func (u *UUID) decodeCanonical(t []byte) error {
124-
if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' {
125-
return fmt.Errorf("uuid: incorrect UUID format in string %q", t)
188+
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(b), b)
126189
}
127-
128-
src := t
129-
dst := u[:]
130-
131-
for i, byteGroup := range byteGroups {
132-
if i > 0 {
133-
src = src[1:] // skip dash
190+
if len(b) == 36 {
191+
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
192+
return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
134193
}
135-
_, err := hex.Decode(dst[:byteGroup/2], src[:byteGroup])
136-
if err != nil {
137-
return err
194+
for i, x := range [16]byte{
195+
0, 2, 4, 6,
196+
9, 11,
197+
14, 16,
198+
19, 21,
199+
24, 26, 28, 30, 32, 34,
200+
} {
201+
v1 := fromHexChar(b[x])
202+
v2 := fromHexChar(b[x+1])
203+
if v1|v2 == 255 {
204+
return errInvalidFormat
205+
}
206+
u[i] = (v1 << 4) | v2
138207
}
139-
src = src[byteGroup:]
140-
dst = dst[byteGroup/2:]
141-
}
142-
143-
return nil
144-
}
145-
146-
// decodeHashLike decodes UUID strings that are using the following format:
147-
// "6ba7b8109dad11d180b400c04fd430c8".
148-
func (u *UUID) decodeHashLike(t []byte) error {
149-
src := t[:]
150-
dst := u[:]
151-
152-
_, err := hex.Decode(dst, src)
153-
return err
154-
}
155-
156-
// decodeBraced decodes UUID strings that are using the following formats:
157-
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}"
158-
// "{6ba7b8109dad11d180b400c04fd430c8}".
159-
func (u *UUID) decodeBraced(t []byte) error {
160-
l := len(t)
161-
162-
if t[0] != '{' || t[l-1] != '}' {
163-
return fmt.Errorf("uuid: incorrect UUID format in string %q", t)
208+
return nil
164209
}
165-
166-
return u.decodePlain(t[1 : l-1])
167-
}
168-
169-
// decodeURN decodes UUID strings that are using the following formats:
170-
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
171-
// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8".
172-
func (u *UUID) decodeURN(t []byte) error {
173-
total := len(t)
174-
175-
urnUUIDPrefix := t[:9]
176-
177-
if !bytes.Equal(urnUUIDPrefix, urnPrefix) {
178-
return fmt.Errorf("uuid: incorrect UUID format in string %q", t)
179-
}
180-
181-
return u.decodePlain(t[9:total])
182-
}
183-
184-
// decodePlain decodes UUID strings that are using the following formats:
185-
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format
186-
// "6ba7b8109dad11d180b400c04fd430c8".
187-
func (u *UUID) decodePlain(t []byte) error {
188-
switch len(t) {
189-
case 32:
190-
return u.decodeHashLike(t)
191-
case 36:
192-
return u.decodeCanonical(t)
193-
default:
194-
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(t), t)
210+
for i := 0; i < 32; i += 2 {
211+
v1 := fromHexChar(b[i])
212+
v2 := fromHexChar(b[i+1])
213+
if v1|v2 == 255 {
214+
return errInvalidFormat
215+
}
216+
u[i/2] = (v1 << 4) | v2
195217
}
218+
return nil
196219
}
197220

198221
// MarshalBinary implements the encoding.BinaryMarshaler interface.

0 commit comments

Comments
 (0)