Skip to content

Commit 19cc102

Browse files
jub0bsgopherbot
authored andcommitted
mime: reduce allocs incurred by ParseMediaType
This change is mostly gardening. It simplifies ParseMediaType and its helper functions and reduces the amount of allocations they incur. Here are some benchmark results: goos: darwin goarch: amd64 pkg: mime cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz │ old │ new │ │ sec/op │ sec/op vs base │ ParseMediaType-8 55.26µ ± 1% 54.54µ ± 1% -1.30% (p=0.000 n=20) ParseMediaTypeBogus-8 3.551µ ± 0% 3.427µ ± 0% -3.48% (p=0.000 n=20) geomean 14.01µ 13.67µ -2.39% │ old │ new │ │ B/op │ B/op vs base │ ParseMediaType-8 38.48Ki ± 0% 37.38Ki ± 0% -2.85% (p=0.000 n=20) ParseMediaTypeBogus-8 2.531Ki ± 0% 2.469Ki ± 0% -2.47% (p=0.000 n=20) geomean 9.869Ki 9.606Ki -2.66% │ old │ new │ │ allocs/op │ allocs/op vs base │ ParseMediaType-8 457.0 ± 0% 425.0 ± 0% -7.00% (p=0.000 n=20) ParseMediaTypeBogus-8 25.00 ± 0% 21.00 ± 0% -16.00% (p=0.000 n=20) geomean 106.9 94.47 -11.62% Change-Id: I51198b40396afa51531794a57c50aa88975eae1d GitHub-Last-Rev: c44e2a2 GitHub-Pull-Request: #75565 Reviewed-on: https://go-review.googlesource.com/c/go/+/705715 Reviewed-by: Emmanuel Odeke <[email protected]> Reviewed-by: Carlos Amedee <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Sean Liao <[email protected]> Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Emmanuel Odeke <[email protected]>
1 parent 08afc50 commit 19cc102

File tree

1 file changed

+35
-32
lines changed

1 file changed

+35
-32
lines changed

src/mime/mediatype.go

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -98,24 +98,32 @@ func FormatMediaType(t string, param map[string]string) string {
9898
func checkMediaTypeDisposition(s string) error {
9999
typ, rest := consumeToken(s)
100100
if typ == "" {
101-
return errors.New("mime: no media type")
101+
return errNoMediaType
102102
}
103103
if rest == "" {
104104
return nil
105105
}
106-
if !strings.HasPrefix(rest, "/") {
107-
return errors.New("mime: expected slash after first token")
106+
var ok bool
107+
if rest, ok = strings.CutPrefix(rest, "/"); !ok {
108+
return errNoSlashAfterFirstToken
108109
}
109-
subtype, rest := consumeToken(rest[1:])
110+
subtype, rest := consumeToken(rest)
110111
if subtype == "" {
111-
return errors.New("mime: expected token after slash")
112+
return errNoTokenAfterSlash
112113
}
113114
if rest != "" {
114-
return errors.New("mime: unexpected content after media subtype")
115+
return errUnexpectedContentAfterMediaSubtype
115116
}
116117
return nil
117118
}
118119

120+
var (
121+
errNoMediaType = errors.New("mime: no media type")
122+
errNoSlashAfterFirstToken = errors.New("mime: expected slash after first token")
123+
errNoTokenAfterSlash = errors.New("mime: expected token after slash")
124+
errUnexpectedContentAfterMediaSubtype = errors.New("mime: unexpected content after media subtype")
125+
)
126+
119127
// ErrInvalidMediaParameter is returned by [ParseMediaType] if
120128
// the media type value was found but there was an error parsing
121129
// the optional parameters
@@ -169,15 +177,14 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
169177
if continuation == nil {
170178
continuation = make(map[string]map[string]string)
171179
}
172-
var ok bool
173180
if pmap, ok = continuation[baseName]; !ok {
174181
continuation[baseName] = make(map[string]string)
175182
pmap = continuation[baseName]
176183
}
177184
}
178185
if v, exists := pmap[key]; exists && v != value {
179186
// Duplicate parameter names are incorrect, but we allow them if they are equal.
180-
return "", nil, errors.New("mime: duplicate parameter name")
187+
return "", nil, errDuplicateParamName
181188
}
182189
pmap[key] = value
183190
v = rest
@@ -227,27 +234,28 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
227234
return
228235
}
229236

