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 }