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  }