What version of Go are you using (go version)?
1.14
What did you do?
For our use-case (a HTTP proxy) textproto.ReadMIMEHeader is in the top 3 in term of allocations. In order to ease the GC, we could pre-allocate 512 bytes to be used for small header values. This can reduce by half the number of allocations for this function.
name old time/op new time/op delta
ReadMIMEHeader/client_headers-16 2.30µs ± 5% 2.11µs ± 2% -8.21% (p=0.000 n=10+10)
ReadMIMEHeader/server_headers-16 1.94µs ± 3% 1.85µs ± 3% -4.56% (p=0.000 n=10+10)
name old alloc/op new alloc/op delta
ReadMIMEHeader/client_headers-16 1.53kB ± 0% 1.69kB ± 0% +10.48% (p=0.000 n=10+10)
ReadMIMEHeader/server_headers-16 1.09kB ± 0% 1.44kB ± 0% +32.32% (p=0.000 n=10+10)
name old allocs/op new allocs/op delta
ReadMIMEHeader/client_headers-16 14.0 ± 0% 6.0 ± 0% -57.14% (p=0.000 n=10+10)
ReadMIMEHeader/server_headers-16 14.0 ± 0% 6.0 ± 0% -57.14% (p=0.000 n=10+10)
The patch is pretty small:
index d26e981ae4..6126a6685c 100644
--- a/src/net/textproto/reader.go
+++ b/src/net/textproto/reader.go
@@ -13,6 +13,7 @@ import (
"strconv"
"strings"
"sync"
+ "unsafe"
)
// A Reader implements convenience methods for reading requests
@@ -502,6 +503,12 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
return m, ProtocolError("malformed MIME header initial line: " + string(line))
}
+ // Create a pre-allocated byte slice for all header values to save
+ // allocations for the first 512 bytes of values, a size that fit typical
+ // small header values, larger ones will get their own allocation.
+ const valuesPreAllocSize = 1 << 9
+ valuesPreAlloc := make([]byte, 0, valuesPreAllocSize)
+
for {
kv, err := r.readContinuedLineSlice(mustHaveFieldNameColon)
if len(kv) == 0 {
@@ -527,7 +534,17 @@ func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
for i < len(kv) && (kv[i] == ' ' || kv[i] == '\t') {
i++
}
- value := string(kv[i:])
+
+ // Try to fit the value in the pre-allocated buffer to save allocations.
+ var value string
+ if len(kv[i:]) <= valuesPreAllocSize-len(valuesPreAlloc) {
+ off := len(valuesPreAlloc)
+ valuesPreAlloc = append(valuesPreAlloc, kv[i:]...)
+ v := valuesPreAlloc[off:]
+ value = *(*string)(unsafe.Pointer(&v))
+ } else {
+ value = string(kv[i:])
+ }
vv := m[key]
if vv == nil && len(strs) > 0 {
Please tell me if it's worth submitting a PR with this change.
What version of Go are you using (
go version)?What did you do?
For our use-case (a HTTP proxy)
textproto.ReadMIMEHeaderis in the top 3 in term of allocations. In order to ease the GC, we could pre-allocate 512 bytes to be used for small header values. This can reduce by half the number of allocations for this function.The patch is pretty small:
Please tell me if it's worth submitting a PR with this change.