github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/colmem/allocator.go (about)

     1  // Copyright 2019 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 colmem
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"time"
    17  	"unsafe"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/col/coldata"
    20  	"github.com/cockroachdb/cockroach/pkg/col/typeconv"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/colexecbase/colexecerror"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    24  	"github.com/cockroachdb/cockroach/pkg/util/duration"
    25  	"github.com/cockroachdb/cockroach/pkg/util/mon"
    26  	"github.com/cockroachdb/errors"
    27  )
    28  
    29  // Allocator is a memory management tool for vectorized components. It provides
    30  // new batches (and appends to existing ones) within a fixed memory budget. If
    31  // the budget is exceeded, it will panic with an error.
    32  //
    33  // In the future this can also be used to pool coldata.Vec allocations.
    34  type Allocator struct {
    35  	ctx     context.Context
    36  	acc     *mon.BoundAccount
    37  	factory coldata.ColumnFactory
    38  }
    39  
    40  func selVectorSize(capacity int) int64 {
    41  	return int64(capacity * sizeOfInt)
    42  }
    43  
    44  func getVecMemoryFootprint(vec coldata.Vec) int64 {
    45  	if vec.CanonicalTypeFamily() == types.BytesFamily {
    46  		return int64(vec.Bytes().Size())
    47  	}
    48  	return int64(EstimateBatchSizeBytes([]*types.T{vec.Type()}, vec.Capacity()))
    49  }
    50  
    51  func getVecsMemoryFootprint(vecs []coldata.Vec) int64 {
    52  	var size int64
    53  	for _, dest := range vecs {
    54  		size += getVecMemoryFootprint(dest)
    55  	}
    56  	return size
    57  }
    58  
    59  // GetProportionalBatchMemSize returns the memory size of the batch that is
    60  // proportional to given 'length'. This method returns the estimated memory
    61  // footprint *only* of the first 'length' tuples in 'b'.
    62  func GetProportionalBatchMemSize(b coldata.Batch, length int64) int64 {
    63  	usesSel := b.Selection() != nil
    64  	b.SetSelection(true)
    65  	selCapacity := cap(b.Selection())
    66  	b.SetSelection(usesSel)
    67  	proportionalBatchMemSize := int64(0)
    68  	if selCapacity > 0 {
    69  		proportionalBatchMemSize = selVectorSize(selCapacity) * length / int64(selCapacity)
    70  	}
    71  	for _, vec := range b.ColVecs() {
    72  		if vec.CanonicalTypeFamily() == types.BytesFamily {
    73  			proportionalBatchMemSize += int64(vec.Bytes().ProportionalSize(length))
    74  		} else {
    75  			proportionalBatchMemSize += getVecMemoryFootprint(vec) * length / int64(vec.Capacity())
    76  		}
    77  	}
    78  	return proportionalBatchMemSize
    79  }
    80  
    81  // NewAllocator constructs a new Allocator instance.
    82  func NewAllocator(
    83  	ctx context.Context, acc *mon.BoundAccount, factory coldata.ColumnFactory,
    84  ) *Allocator {
    85  	return &Allocator{
    86  		ctx:     ctx,
    87  		acc:     acc,
    88  		factory: factory,
    89  	}
    90  }
    91  
    92  // NewMemBatch allocates a new in-memory coldata.Batch.
    93  func (a *Allocator) NewMemBatch(typs []*types.T) coldata.Batch {
    94  	return a.NewMemBatchWithSize(typs, coldata.BatchSize())
    95  }
    96  
    97  // NewMemBatchWithSize allocates a new in-memory coldata.Batch with the given
    98  // column size.
    99  func (a *Allocator) NewMemBatchWithSize(typs []*types.T, size int) coldata.Batch {
   100  	estimatedMemoryUsage := selVectorSize(size) + int64(EstimateBatchSizeBytes(typs, size))
   101  	if err := a.acc.Grow(a.ctx, estimatedMemoryUsage); err != nil {
   102  		colexecerror.InternalError(err)
   103  	}
   104  	return coldata.NewMemBatchWithSize(typs, size, a.factory)
   105  }
   106  
   107  // NewMemBatchNoCols creates a "skeleton" of new in-memory coldata.Batch. It
   108  // allocates memory for the selection vector but does *not* allocate any memory
   109  // for the column vectors - those will have to be added separately.
   110  func (a *Allocator) NewMemBatchNoCols(types []*types.T, size int) coldata.Batch {
   111  	estimatedMemoryUsage := selVectorSize(size)
   112  	if err := a.acc.Grow(a.ctx, estimatedMemoryUsage); err != nil {
   113  		colexecerror.InternalError(err)
   114  	}
   115  	return coldata.NewMemBatchNoCols(types, size)
   116  }
   117  
   118  // RetainBatch adds the size of the batch to the memory account. This shouldn't
   119  // need to be used regularly, since most memory accounting necessary is done
   120  // through PerformOperation. Use this if you want to explicitly manage the
   121  // memory accounted for.
   122  // NOTE: when calculating memory footprint, this method looks at the capacities
   123  // of the vectors and does *not* pay attention to the length of the batch.
   124  func (a *Allocator) RetainBatch(b coldata.Batch) {
   125  	if b == coldata.ZeroBatch {
   126  		// coldata.ZeroBatch takes up no space but also doesn't support the change
   127  		// of the selection vector, so we need to handle it separately.
   128  		return
   129  	}
   130  	// We need to get the capacity of the internal selection vector, even if b
   131  	// currently doesn't use it, so we set selection to true and will reset
   132  	// below.
   133  	usesSel := b.Selection() != nil
   134  	b.SetSelection(true)
   135  	if err := a.acc.Grow(a.ctx, selVectorSize(cap(b.Selection()))+getVecsMemoryFootprint(b.ColVecs())); err != nil {
   136  		colexecerror.InternalError(err)
   137  	}
   138  	b.SetSelection(usesSel)
   139  }
   140  
   141  // ReleaseBatch releases the size of the batch from the memory account. This
   142  // shouldn't need to be used regularly, since all accounts are closed by
   143  // Flow.Cleanup. Use this if you want to explicitly manage the memory used. An
   144  // example of a use case is releasing a batch before writing it to disk.
   145  // NOTE: when calculating memory footprint, this method looks at the capacities
   146  // of the vectors and does *not* pay attention to the length of the batch.
   147  func (a *Allocator) ReleaseBatch(b coldata.Batch) {
   148  	if b == coldata.ZeroBatch {
   149  		// coldata.ZeroBatch takes up no space but also doesn't support the change
   150  		// of the selection vector, so we need to handle it separately.
   151  		return
   152  	}
   153  	// We need to get the capacity of the internal selection vector, even if b
   154  	// currently doesn't use it, so we set selection to true and will reset
   155  	// below.
   156  	usesSel := b.Selection() != nil
   157  	b.SetSelection(true)
   158  	batchMemSize := selVectorSize(cap(b.Selection())) + getVecsMemoryFootprint(b.ColVecs())
   159  	a.ReleaseMemory(batchMemSize)
   160  	b.SetSelection(usesSel)
   161  }
   162  
   163  // NewMemColumn returns a new coldata.Vec, initialized with a length.
   164  func (a *Allocator) NewMemColumn(t *types.T, n int) coldata.Vec {
   165  	estimatedMemoryUsage := int64(EstimateBatchSizeBytes([]*types.T{t}, n))
   166  	if err := a.acc.Grow(a.ctx, estimatedMemoryUsage); err != nil {
   167  		colexecerror.InternalError(err)
   168  	}
   169  	return coldata.NewMemColumn(t, n, a.factory)
   170  }
   171  
   172  // MaybeAppendColumn might append a newly allocated coldata.Vec of the given
   173  // type to b at position colIdx. Behavior of the function depends on how colIdx
   174  // compares to the width of b:
   175  // 1. if colIdx < b.Width(), then we expect that correctly-typed vector is
   176  // already present in position colIdx. If that's not the case, we will panic.
   177  // 2. if colIdx == b.Width(), then we will append a newly allocated coldata.Vec
   178  // of the given type.
   179  // 3. if colIdx > b.Width(), then we will panic because such condition
   180  // indicates an error in setting up vector type enforcers during the planning
   181  // stage.
   182  // NOTE: b must be non-zero length batch.
   183  func (a *Allocator) MaybeAppendColumn(b coldata.Batch, t *types.T, colIdx int) {
   184  	if b.Length() == 0 {
   185  		colexecerror.InternalError("trying to add a column to zero length batch")
   186  	}
   187  	width := b.Width()
   188  	if colIdx < width {
   189  		presentVec := b.ColVec(colIdx)
   190  		presentType := presentVec.Type()
   191  		if presentType.Identical(t) {
   192  			// We already have the vector of the desired type in place.
   193  			if presentVec.CanonicalTypeFamily() == types.BytesFamily {
   194  				// Flat bytes vector needs to be reset before the vector can be
   195  				// reused.
   196  				presentVec.Bytes().Reset()
   197  			}
   198  			return
   199  		}
   200  		if presentType.Family() == types.UnknownFamily {
   201  			// We already have an unknown vector in place. If this is expected,
   202  			// then it will not be accessed and we're good; if this is not
   203  			// expected, then an error will occur later.
   204  			return
   205  		}
   206  		// We have a vector with an unexpected type, so we panic.
   207  		colexecerror.InternalError(errors.Errorf(
   208  			"trying to add a column of %s type at index %d but %s vector already present",
   209  			t, colIdx, presentType,
   210  		))
   211  	} else if colIdx > width {
   212  		// We have a batch of unexpected width which indicates an error in the
   213  		// planning stage.
   214  		colexecerror.InternalError(errors.Errorf(
   215  			"trying to add a column of %s type at index %d but batch has width %d",
   216  			t, colIdx, width,
   217  		))
   218  	}
   219  	estimatedMemoryUsage := int64(EstimateBatchSizeBytes([]*types.T{t}, coldata.BatchSize()))
   220  	if err := a.acc.Grow(a.ctx, estimatedMemoryUsage); err != nil {
   221  		colexecerror.InternalError(err)
   222  	}
   223  	b.AppendCol(a.NewMemColumn(t, coldata.BatchSize()))
   224  }
   225  
   226  // PerformOperation executes 'operation' (that somehow modifies 'destVecs') and
   227  // updates the memory account accordingly.
   228  // NOTE: if some columnar vectors are not modified, they should not be included
   229  // in 'destVecs' to reduce the performance hit of memory accounting.
   230  func (a *Allocator) PerformOperation(destVecs []coldata.Vec, operation func()) {
   231  	before := getVecsMemoryFootprint(destVecs)
   232  	// To simplify the accounting, we perform the operation first and then will
   233  	// update the memory account. The minor "drift" in accounting that is
   234  	// caused by this approach is ok.
   235  	operation()
   236  	after := getVecsMemoryFootprint(destVecs)
   237  
   238  	a.AdjustMemoryUsage(after - before)
   239  }
   240  
   241  // Used returns the number of bytes currently allocated through this allocator.
   242  func (a *Allocator) Used() int64 {
   243  	return a.acc.Used()
   244  }
   245  
   246  // AdjustMemoryUsage adjusts the number of bytes currently allocated through
   247  // this allocator by delta bytes (which can be both positive or negative).
   248  func (a *Allocator) AdjustMemoryUsage(delta int64) {
   249  	if delta > 0 {
   250  		if err := a.acc.Grow(a.ctx, delta); err != nil {
   251  			colexecerror.InternalError(err)
   252  		}
   253  	} else {
   254  		a.ReleaseMemory(-delta)
   255  	}
   256  }
   257  
   258  // ReleaseMemory reduces the number of bytes currently allocated through this
   259  // allocator by (at most) size bytes. size must be non-negative.
   260  func (a *Allocator) ReleaseMemory(size int64) {
   261  	if size < 0 {
   262  		colexecerror.InternalError(fmt.Sprintf("unexpectedly negative size in ReleaseMemory: %d", size))
   263  	}
   264  	if size > a.acc.Used() {
   265  		size = a.acc.Used()
   266  	}
   267  	a.acc.Shrink(a.ctx, size)
   268  }
   269  
   270  const (
   271  	// SizeOfBool is the size of a single bool value.
   272  	SizeOfBool = int(unsafe.Sizeof(true))
   273  	sizeOfInt  = int(unsafe.Sizeof(int(0)))
   274  	// SizeOfInt16 is the size of a single int16 value.
   275  	SizeOfInt16 = int(unsafe.Sizeof(int16(0)))
   276  	// SizeOfInt32 is the size of a single int32 value.
   277  	SizeOfInt32 = int(unsafe.Sizeof(int32(0)))
   278  	// SizeOfInt64 is the size of a single int64 value.
   279  	SizeOfInt64 = int(unsafe.Sizeof(int64(0)))
   280  	// SizeOfFloat64 is the size of a single float64 value.
   281  	SizeOfFloat64 = int(unsafe.Sizeof(float64(0)))
   282  	// SizeOfTime is the size of a single time.Time value.
   283  	SizeOfTime = int(unsafe.Sizeof(time.Time{}))
   284  	// SizeOfDuration is the size of a single duration.Duration value.
   285  	SizeOfDuration = int(unsafe.Sizeof(duration.Duration{}))
   286  	sizeOfDatum    = int(unsafe.Sizeof(tree.Datum(nil)))
   287  )
   288  
   289  // SizeOfBatchSizeSelVector is the size (in bytes) of a selection vector of
   290  // coldata.BatchSize() length.
   291  var SizeOfBatchSizeSelVector = coldata.BatchSize() * sizeOfInt
   292  
   293  // EstimateBatchSizeBytes returns an estimated amount of bytes needed to
   294  // store a batch in memory that has column types vecTypes.
   295  // WARNING: This only is correct for fixed width types, and returns an
   296  // estimate for non fixed width types. In future it might be possible to
   297  // remove the need for estimation by specifying batch sizes in terms of bytes.
   298  func EstimateBatchSizeBytes(vecTypes []*types.T, batchLength int) int {
   299  	// acc represents the number of bytes to represent a row in the batch.
   300  	acc := 0
   301  	for _, t := range vecTypes {
   302  		switch typeconv.TypeFamilyToCanonicalTypeFamily(t.Family()) {
   303  		case types.BoolFamily:
   304  			acc += SizeOfBool
   305  		case types.BytesFamily:
   306  			// For byte arrays, we initially allocate BytesInitialAllocationFactor
   307  			// number of bytes (plus an int32 for the offset) for each row, so we use
   308  			// the sum of two values as the estimate. However, later, the exact
   309  			// memory footprint will be used: whenever a modification of Bytes takes
   310  			// place, the Allocator will measure the old footprint and the updated
   311  			// one and will update the memory account accordingly.
   312  			acc += coldata.BytesInitialAllocationFactor + SizeOfInt32
   313  		case types.IntFamily:
   314  			switch t.Width() {
   315  			case 16:
   316  				acc += SizeOfInt16
   317  			case 32:
   318  				acc += SizeOfInt32
   319  			default:
   320  				acc += SizeOfInt64
   321  			}
   322  		case types.FloatFamily:
   323  			acc += SizeOfFloat64
   324  		case types.DecimalFamily:
   325  			// Similar to byte arrays, we can't tell how much space is used
   326  			// to hold the arbitrary precision decimal objects.
   327  			acc += 50
   328  		case types.TimestampTZFamily:
   329  			// time.Time consists of two 64 bit integers and a pointer to
   330  			// time.Location. We will only account for this 3 bytes without paying
   331  			// attention to the full time.Location struct. The reason is that it is
   332  			// likely that time.Location's are cached and are shared among all the
   333  			// timestamps, so if we were to include that in the estimation, we would
   334  			// significantly overestimate.
   335  			// TODO(yuzefovich): figure out whether the caching does take place.
   336  			acc += SizeOfTime
   337  		case types.IntervalFamily:
   338  			acc += SizeOfDuration
   339  		case typeconv.DatumVecCanonicalTypeFamily:
   340  			// In datum vec we need to account for memory underlying the struct
   341  			// that is the implementation of tree.Datum interface (for example,
   342  			// tree.DBoolFalse) as well as for the overhead of storing that
   343  			// implementation in the slice of tree.Datums.
   344  			implementationSize, _ := tree.DatumTypeSize(t)
   345  			acc += int(implementationSize) + sizeOfDatum
   346  		default:
   347  			colexecerror.InternalError(fmt.Sprintf("unhandled type %s", t))
   348  		}
   349  	}
   350  	return acc * batchLength
   351  }