Reduce allocations (#17584)

* Reduce allocations

* Add stringsHasPrefixFold which can compare string prefixes, while ignoring case and not allocating.
* Reuse all msgp.Readers
* Reuse metadata buffers when not reading data.

* Make type safe. Make buffer 4K instead of 8.

* Unslice
This commit is contained in:
Klaus Post 2023-07-06 16:02:08 -07:00 committed by GitHub
parent 1bf23374a3
commit ff5988f4e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 67 additions and 36 deletions

View file

@ -153,7 +153,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
continue
}
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
// Do not need to send any internal metadata
// values to client.
continue
@ -166,7 +166,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
var isSet bool
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(userMetadataPrefix)) {
if !stringsHasPrefixFold(k, userMetadataPrefix) {
continue
}
w.Header()[strings.ToLower(k)] = []string{v}

View file

@ -550,7 +550,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
}
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
// Do not need to send any internal metadata
// values to client.
continue
@ -693,7 +693,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
}
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
// Do not need to send any internal metadata
// values to client.
continue

View file

@ -520,7 +520,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
if len(r.Flags.Filter.Metadata) > 0 {
for _, kv := range r.Flags.Filter.Metadata {
for k, v := range oi.UserDefined {
if !strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") && !isStandardHeader(k) {
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
continue
}
// We only need to match x-amz-meta or standardHeaders
@ -1075,7 +1075,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
if len(r.Flags.Filter.Metadata) > 0 {
for _, kv := range r.Flags.Filter.Metadata {
for k, v := range info.Metadata {
if !strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") && !isStandardHeader(k) {
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
continue
}
// We only need to match x-amz-meta or standardHeaders

View file

@ -289,7 +289,7 @@ func (r *BatchJobKeyRotateV1) KeyRotate(ctx context.Context, api ObjectLayer, ob
)
encMetadata := make(map[string]string)
for k, v := range oi.UserDefined {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
encMetadata[k] = v
}
}
@ -401,7 +401,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
if len(r.Flags.Filter.Metadata) > 0 {
for _, kv := range r.Flags.Filter.Metadata {
for k, v := range info.Metadata {
if !strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") && !isStandardHeader(k) {
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
continue
}
// We only need to match x-amz-meta or standardHeaders

View file

@ -691,7 +691,7 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
if rreq.Type == SelectRestoreRequest {
for _, v := range rreq.OutputLocation.S3.UserMetadata {
if !strings.HasPrefix(strings.ToLower(v.Name), "x-amz-meta") {
if !stringsHasPrefixFold(v.Name, "x-amz-meta") {
meta["x-amz-meta-"+v.Name] = v.Value
continue
}

View file

@ -679,7 +679,7 @@ func replicateDeleteToTarget(ctx context.Context, dobj DeletedObjectReplicationI
func getCopyObjMetadata(oi ObjectInfo, sc string) map[string]string {
meta := make(map[string]string, len(oi.UserDefined))
for k, v := range oi.UserDefined {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
continue
}
@ -744,7 +744,7 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) {
meta := make(map[string]string)
for k, v := range objInfo.UserDefined {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
continue
}
if isStandardHeader(k) {
@ -909,7 +909,7 @@ func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo, opType replicati
for k, v := range oi1.UserDefined {
var found bool
for _, prefix := range compareKeys {
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
if !stringsHasPrefixFold(k, prefix) {
continue
}
found = true
@ -924,7 +924,7 @@ func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo, opType replicati
for k, v := range oi2.Metadata {
var found bool
for _, prefix := range compareKeys {
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
if !stringsHasPrefixFold(k, prefix) {
continue
}
found = true

View file

@ -139,14 +139,14 @@ func (c *cacheObjects) updateMetadataIfChanged(ctx context.Context, dcache *disk
bkMeta := make(map[string]string, len(bkObjectInfo.UserDefined))
cacheMeta := make(map[string]string, len(cacheObjInfo.UserDefined))
for k, v := range bkObjectInfo.UserDefined {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
// Do not need to send any internal metadata
continue
}
bkMeta[http.CanonicalHeaderKey(k)] = v
}
for k, v := range cacheObjInfo.UserDefined {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
// Do not need to send any internal metadata
continue
}

View file

@ -527,6 +527,10 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
metadataArray := make([]*xlMetaV2, len(disks))
metaFileInfos := make([]FileInfo, len(metadataArray))
metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks))
var v2bufs [][]byte
if !readData {
v2bufs = make([][]byte, len(disks))
}
g := errgroup.WithNErrs(len(disks))
// Read `xl.meta` in parallel across disks.
@ -540,6 +544,10 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
if err != nil {
return err
}
if !readData {
// Save the buffer so we can reuse it.
v2bufs[index] = rf.Buf
}
var xl xlMetaV2
if err = xl.LoadOrConvert(rf.Buf); err != nil {
@ -623,6 +631,11 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
}
metaFileInfos[index].DiskMTime = diskMTime
}
if !readData {
for i := range v2bufs {
metaDataPoolPut(v2bufs[i])
}
}
// Return all the metadata.
return metaFileInfos, errs

View file

@ -268,7 +268,7 @@ func (args eventArgs) ToEvent(escape bool) event.Event {
newEvent.S3.Object.ContentType = args.Object.ContentType
newEvent.S3.Object.UserMetadata = make(map[string]string, len(args.Object.UserDefined))
for k, v := range args.Object.UserDefined {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(strings.ToLower(k), ReservedMetadataPrefixLower) {
continue
}
newEvent.S3.Object.UserMetadata[k] = v

View file

@ -71,7 +71,7 @@ const (
// and must not set by clients
func containsReservedMetadata(header http.Header) bool {
for key := range header {
if strings.HasPrefix(strings.ToLower(key), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(key, ReservedMetadataPrefix) {
return true
}
}
@ -87,7 +87,7 @@ func isHTTPHeaderSizeTooLarge(header http.Header) bool {
length := len(key) + len(header.Get(key))
size += length
for _, prefix := range userMetadataKeyPrefixes {
if strings.HasPrefix(strings.ToLower(key), prefix) {
if stringsHasPrefixFold(key, prefix) {
usersize += length
break
}

View file

@ -184,7 +184,7 @@ func extractMetadataFromMime(ctx context.Context, v textproto.MIMEHeader, m map[
for key := range v {
for _, prefix := range userMetadataKeyPrefixes {
if !strings.HasPrefix(strings.ToLower(key), strings.ToLower(prefix)) {
if !stringsHasPrefixFold(key, prefix) {
continue
}
value, ok := nv[http.CanonicalHeaderKey(key)]

View file

@ -73,7 +73,7 @@ func (l *lockRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
func getLockArgs(r *http.Request) (args dsync.LockArgs, err error) {
dec := msgpNewReader(io.LimitReader(r.Body, 1000*humanize.KiByte))
defer readMsgpReaderPool.Put(dec)
defer readMsgpReaderPoolPut(dec)
err = args.DecodeMsg(dec)
return args, err
}

View file

@ -255,12 +255,13 @@ type metacacheReader struct {
func newMetacacheReader(r io.Reader) *metacacheReader {
dec := s2DecPool.Get().(*s2.Reader)
dec.Reset(r)
mr := msgp.NewReader(dec)
mr := msgpNewReader(dec)
return &metacacheReader{
mr: mr,
closer: func() {
dec.Reset(nil)
s2DecPool.Put(dec)
readMsgpReaderPoolPut(mr)
},
creator: func() error {
v, err := mr.ReadByte()

View file

@ -402,7 +402,7 @@ func extractETag(metadata map[string]string) string {
// to do case insensitive checks.
func HasPrefix(s string, prefix string) bool {
if runtime.GOOS == globalWindowsOSName {
return strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix))
return stringsHasPrefixFold(s, prefix)
}
return strings.HasPrefix(s, prefix)
}

View file

@ -1261,7 +1261,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
}
for k, v := range srcInfo.UserDefined {
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
encMetadata[k] = v
}
}

View file

@ -22,7 +22,6 @@ import (
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/klauspost/compress/gzhttp"
@ -182,7 +181,7 @@ func StatusCode(text string) int {
func fwdHeadersToS3(h http.Header, w http.ResponseWriter) {
const trim = "x-amz-fwd-header-"
for k, v := range h {
if strings.HasPrefix(strings.ToLower(k), trim) {
if stringsHasPrefixFold(k, trim) {
w.Header()[k[len(trim):]] = v
}
}

View file

@ -62,8 +62,9 @@ func getCanonicalHeaders(signedHeaders http.Header) string {
var headers []string
vals := make(http.Header)
for k, vv := range signedHeaders {
headers = append(headers, strings.ToLower(k))
vals[strings.ToLower(k)] = vv
k = strings.ToLower(k)
headers = append(headers, k)
vals[k] = vv
}
sort.Strings(headers)

View file

@ -227,7 +227,8 @@ func (client *storageRESTClient) NSScanner(ctx context.Context, cache dataUsageC
rw.CloseWithError(waitForHTTPStream(respBody, rw))
}()
ms := msgp.NewReader(rr)
ms := msgpNewReader(rr)
defer readMsgpReaderPoolPut(ms)
for {
// Read whether it is an update.
upd, err := ms.ReadBool()
@ -498,16 +499,24 @@ var readMsgpReaderPool = sync.Pool{New: func() interface{} { return &msgp.Reader
// mspNewReader returns a *Reader that reads from the provided reader.
// The reader will be buffered.
// Return with readMsgpReaderPoolPut when done.
func msgpNewReader(r io.Reader) *msgp.Reader {
p := readMsgpReaderPool.Get().(*msgp.Reader)
if p.R == nil {
p.R = xbufio.NewReaderSize(r, 8<<10)
p.R = xbufio.NewReaderSize(r, 4<<10)
} else {
p.R.Reset(r)
}
return p
}
// readMsgpReaderPoolPut can be used to reuse a *msgp.Reader.
func readMsgpReaderPoolPut(r *msgp.Reader) {
if r != nil {
readMsgpReaderPool.Put(r)
}
}
func (client *storageRESTClient) ReadVersion(ctx context.Context, volume, path, versionID string, readData bool) (fi FileInfo, err error) {
values := make(url.Values)
values.Set(storageRESTVolume, volume)
@ -522,7 +531,7 @@ func (client *storageRESTClient) ReadVersion(ctx context.Context, volume, path,
defer xhttp.DrainBody(respBody)
dec := msgpNewReader(respBody)
defer readMsgpReaderPool.Put(dec)
defer readMsgpReaderPoolPut(dec)
err = fi.DecodeMsg(dec)
return fi, err
@ -541,7 +550,7 @@ func (client *storageRESTClient) ReadXL(ctx context.Context, volume string, path
defer xhttp.DrainBody(respBody)
dec := msgpNewReader(respBody)
defer readMsgpReaderPool.Put(dec)
defer readMsgpReaderPoolPut(dec)
err = rf.DecodeMsg(dec)
return rf, err
@ -735,7 +744,7 @@ func (client *storageRESTClient) StatInfoFile(ctx context.Context, volume, path
return stat, err
}
rd := msgpNewReader(respReader)
defer readMsgpReaderPool.Put(rd)
defer readMsgpReaderPoolPut(rd)
for {
var st StatInfo
err = st.DecodeMsg(rd)
@ -774,6 +783,7 @@ func (client *storageRESTClient) ReadMultiple(ctx context.Context, req ReadMulti
pw.CloseWithError(waitForHTTPStream(respBody, pw))
}()
mr := msgp.NewReader(pr)
defer readMsgpReaderPoolPut(mr)
for {
var file ReadMultipleResp
if err := file.DecodeMsg(mr); err != nil {

View file

@ -685,7 +685,8 @@ func (s *storageRESTServer) DeleteVersionsHandler(w http.ResponseWriter, r *http
}
versions := make([]FileInfoVersions, totalVersions)
decoder := msgp.NewReader(r.Body)
decoder := msgpNewReader(r.Body)
defer readMsgpReaderPoolPut(decoder)
for i := 0; i < totalVersions; i++ {
dst := &versions[i]
if err := dst.DecodeMsg(decoder); err != nil {
@ -1294,7 +1295,7 @@ func (s *storageRESTServer) ReadMultiple(w http.ResponseWriter, r *http.Request)
var req ReadMultipleReq
mr := msgpNewReader(r.Body)
defer readMsgpReaderPool.Put(mr)
defer readMsgpReaderPoolPut(mr)
err := req.DecodeMsg(mr)
if err != nil {
rw.CloseWithError(err)

View file

@ -29,7 +29,6 @@ import (
"time"
"github.com/minio/minio/internal/logger"
"github.com/tinylib/msgp/msgp"
)
//go:generate msgp -file $GOFILE -unexported
@ -120,7 +119,8 @@ func (jd *tierDiskJournal) WalkEntries(ctx context.Context, fn walkFn) {
return
}
defer ro.Close()
mr := msgp.NewReader(ro)
mr := msgpNewReader(ro)
defer readMsgpReaderPoolPut(mr)
done := false
for {

View file

@ -1286,3 +1286,9 @@ func unwrapAll(err error) error {
err = werr
}
}
// stringsHasPrefixFold tests whether the string s begins with prefix ignoring case.
func stringsHasPrefixFold(s, prefix string) bool {
// Test match with case first.
return len(s) >= len(prefix) && (s[0:len(prefix)] == prefix || strings.EqualFold(s[0:len(prefix)], prefix))
}