237+
var errDuplicateParamName = errors.New("mime: duplicate parameter name")
238+
230239
func decode2231Enc(v string) (string, bool) {
231-
sv := strings.SplitN(v, "'", 3)
232-
if len(sv) != 3 {
240+
charset, v, ok := strings.Cut(v, "'")
241+
if !ok {
233242
return "", false
234243
}
235-
// TODO: ignoring lang in sv[1] for now. If anybody needs it we'll
244+
// TODO: ignoring the language part for now. If anybody needs it, we'll
236245
// need to decide how to expose it in the API. But I'm not sure
237246
// anybody uses it in practice.
238-
charset := strings.ToLower(sv[0])
239-
if len(charset) == 0 {
247+
_, extOtherVals, ok := strings.Cut(v, "'")
248+
if !ok {
240249
return "", false
241250
}
242-
if charset != "us-ascii" && charset != "utf-8" {
243-
// TODO: unsupported encoding
251+
charset = strings.ToLower(charset)
252+
switch charset {
253+
case "us-ascii", "utf-8":
254+
default:
255+
// Empty or unsupported encoding.
244256
return "", false
245257
}
246-
encv, err := percentHexUnescape(sv[2])
247-
if err != nil {
248-
return "", false
249-
}
250-
return encv, true
258+
return percentHexUnescape(extOtherVals)
251259
}
252260

253261
// consumeToken consumes a token from the beginning of provided
@@ -309,11 +317,11 @@ func consumeValue(v string) (value, rest string) {
309317

310318
func consumeMediaParam(v string) (param, value, rest string) {
311319
rest = strings.TrimLeftFunc(v, unicode.IsSpace)
312-
if !strings.HasPrefix(rest, ";") {
320+
var ok bool
321+
if rest, ok = strings.CutPrefix(rest, ";"); !ok {
313322
return "", "", v
314323
}
315324

316-
rest = rest[1:] // consume semicolon
317325
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
318326
param, rest = consumeToken(rest)
319327
param = strings.ToLower(param)
@@ -322,10 +330,9 @@ func consumeMediaParam(v string) (param, value, rest string) {
322330
}
323331

324332
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
325-
if !strings.HasPrefix(rest, "=") {
333+
if rest, ok = strings.CutPrefix(rest, "="); !ok {
326334
return "", "", v
327335
}
328-
rest = rest[1:] // consume equals sign
329336
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
330337
value, rest2 := consumeValue(rest)
331338
if value == "" && rest2 == rest {
@@ -335,7 +342,7 @@ func consumeMediaParam(v string) (param, value, rest string) {
335342
return param, value, rest
336343
}
337344

338-
func percentHexUnescape(s string) (string, error) {
345+
func percentHexUnescape(s string) (string, bool) {
339346
// Count %, check that they're well-formed.
340347
percents := 0
341348
for i := 0; i < len(s); {
@@ -345,16 +352,12 @@ func percentHexUnescape(s string) (string, error) {
345352
}
346353
percents++
347354
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
348-
s = s[i:]
349-
if len(s) > 3 {
350-
s = s[0:3]
351-
}
352-
return "", fmt.Errorf("mime: bogus characters after %%: %q", s)
355+
return "", false
353356
}
354357
i += 3
355358
}
356359
if percents == 0 {
357-
return s, nil
360+
return s, true
358361
}
359362

360363
t := make([]byte, len(s)-2*percents)
@@ -371,7 +374,7 @@ func percentHexUnescape(s string) (string, error) {
371374
i++
372375
}
373376
}
374-
return string(t), nil
377+
return string(t), true
375378
}
376379

377380
func ishex(c byte) bool {

0 commit comments

Comments
 (0)