github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/stringarena/arena.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package stringarena 12 13 import ( 14 "context" 15 "unsafe" 16 17 "github.com/cockroachdb/cockroach/pkg/util/mon" 18 ) 19 20 // Arena provides arena allocation of a string from a []byte, reducing 21 // allocation overhead and GC pressure significantly if a large number of 22 // strings with a similar lifetime are being created. The reduction in 23 // allocation overhead should be obvious because we're replacing general 24 // allocation with what is essentially bump-pointer allocation. The reduction 25 // in GC pressure is due to the decrease in objects that the GC has to 26 // consider. 27 type Arena struct { 28 alloc []byte 29 acc *mon.BoundAccount 30 size int64 31 } 32 33 // Make creates a new Arena with the specified monitoring account. If acc is 34 // nil, memory monitoring will be disabled. 35 func Make(acc *mon.BoundAccount) Arena { 36 return Arena{acc: acc} 37 } 38 39 // UnsafeReset informs the memory account that previously allocated strings will 40 // no longer be used, and moves the current block pointer to the front of the 41 // buffer. Prefer to use this over creating a new arena in situations where we 42 // know that none of the strings currently on the arena will be referenced 43 // again. 44 // 45 // NOTE: Do NOT use this if you cannot guarantee that previously allocated 46 // strings will never be referenced again! If we cannot guarantee that, we run 47 // the risk of overwriting their contents with new data, which violates 48 // assumptions about the immutability of Go's strings. 49 // 50 // To prevent overwriting, we theoretically only need to ensure that strings 51 // allocated on the current block are never used again, but callers are 52 // oblivious to which block the string they get is allocated on, so it is easier 53 // to ensure that no previously allocated strings are used again. 54 func (a *Arena) UnsafeReset(ctx context.Context) error { 55 newSize := int64(cap(a.alloc)) 56 if a.acc != nil { 57 if err := a.acc.Resize(ctx, a.size, newSize); err != nil { 58 return err 59 } 60 } 61 a.alloc = a.alloc[:0] 62 a.size = newSize 63 return nil 64 } 65 66 // AllocBytes allocates a string in the arena with contents specified by 67 // b. Returns an error on memory accounting failure. The returned string can be 68 // used for as long as desired, but it will pin the entire underlying chunk of 69 // memory it was allocated from which can be significantly larger than the 70 // string itself. The primary use case for arena allocation is when all of the 71 // allocated strings will be released in mass. 72 func (a *Arena) AllocBytes(ctx context.Context, b []byte) (string, error) { 73 n := len(b) 74 if cap(a.alloc)-len(a.alloc) < n { 75 if err := a.reserve(ctx, n); err != nil { 76 return "", err 77 } 78 } 79 80 // Note that we're carving the bytes for the string from the end of 81 // a.alloc. This allows us to use cap(a.alloc) to reserve large chunk sizes 82 // up to a maximum. See reserve(). 83 pos := len(a.alloc) 84 data := a.alloc[pos : pos+n : pos+n] 85 a.alloc = a.alloc[:pos+n] 86 87 copy(data, b) 88 // NB: string and []byte have the same layout for the first 2 fields. This 89 // use of unsafe avoids an allocation. We're promising to never mutate "data" 90 // at this point, which is true because we carved it off from ss.alloc. 91 return *(*string)(unsafe.Pointer(&data)), nil 92 } 93 94 // TODO(peter): This is copied from util/bufalloc in order to support memory 95 // accounting. Perhaps figure out how to share the code. 96 func (a *Arena) reserve(ctx context.Context, n int) error { 97 const chunkAllocMinSize = 512 98 const chunkAllocMaxSize = 16384 99 100 allocSize := cap(a.alloc) * 2 101 if allocSize < chunkAllocMinSize { 102 allocSize = chunkAllocMinSize 103 } else if allocSize > chunkAllocMaxSize { 104 allocSize = chunkAllocMaxSize 105 } 106 if allocSize < n { 107 allocSize = n 108 } 109 if a.acc != nil { 110 if err := a.acc.Grow(ctx, int64(allocSize)); err != nil { 111 return err 112 } 113 } 114 a.alloc = make([]byte, 0, allocSize) 115 a.size += int64(allocSize) 116 return nil 117 }