mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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: c44e2a2577
GitHub-Pull-Request: golang/go#75565
Reviewed-on: https://go-review.googlesource.com/c/go/+/705715
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: Damien Neil <dneil@google.com>
Auto-Submit: Emmanuel Odeke <emmanuel@orijtech.com>
This commit is contained in:
parent
08afc50bea
commit
19cc1022ba
1 changed files with 35 additions and 32 deletions
|
|
@ -98,24 +98,32 @@ func FormatMediaType(t string, param map[string]string) string {
|
||||||
func checkMediaTypeDisposition(s string) error {
|
func checkMediaTypeDisposition(s string) error {
|
||||||
typ, rest := consumeToken(s)
|
typ, rest := consumeToken(s)
|
||||||
if typ == "" {
|
if typ == "" {
|
||||||
return errors.New("mime: no media type")
|
return errNoMediaType
|
||||||
}
|
}
|
||||||
if rest == "" {
|
if rest == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(rest, "/") {
|
var ok bool
|
||||||
return errors.New("mime: expected slash after first token")
|
if rest, ok = strings.CutPrefix(rest, "/"); !ok {
|
||||||
|
return errNoSlashAfterFirstToken
|
||||||
}
|
}
|
||||||
subtype, rest := consumeToken(rest[1:])
|
subtype, rest := consumeToken(rest)
|
||||||
if subtype == "" {
|
if subtype == "" {
|
||||||
return errors.New("mime: expected token after slash")
|
return errNoTokenAfterSlash
|
||||||
}
|
}
|
||||||
if rest != "" {
|
if rest != "" {
|
||||||
return errors.New("mime: unexpected content after media subtype")
|
return errUnexpectedContentAfterMediaSubtype
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoMediaType = errors.New("mime: no media type")
|
||||||
|
errNoSlashAfterFirstToken = errors.New("mime: expected slash after first token")
|
||||||
|
errNoTokenAfterSlash = errors.New("mime: expected token after slash")
|
||||||
|
errUnexpectedContentAfterMediaSubtype = errors.New("mime: unexpected content after media subtype")
|
||||||
|
)
|
||||||
|
|
||||||
// ErrInvalidMediaParameter is returned by [ParseMediaType] if
|
// ErrInvalidMediaParameter is returned by [ParseMediaType] if
|
||||||
// the media type value was found but there was an error parsing
|
// the media type value was found but there was an error parsing
|
||||||
// the optional parameters
|
// the optional parameters
|
||||||
|
|
@ -169,7 +177,6 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
|
||||||
if continuation == nil {
|
if continuation == nil {
|
||||||
continuation = make(map[string]map[string]string)
|
continuation = make(map[string]map[string]string)
|
||||||
}
|
}
|
||||||
var ok bool
|
|
||||||
if pmap, ok = continuation[baseName]; !ok {
|
if pmap, ok = continuation[baseName]; !ok {
|
||||||
continuation[baseName] = make(map[string]string)
|
continuation[baseName] = make(map[string]string)
|
||||||
pmap = continuation[baseName]
|
pmap = continuation[baseName]
|
||||||
|
|
@ -177,7 +184,7 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
|
||||||
}
|
}
|
||||||
if v, exists := pmap[key]; exists && v != value {
|
if v, exists := pmap[key]; exists && v != value {
|
||||||
// Duplicate parameter names are incorrect, but we allow them if they are equal.
|
// Duplicate parameter names are incorrect, but we allow them if they are equal.
|
||||||
return "", nil, errors.New("mime: duplicate parameter name")
|
return "", nil, errDuplicateParamName
|
||||||
}
|
}
|
||||||
pmap[key] = value
|
pmap[key] = value
|
||||||
v = rest
|
v = rest
|
||||||
|
|
@ -227,27 +234,28 @@ func ParseMediaType(v string) (mediatype string, params map[string]string, err e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errDuplicateParamName = errors.New("mime: duplicate parameter name")
|
||||||
|
|
||||||
func decode2231Enc(v string) (string, bool) {
|
func decode2231Enc(v string) (string, bool) {
|
||||||
sv := strings.SplitN(v, "'", 3)
|
charset, v, ok := strings.Cut(v, "'")
|
||||||
if len(sv) != 3 {
|
if !ok {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
// TODO: ignoring lang in sv[1] for now. If anybody needs it we'll
|
// TODO: ignoring the language part for now. If anybody needs it, we'll
|
||||||
// need to decide how to expose it in the API. But I'm not sure
|
// need to decide how to expose it in the API. But I'm not sure
|
||||||
// anybody uses it in practice.
|
// anybody uses it in practice.
|
||||||
charset := strings.ToLower(sv[0])
|
_, extOtherVals, ok := strings.Cut(v, "'")
|
||||||
if len(charset) == 0 {
|
if !ok {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
if charset != "us-ascii" && charset != "utf-8" {
|
charset = strings.ToLower(charset)
|
||||||
// TODO: unsupported encoding
|
switch charset {
|
||||||
|
case "us-ascii", "utf-8":
|
||||||
|
default:
|
||||||
|
// Empty or unsupported encoding.
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
encv, err := percentHexUnescape(sv[2])
|
return percentHexUnescape(extOtherVals)
|
||||||
if err != nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return encv, true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// consumeToken consumes a token from the beginning of provided
|
// consumeToken consumes a token from the beginning of provided
|
||||||
|
|
@ -309,11 +317,11 @@ func consumeValue(v string) (value, rest string) {
|
||||||
|
|
||||||
func consumeMediaParam(v string) (param, value, rest string) {
|
func consumeMediaParam(v string) (param, value, rest string) {
|
||||||
rest = strings.TrimLeftFunc(v, unicode.IsSpace)
|
rest = strings.TrimLeftFunc(v, unicode.IsSpace)
|
||||||
if !strings.HasPrefix(rest, ";") {
|
var ok bool
|
||||||
|
if rest, ok = strings.CutPrefix(rest, ";"); !ok {
|
||||||
return "", "", v
|
return "", "", v
|
||||||
}
|
}
|
||||||
|
|
||||||
rest = rest[1:] // consume semicolon
|
|
||||||
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
|
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
|
||||||
param, rest = consumeToken(rest)
|
param, rest = consumeToken(rest)
|
||||||
param = strings.ToLower(param)
|
param = strings.ToLower(param)
|
||||||
|
|
@ -322,10 +330,9 @@ func consumeMediaParam(v string) (param, value, rest string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
|
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
|
||||||
if !strings.HasPrefix(rest, "=") {
|
if rest, ok = strings.CutPrefix(rest, "="); !ok {
|
||||||
return "", "", v
|
return "", "", v
|
||||||
}
|
}
|
||||||
rest = rest[1:] // consume equals sign
|
|
||||||
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
|
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
|
||||||
value, rest2 := consumeValue(rest)
|
value, rest2 := consumeValue(rest)
|
||||||
if value == "" && rest2 == rest {
|
if value == "" && rest2 == rest {
|
||||||
|
|
@ -335,7 +342,7 @@ func consumeMediaParam(v string) (param, value, rest string) {
|
||||||
return param, value, rest
|
return param, value, rest
|
||||||
}
|
}
|
||||||
|
|
||||||
func percentHexUnescape(s string) (string, error) {
|
func percentHexUnescape(s string) (string, bool) {
|
||||||
// Count %, check that they're well-formed.
|
// Count %, check that they're well-formed.
|
||||||
percents := 0
|
percents := 0
|
||||||
for i := 0; i < len(s); {
|
for i := 0; i < len(s); {
|
||||||
|
|
@ -345,16 +352,12 @@ func percentHexUnescape(s string) (string, error) {
|
||||||
}
|
}
|
||||||
percents++
|
percents++
|
||||||
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
||||||
s = s[i:]
|
return "", false
|
||||||
if len(s) > 3 {
|
|
||||||
s = s[0:3]
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("mime: bogus characters after %%: %q", s)
|
|
||||||
}
|
}
|
||||||
i += 3
|
i += 3
|
||||||
}
|
}
|
||||||
if percents == 0 {
|
if percents == 0 {
|
||||||
return s, nil
|
return s, true
|
||||||
}
|
}
|
||||||
|
|
||||||
t := make([]byte, len(s)-2*percents)
|
t := make([]byte, len(s)-2*percents)
|
||||||
|
|
@ -371,7 +374,7 @@ func percentHexUnescape(s string) (string, error) {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string(t), nil
|
return string(t), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func ishex(c byte) bool {
|
func ishex(c byte) bool {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue