github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rowcontainer/datum_row_container.go (about) 1 // Copyright 2016 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 rowcontainer 12 13 import ( 14 "context" 15 "fmt" 16 "unsafe" 17 18 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 19 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 20 "github.com/cockroachdb/cockroach/pkg/util" 21 "github.com/cockroachdb/cockroach/pkg/util/mon" 22 ) 23 24 const ( 25 // SizeOfDatum is the memory size of a Datum reference. 26 SizeOfDatum = int64(unsafe.Sizeof(tree.Datum(nil))) 27 // SizeOfDatums is the memory size of a Datum slice. 28 SizeOfDatums = int64(unsafe.Sizeof(tree.Datums(nil))) 29 ) 30 31 // RowContainer is a container for rows of Datums which tracks the 32 // approximate amount of memory allocated for row data. 33 // Rows must be added using AddRow(); once the work is done 34 // the Close() method must be called to release the allocated memory. 35 // 36 // TODO(knz): this does not currently track the amount of memory used 37 // for the outer array of Datums references. 38 type RowContainer struct { 39 // We should not copy this structure around; each copy would have a 40 // different memAcc (among other things like aliasing chunks). 41 _ util.NoCopy 42 43 numCols int 44 45 // rowsPerChunk is the number of rows in a chunk; we pack multiple rows in a 46 // single []Datum to reduce the overhead of the slice if we have few 47 // columns. Must be a power of 2 as determination of the chunk given a row 48 // index is performed using shifting. 49 rowsPerChunk int 50 rowsPerChunkShift uint 51 // preallocChunks is the number of chunks we allocate upfront (on the first 52 // AddRow call). 53 preallocChunks int 54 chunks [][]tree.Datum 55 numRows int 56 57 // chunkMemSize is the memory used by a chunk. 58 chunkMemSize int64 59 // fixedColsSize is the sum of widths of fixed-width columns in a 60 // single row. 61 fixedColsSize int64 62 // varSizedColumns indicates for which columns the datum size 63 // is variable. 64 varSizedColumns []int 65 66 // deletedRows is the number of rows that have been deleted from the front 67 // of the container. When this number reaches rowsPerChunk we delete that chunk 68 // and reset this back to zero. 69 deletedRows int 70 71 // memAcc tracks the current memory consumption of this 72 // RowContainer. 73 memAcc mon.BoundAccount 74 } 75 76 // NewRowContainer allocates a new row container. 77 // 78 // The acc argument indicates where to register memory allocations by 79 // this row container. Should probably be created by 80 // Session.makeBoundAccount() or Session.TxnState.makeBoundAccount(). 81 // 82 // The rowCapacity argument indicates how many rows are to be 83 // expected; it is used to pre-allocate the outer array of row 84 // references, in the fashion of Go's capacity argument to the make() 85 // function. 86 // 87 // Note that we could, but do not (yet), report the size of the row 88 // container itself to the monitor in this constructor. This is 89 // because the various planNodes are not (yet) equipped to call 90 // Close() upon encountering errors in their constructor (all nodes 91 // initializing a RowContainer there) and SetLimitHint() (for sortNode 92 // which initializes a RowContainer there). This would be rather 93 // error-prone to implement consistently and hellishly difficult to 94 // test properly. The trade-off is that very large table schemas or 95 // column selections could cause unchecked and potentially dangerous 96 // memory growth. 97 func NewRowContainer(acc mon.BoundAccount, ti sqlbase.ColTypeInfo, rowCapacity int) *RowContainer { 98 c := &RowContainer{} 99 c.Init(acc, ti, rowCapacity) 100 return c 101 } 102 103 // Init can be used instead of NewRowContainer if we have a RowContainer that is 104 // already part of an on-heap structure. 105 func (c *RowContainer) Init(acc mon.BoundAccount, ti sqlbase.ColTypeInfo, rowCapacity int) { 106 nCols := ti.NumColumns() 107 108 c.numCols = nCols 109 c.memAcc = acc 110 c.preallocChunks = 1 111 112 if nCols != 0 { 113 // If the rows have columns, we use 64 rows per chunk. 114 c.rowsPerChunkShift = 6 115 c.rowsPerChunk = 1 << c.rowsPerChunkShift 116 if rowCapacity > 0 { 117 c.preallocChunks = (rowCapacity + c.rowsPerChunk - 1) / c.rowsPerChunk 118 } 119 } else { 120 // If there are no columns, every row gets mapped to the first chunk. 121 c.rowsPerChunkShift = 32 122 c.rowsPerChunk = 1 << c.rowsPerChunkShift 123 c.chunks = [][]tree.Datum{{}} 124 } 125 126 for i := 0; i < nCols; i++ { 127 sz, variable := tree.DatumTypeSize(ti.Type(i)) 128 if variable { 129 if c.varSizedColumns == nil { 130 // Only allocate varSizedColumns if necessary. 131 c.varSizedColumns = make([]int, 0, nCols) 132 } 133 c.varSizedColumns = append(c.varSizedColumns, i) 134 } else { 135 c.fixedColsSize += int64(sz) 136 } 137 } 138 139 // Precalculate the memory used for a chunk, specifically by the Datums in the 140 // chunk and the slice pointing at the chunk. 141 c.chunkMemSize = SizeOfDatum * int64(c.rowsPerChunk*c.numCols) 142 c.chunkMemSize += SizeOfDatums 143 } 144 145 // Clear resets the container and releases the associated memory. This allows 146 // the RowContainer to be reused. 147 func (c *RowContainer) Clear(ctx context.Context) { 148 c.chunks = nil 149 c.numRows = 0 150 c.deletedRows = 0 151 c.memAcc.Clear(ctx) 152 } 153 154 // UnsafeReset resets the container without releasing the associated memory. This 155 // allows the RowContainer to be reused, but keeps the previously-allocated 156 // buffers around for reuse. This is desirable if this RowContainer will be used 157 // and reset many times in the course of a computation before eventually being 158 // discarded. It's unsafe because it immediately renders all previously 159 // allocated rows unsafe - they might be overwritten without notice. This is 160 // only safe to use if it's guaranteed that all previous rows retrieved by At 161 // have been copied or otherwise not retained. 162 func (c *RowContainer) UnsafeReset(ctx context.Context) error { 163 c.numRows = 0 164 c.deletedRows = 0 165 return c.memAcc.ResizeTo(ctx, int64(len(c.chunks))*c.chunkMemSize) 166 } 167 168 // Close releases the memory associated with the RowContainer. 169 func (c *RowContainer) Close(ctx context.Context) { 170 if c == nil { 171 // Allow Close on an uninitialized container. 172 return 173 } 174 c.chunks = nil 175 c.varSizedColumns = nil 176 c.memAcc.Close(ctx) 177 } 178 179 func (c *RowContainer) allocChunks(ctx context.Context, numChunks int) error { 180 datumsPerChunk := c.rowsPerChunk * c.numCols 181 182 if err := c.memAcc.Grow(ctx, c.chunkMemSize*int64(numChunks)); err != nil { 183 return err 184 } 185 186 if c.chunks == nil { 187 c.chunks = make([][]tree.Datum, 0, numChunks) 188 } 189 190 datums := make([]tree.Datum, numChunks*datumsPerChunk) 191 for i, pos := 0, 0; i < numChunks; i++ { 192 c.chunks = append(c.chunks, datums[pos:pos+datumsPerChunk]) 193 pos += datumsPerChunk 194 } 195 return nil 196 } 197 198 // rowSize computes the size of a single row. 199 func (c *RowContainer) rowSize(row tree.Datums) int64 { 200 rsz := c.fixedColsSize 201 for _, i := range c.varSizedColumns { 202 rsz += int64(row[i].Size()) 203 } 204 return rsz 205 } 206 207 // getChunkAndPos returns the chunk index and the position inside the chunk for 208 // a given row index. 209 func (c *RowContainer) getChunkAndPos(rowIdx int) (chunk int, pos int) { 210 // This is a hot path; use shifting to avoid division. 211 row := rowIdx + c.deletedRows 212 chunk = row >> c.rowsPerChunkShift 213 return chunk, (row - (chunk << c.rowsPerChunkShift)) * (c.numCols) 214 } 215 216 // AddRow attempts to insert a new row in the RowContainer. The row slice is not 217 // used directly: the Datum values inside the Datums are copied to internal storage. 218 // Returns an error if the allocation was denied by the MemoryMonitor. 219 func (c *RowContainer) AddRow(ctx context.Context, row tree.Datums) (tree.Datums, error) { 220 if len(row) != c.numCols { 221 panic(fmt.Sprintf("invalid row length %d, expected %d", len(row), c.numCols)) 222 } 223 if c.numCols == 0 { 224 c.numRows++ 225 return nil, nil 226 } 227 if err := c.memAcc.Grow(ctx, c.rowSize(row)); err != nil { 228 return nil, err 229 } 230 chunk, pos := c.getChunkAndPos(c.numRows) 231 if chunk == len(c.chunks) { 232 numChunks := c.preallocChunks 233 if len(c.chunks) > 0 { 234 // Grow the number of chunks by a fraction. 235 numChunks = 1 + len(c.chunks)/8 236 } 237 if err := c.allocChunks(ctx, numChunks); err != nil { 238 return nil, err 239 } 240 } 241 copy(c.chunks[chunk][pos:pos+c.numCols], row) 242 c.numRows++ 243 return c.chunks[chunk][pos : pos+c.numCols : pos+c.numCols], nil 244 } 245 246 // Len reports the number of rows currently held in this RowContainer. 247 func (c *RowContainer) Len() int { 248 return c.numRows 249 } 250 251 // NumCols reports the number of columns for each row in the container. 252 func (c *RowContainer) NumCols() int { 253 return c.numCols 254 } 255 256 // At accesses a row at a specific index. 257 func (c *RowContainer) At(i int) tree.Datums { 258 // This is a hot-path: do not add additional checks here. 259 chunk, pos := c.getChunkAndPos(i) 260 return c.chunks[chunk][pos : pos+c.numCols : pos+c.numCols] 261 } 262 263 // Swap exchanges two rows. Used for sorting. 264 func (c *RowContainer) Swap(i, j int) { 265 r1 := c.At(i) 266 r2 := c.At(j) 267 for idx := 0; idx < c.numCols; idx++ { 268 r1[idx], r2[idx] = r2[idx], r1[idx] 269 } 270 } 271 272 // PopFirst discards the first row in the RowContainer. 273 func (c *RowContainer) PopFirst() { 274 if c.numRows == 0 { 275 panic("no rows added to container, nothing to pop") 276 } 277 c.numRows-- 278 if c.numCols != 0 { 279 c.deletedRows++ 280 if c.deletedRows == c.rowsPerChunk { 281 // We release the memory for rows in chunks. This includes the 282 // chunk slice (allocated by allocChunks) and the Datums. 283 size := c.chunkMemSize 284 for i, pos := 0, 0; i < c.rowsPerChunk; i, pos = i+1, pos+c.numCols { 285 size += c.rowSize(c.chunks[0][pos : pos+c.numCols]) 286 } 287 // Reset the pointer so the slice can be garbage collected. 288 c.chunks[0] = nil 289 c.deletedRows = 0 290 c.chunks = c.chunks[1:] 291 292 // We don't have a context plumbed here, but that's ok: it's not actually 293 // used in the shrink paths. 294 c.memAcc.Shrink(context.TODO(), size) 295 } 296 } 297 } 298 299 // Replace substitutes one row for another. This does query the 300 // MemoryMonitor to determine whether the new row fits the 301 // allowance. 302 func (c *RowContainer) Replace(ctx context.Context, i int, newRow tree.Datums) error { 303 newSz := c.rowSize(newRow) 304 row := c.At(i) 305 oldSz := c.rowSize(row) 306 if newSz != oldSz { 307 if err := c.memAcc.Resize(ctx, oldSz, newSz); err != nil { 308 return err 309 } 310 } 311 copy(row, newRow) 312 return nil 313 } 314 315 // MemUsage returns the current accounted memory usage. 316 func (c *RowContainer) MemUsage() int64 { 317 return c.memAcc.Used() 318 }