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  }