github.com/matrixorigin/matrixone@v0.7.0/pkg/common/mpool/mpool.go (about)

     1  // Copyright 2021 - 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package mpool
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"sync"
    21  	"sync/atomic"
    22  	"unsafe"
    23  
    24  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    25  	"github.com/matrixorigin/matrixone/pkg/logutil"
    26  	"github.com/matrixorigin/matrixone/pkg/util/stack"
    27  )
    28  
    29  // Mo's extremely simple memory pool.
    30  
    31  // Stats
    32  type MPoolStats struct {
    33  	NumAlloc      atomic.Int64 // number of allocations
    34  	NumFree       atomic.Int64 // number of frees
    35  	NumGoAlloc    atomic.Int64 // number of go runtime alloc
    36  	NumAllocBytes atomic.Int64 // number of bytes allocated
    37  	NumFreeBytes  atomic.Int64 // number of bytes freed
    38  	NumCurrBytes  atomic.Int64 // current number of bytes
    39  	HighWaterMark atomic.Int64 // high water mark
    40  }
    41  
    42  func (s *MPoolStats) Report(tab string) string {
    43  	if s.HighWaterMark.Load() == 0 {
    44  		// empty, reduce noise.
    45  		return ""
    46  	}
    47  
    48  	ret := ""
    49  	ret += fmt.Sprintf("%s allocations : %d\n", tab, s.NumAlloc.Load())
    50  	ret += fmt.Sprintf("%s frees : %d\n", tab, s.NumFree.Load())
    51  	ret += fmt.Sprintf("%s go runtime allocations : %d\n", tab, s.NumGoAlloc.Load())
    52  	ret += fmt.Sprintf("%s alloc bytes : %d\n", tab, s.NumAllocBytes.Load())
    53  	ret += fmt.Sprintf("%s free bytes : %d\n", tab, s.NumFreeBytes.Load())
    54  	ret += fmt.Sprintf("%s current bytes : %d\n", tab, s.NumCurrBytes.Load())
    55  	ret += fmt.Sprintf("%s high water mark : %d\n", tab, s.HighWaterMark.Load())
    56  	return ret
    57  }
    58  
    59  func (s *MPoolStats) ReportJson() string {
    60  	if s.HighWaterMark.Load() == 0 {
    61  		return ""
    62  	}
    63  	ret := "{"
    64  	ret += fmt.Sprintf("\"alloc\": %d,", s.NumAlloc.Load())
    65  	ret += fmt.Sprintf("\"free\": %d,", s.NumFree.Load())
    66  	ret += fmt.Sprintf("\"goalloc\": %d,", s.NumGoAlloc.Load())
    67  	ret += fmt.Sprintf("\"allocBytes\": %d,", s.NumAllocBytes.Load())
    68  	ret += fmt.Sprintf("\"freeBytes\": %d,", s.NumFreeBytes.Load())
    69  	ret += fmt.Sprintf("\"currBytes\": %d,", s.NumCurrBytes.Load())
    70  	ret += fmt.Sprintf("\"highWaterMark\": %d", s.HighWaterMark.Load())
    71  	ret += "}"
    72  	return ret
    73  }
    74  
    75  // Update alloc stats, return curr bytes
    76  func (s *MPoolStats) RecordAlloc(tag string, sz int64) int64 {
    77  	s.NumAlloc.Add(1)
    78  	s.NumAllocBytes.Add(sz)
    79  	curr := s.NumCurrBytes.Add(sz)
    80  	hwm := s.HighWaterMark.Load()
    81  	if curr > hwm {
    82  		swapped := s.HighWaterMark.CompareAndSwap(hwm, curr)
    83  		if swapped && curr/GB != hwm/GB {
    84  			logutil.Infof("MPool %s new high watermark\n%s", tag, s.Report("    "))
    85  		}
    86  	}
    87  	return curr
    88  }
    89  
    90  // Update free stats, return curr bytes.
    91  func (s *MPoolStats) RecordFree(tag string, sz int64) int64 {
    92  	s.NumFree.Add(1)
    93  	s.NumFreeBytes.Add(sz)
    94  	curr := s.NumCurrBytes.Add(-sz)
    95  	if curr < 0 {
    96  		logutil.Errorf("Mpool %s free bug, stats: %s", tag, s.Report("    "))
    97  		panic(moerr.NewInternalErrorNoCtx("mpool freed more bytes than alloc"))
    98  	}
    99  	return curr
   100  }
   101  
   102  func (s *MPoolStats) RecordManyFrees(tag string, nfree, sz int64) int64 {
   103  	s.NumFree.Add(nfree)
   104  	s.NumFreeBytes.Add(sz)
   105  	curr := s.NumCurrBytes.Add(-sz)
   106  	if curr < 0 {
   107  		logutil.Errorf("Mpool %s free many bug, stats: %s", tag, s.Report("    "))
   108  		panic(moerr.NewInternalErrorNoCtx("mpool freemany freed more bytes than alloc"))
   109  	}
   110  	return curr
   111  }
   112  
   113  const (
   114  	NumFixedPool = 7
   115  	kMemHdrSz    = 16
   116  	B            = 1
   117  	KB           = 1024
   118  	MB           = 1024 * KB
   119  	GB           = 1024 * MB
   120  	TB           = 1024 * GB
   121  	PB           = 1024 * TB
   122  )
   123  
   124  // Fixed pool configurations.  Number of entries in fixed pool.
   125  //
   126  // Small: esp, 0 is a valid value.
   127  var NoFixed = []int{0, 0, 0, 0, 0, 0, 0}
   128  var Small = []int{1024, 1024, 1024, 1024, 512, 0, 0}
   129  var Mid = []int{4096, 4096, 1024, 1024, 1024, 512, 512}
   130  var Large = []int{32 * 1024, 16 * 1024, 8 * 1024, 4 * 1024, 1024, 1024, 1024}
   131  
   132  // Pool emement size
   133  var PoolElemSize = []int{64, 128, 256, 512, 1024, 2048, 4096}
   134  
   135  // Zeros, enough for largest pool element
   136  var ZeroSlice = make([]byte, 4096)
   137  
   138  // Memory header, kMemHdrSz bytes.
   139  type memHdr struct {
   140  	poolId       int64
   141  	allocSz      int32
   142  	fixedPoolIdx int8
   143  	guard        [3]uint8
   144  }
   145  
   146  func (pHdr *memHdr) SetGuard() {
   147  	pHdr.guard[0] = 0xDE
   148  	pHdr.guard[1] = 0xAD
   149  	pHdr.guard[2] = 0xBF
   150  }
   151  
   152  func (pHdr *memHdr) CheckGuard() bool {
   153  	return pHdr.guard[0] == 0xDE && pHdr.guard[1] == 0xAD && pHdr.guard[2] == 0xBF
   154  }
   155  
   156  // pool for fixed elements.  Note that we preconfigure the pool size.
   157  // We should consider implement some kind of growing logic.
   158  type fixedPool struct {
   159  	eleSz  int
   160  	eleCnt int
   161  	stats  MPoolStats
   162  	buf    []uint64
   163  	flist  *freelist
   164  }
   165  
   166  // Initaialze a fixed pool
   167  func (fp *fixedPool) initPool(tag string, poolid int64, idx int, eleCnt int, cap int64) (int64, error) {
   168  	eleSz := PoolElemSize[idx]
   169  	fp.eleSz = eleSz
   170  	fp.eleCnt = eleCnt
   171  
   172  	nb := (kMemHdrSz + eleSz) * eleCnt
   173  	if nb == 0 {
   174  		return 0, nil
   175  	}
   176  
   177  	if int64(nb) >= cap {
   178  		return 0, moerr.NewInternalErrorNoCtx("initPool failed, not enough space %d < %d", nb, cap)
   179  	}
   180  
   181  	// The poll, is considered allocated, so do accouting with global stats.
   182  	curr := globalStats.RecordAlloc(tag, int64(nb))
   183  	if curr > GlobalCap() {
   184  		// OOM, return nb back to globalStats
   185  		globalStats.RecordFree(tag, int64(nb))
   186  		return 0, moerr.NewOOMNoCtx()
   187  	}
   188  
   189  	fp.flist = make_freelist(int32(eleCnt))
   190  
   191  	// Really allocate buffer, and put hdr of each slot into pool.
   192  	// nb is always 8x,
   193  	fp.buf = make([]uint64, nb/8)
   194  	ptr := unsafe.Pointer(&fp.buf[0])
   195  
   196  	for i := 0; i < eleCnt; i++ {
   197  		offset := (kMemHdrSz + eleSz) * i
   198  		hdr := unsafe.Add(ptr, offset)
   199  		pHdr := (*memHdr)(hdr)
   200  		pHdr.poolId = poolid
   201  		pHdr.fixedPoolIdx = int8(idx)
   202  		pHdr.allocSz = -1
   203  		pHdr.SetGuard()
   204  
   205  		fp.flist.put(hdr)
   206  	}
   207  	return int64(nb), nil
   208  }
   209  
   210  /*
   211  func (fp *fixedPool) destroy() {
   212  	// not necessary, but doing it anyway
   213  	// original here to maintain stats.  All relavent stats are maintained
   214  	// in MPool itself.
   215  	fp.flist.destroy()
   216  	fp.buf = nil
   217  }
   218  */
   219  
   220  type detailInfo struct {
   221  	cnt, bytes int64
   222  }
   223  
   224  type mpoolDetails struct {
   225  	mu    sync.Mutex
   226  	alloc map[string]detailInfo
   227  	free  map[string]detailInfo
   228  }
   229  
   230  func newMpoolDetails() *mpoolDetails {
   231  	mpd := mpoolDetails{}
   232  	mpd.alloc = make(map[string]detailInfo)
   233  	mpd.free = make(map[string]detailInfo)
   234  	return &mpd
   235  }
   236  
   237  func (d *mpoolDetails) recordAlloc(nb int64) {
   238  	f := stack.Caller(2)
   239  	k := fmt.Sprintf("%v", f)
   240  	d.mu.Lock()
   241  	defer d.mu.Unlock()
   242  
   243  	info := d.alloc[k]
   244  	info.cnt += 1
   245  	info.bytes += nb
   246  	d.alloc[k] = info
   247  }
   248  
   249  func (d *mpoolDetails) recordFree(nb int64) {
   250  	f := stack.Caller(2)
   251  	k := fmt.Sprintf("%v", f)
   252  	d.mu.Lock()
   253  	defer d.mu.Unlock()
   254  
   255  	info := d.free[k]
   256  	info.cnt += 1
   257  	info.bytes += nb
   258  	d.free[k] = info
   259  }
   260  
   261  func (d *mpoolDetails) reportJson() string {
   262  	d.mu.Lock()
   263  	defer d.mu.Unlock()
   264  	ret := `{"alloc": {`
   265  	allocs := make([]string, 0)
   266  	for k, v := range d.alloc {
   267  		kvs := fmt.Sprintf("\"%s\": [%d, %d]", k, v.cnt, v.bytes)
   268  		allocs = append(allocs, kvs)
   269  	}
   270  	ret += strings.Join(allocs, ",")
   271  	ret += `}, "free": {`
   272  	frees := make([]string, 0)
   273  	for k, v := range d.free {
   274  		kvs := fmt.Sprintf("\"%s\": [%d, %d]", k, v.cnt, v.bytes)
   275  		frees = append(frees, kvs)
   276  	}
   277  	ret += strings.Join(frees, ",")
   278  	ret += "}}"
   279  	return ret
   280  }
   281  
   282  // The memory pool.
   283  type MPool struct {
   284  	id      int64      // mpool generated, used to look up the MPool
   285  	tag     string     // user supplied, for debug/inspect
   286  	cap     int64      // pool capacity
   287  	stats   MPoolStats // stats
   288  	pools   [7]fixedPool
   289  	sels    *sync.Pool // weirdness, keep old API but this should go away.
   290  	details *mpoolDetails
   291  }
   292  
   293  func (mp *MPool) EnableDetailRecording() {
   294  	if mp.details == nil {
   295  		mp.details = newMpoolDetails()
   296  	}
   297  }
   298  
   299  func (mp *MPool) DisableDetailRecording() {
   300  	mp.details = nil
   301  }
   302  
   303  func (mp *MPool) Stats() *MPoolStats {
   304  	return &mp.stats
   305  }
   306  
   307  func (mp *MPool) Cap() int64 {
   308  	if mp.cap == 0 {
   309  		return PB
   310  	}
   311  	return mp.cap
   312  }
   313  
   314  func (mp *MPool) FixedPoolStats(i int) *MPoolStats {
   315  	if i < 0 || i > NumFixedPool {
   316  		panic(moerr.NewInternalErrorNoCtx("accessing stats of %d-th fixed pool", i))
   317  	}
   318  	return &mp.pools[i].stats
   319  }
   320  
   321  func (mp *MPool) initPool(sz []int) error {
   322  	var tot int64
   323  	cap := mp.Cap()
   324  	for i, cnt := range sz {
   325  		nb, err := mp.pools[i].initPool(mp.tag, mp.id, i, cnt, cap-tot)
   326  		if err != nil {
   327  			return err
   328  		} else if nb > 0 {
   329  			mp.stats.RecordAlloc(mp.tag, nb)
   330  		}
   331  		tot += nb
   332  	}
   333  	return nil
   334  }
   335  
   336  func (mp *MPool) destroy() {
   337  	if mp.stats.NumAlloc.Load() < mp.stats.NumFree.Load() {
   338  		logutil.Errorf("mp error: %s", mp.stats.Report(""))
   339  	}
   340  
   341  	// We do not call each individual fixedPool's destroy
   342  	// because they recorded pooled elements alloc/frees.
   343  	// Those are not reflected in globalStats.
   344  	// Here we just compensate whatever left over in mp.stats
   345  	// into globalStats.
   346  	globalStats.RecordManyFrees(mp.tag,
   347  		mp.stats.NumAlloc.Load()-mp.stats.NumFree.Load(),
   348  		mp.stats.NumCurrBytes.Load())
   349  }
   350  
   351  // For test.
   352  func MustNewZero() *MPool {
   353  	return MustNewZeroWithTag("zero_fixed_mp_for_test")
   354  }
   355  
   356  func MustNewZeroWithTag(tag string) *MPool {
   357  	mp, err := NewMPool(tag, 0, NoFixed)
   358  	if err != nil {
   359  		panic(err)
   360  	}
   361  	return mp
   362  }
   363  
   364  // New a MPool.   Tag is user supplied, used for debugging/diagnostics.
   365  func NewMPool(tag string, cap int64, sz []int) (*MPool, error) {
   366  	if len(sz) != NumFixedPool {
   367  		return nil, moerr.NewInternalErrorNoCtx("invalid mpool size config")
   368  	}
   369  
   370  	if cap > 0 {
   371  		// simple sanity check
   372  		if cap < 1024*1024 {
   373  			return nil, moerr.NewInternalErrorNoCtx("mpool cap %d too small", cap)
   374  		}
   375  		if cap > GlobalCap() {
   376  			return nil, moerr.NewInternalErrorNoCtx("mpool cap %d too big, global cap %d", cap, globalCap)
   377  		}
   378  	}
   379  
   380  	id := atomic.AddInt64(&nextPool, 1)
   381  	var mp MPool
   382  	mp.id = id
   383  	mp.tag = tag
   384  	mp.cap = cap
   385  	err := mp.initPool(sz)
   386  	if err != nil {
   387  		mp.destroy()
   388  		return nil, err
   389  	}
   390  	mp.sels = &sync.Pool{
   391  		New: func() any {
   392  			ss := make([]int64, 0, 16)
   393  			return &ss
   394  		},
   395  	}
   396  
   397  	globalPools.Store(id, &mp)
   398  	// logutil.Infof("creating mpool %s, cap %d, fixed size %v", tag, cap, sz)
   399  	return &mp, nil
   400  }
   401  
   402  func (mp *MPool) Report() string {
   403  	ret := fmt.Sprintf("    mpool stats: %s", mp.Stats().Report("        "))
   404  	for i := range mp.pools {
   405  		ret += fmt.Sprintf("        fixed pool %d stats: %s", i, mp.FixedPoolStats(i).Report("            "))
   406  	}
   407  	return ret
   408  }
   409  
   410  func (mp *MPool) ReportJson() string {
   411  	ss := mp.stats.ReportJson()
   412  	if ss == "" {
   413  		return fmt.Sprintf("{\"%s\": \"\"}", mp.tag)
   414  	}
   415  	ret := fmt.Sprintf("{\"%s\": %s", mp.tag, ss)
   416  	for i := range mp.pools {
   417  		ps := mp.FixedPoolStats(i).ReportJson()
   418  		if ps != "" {
   419  			ret += fmt.Sprintf(",\n \"Fixed-%d\": %s", i, ps)
   420  		}
   421  	}
   422  
   423  	if mp.details != nil {
   424  		ret += `,\n "detailed_alloc": `
   425  		ret += mp.details.reportJson()
   426  	}
   427  
   428  	return ret + "}"
   429  }
   430  
   431  func (mp *MPool) CurrNB() int64 {
   432  	return mp.stats.NumCurrBytes.Load()
   433  }
   434  
   435  func DeleteMPool(mp *MPool) {
   436  	if mp == nil {
   437  		return
   438  	}
   439  
   440  	// logutil.Infof("destroy mpool %s, cap %d, stats\n%s", mp.tag, mp.cap, mp.Report())
   441  	globalPools.Delete(mp.id)
   442  	mp.destroy()
   443  }
   444  
   445  var nextPool int64
   446  var globalCap int64
   447  var globalStats MPoolStats
   448  var globalPools sync.Map
   449  
   450  func InitCap(cap int64) {
   451  	if cap < GB {
   452  		globalCap = GB
   453  	} else {
   454  		globalCap = cap
   455  	}
   456  }
   457  
   458  func GlobalStats() *MPoolStats {
   459  	return &globalStats
   460  }
   461  func GlobalCap() int64 {
   462  	if globalCap == 0 {
   463  		return PB
   464  	}
   465  	return globalCap
   466  }
   467  
   468  func sizeToIdx(size int) int {
   469  	for i, sz := range PoolElemSize {
   470  		if int(size) <= sz {
   471  			return i
   472  		}
   473  	}
   474  	return NumFixedPool
   475  }
   476  
   477  func (fp *fixedPool) alloc(sz int) []byte {
   478  	if fp.eleCnt == 0 {
   479  		return nil
   480  	}
   481  	// We have already done the accounting when we init the fixed pool
   482  	// so we don't do any global, or mpool level accounting.
   483  	hdr := fp.flist.get()
   484  	if hdr != nil {
   485  		// alloc from fixed pool, record alloc bytes
   486  		fp.stats.RecordAlloc("", int64(sz))
   487  	} else {
   488  		// failure to alloc, record this stats
   489  		fp.stats.NumGoAlloc.Add(1)
   490  		return nil
   491  	}
   492  
   493  	pHdr := (*memHdr)(hdr)
   494  	pHdr.allocSz = int32(sz)
   495  	bPtr := (*byte)(unsafe.Add(hdr, kMemHdrSz))
   496  
   497  	// Keep cap as best as we can
   498  	bs := unsafe.Slice(bPtr, fp.eleSz)
   499  	// zero the content
   500  	copy(bs, ZeroSlice)
   501  	return bs[:sz]
   502  }
   503  
   504  func (mp *MPool) Alloc(sz int) ([]byte, error) {
   505  	if sz < 0 || sz > GB {
   506  		return nil, moerr.NewInternalErrorNoCtx("Invalid alloc size %d", sz)
   507  	}
   508  
   509  	if sz == 0 {
   510  		// Alloc size of 0, return nil instead of a []byte{}.  Otherwise,
   511  		// later when we try to free, we will not be able to get a[0]
   512  		return nil, nil
   513  	}
   514  
   515  	idx := sizeToIdx(sz)
   516  	if idx < NumFixedPool {
   517  		bs := mp.pools[idx].alloc(sz)
   518  		if bs != nil {
   519  			return bs, nil
   520  		}
   521  	}
   522  
   523  	// fallback to go alloc, first, check we are under cap
   524  	gcurr := globalStats.RecordAlloc("global", int64(sz))
   525  	if gcurr > GlobalCap() {
   526  		globalStats.RecordFree("global", int64(sz))
   527  		return nil, moerr.NewOOMNoCtx()
   528  	}
   529  
   530  	// check if it is under my cap
   531  	mycurr := mp.stats.RecordAlloc(mp.tag, int64(sz))
   532  	if mycurr > mp.Cap() {
   533  		mp.stats.RecordFree(mp.tag, int64(sz))
   534  		return nil, moerr.NewInternalErrorNoCtx("mpool out of space, alloc %d bytes, cap %d", sz, mp.cap)
   535  	}
   536  
   537  	if mp.details != nil {
   538  		mp.details.recordAlloc(int64(sz))
   539  	}
   540  
   541  	// allocate!
   542  	bs := make([]uint64, (sz+kMemHdrSz+7)/8)
   543  	hdr := unsafe.Pointer(&bs[0])
   544  	pHdr := (*memHdr)(hdr)
   545  	pHdr.poolId = mp.id
   546  	pHdr.fixedPoolIdx = NumFixedPool
   547  	pHdr.allocSz = int32(sz)
   548  	pHdr.SetGuard()
   549  
   550  	return unsafe.Slice((*byte)(unsafe.Add(hdr, kMemHdrSz)), sz), nil
   551  }
   552  
   553  func (fp *fixedPool) free(hdr unsafe.Pointer) {
   554  	pHdr := (*memHdr)(hdr)
   555  	fp.stats.RecordFree("", int64(pHdr.allocSz))
   556  
   557  	if pHdr.allocSz == -1 {
   558  		// double free.
   559  		panic(moerr.NewInternalErrorNoCtx("free size -1, possible double free"))
   560  	}
   561  	pHdr.allocSz = -1
   562  	fp.flist.put(hdr)
   563  }
   564  
   565  func (mp *MPool) Free(bs []byte) {
   566  	if bs == nil || cap(bs) == 0 {
   567  		// free nil is OK.
   568  		return
   569  	}
   570  
   571  	bs = bs[:1]
   572  	pb := (unsafe.Pointer)(&bs[0])
   573  	offset := -kMemHdrSz
   574  	hdr := unsafe.Add(pb, offset)
   575  	pHdr := (*memHdr)(hdr)
   576  
   577  	if !pHdr.CheckGuard() {
   578  		panic(moerr.NewInternalErrorNoCtx("mp header corruption"))
   579  	}
   580  
   581  	if pHdr.poolId == mp.id {
   582  		if pHdr.fixedPoolIdx < NumFixedPool {
   583  			mp.pools[pHdr.fixedPoolIdx].free(hdr)
   584  		} else {
   585  			if pHdr.allocSz == -1 {
   586  				// double free.
   587  				panic(moerr.NewInternalErrorNoCtx("free size -1, possible double free"))
   588  			}
   589  			if mp.details != nil {
   590  				mp.details.recordFree(int64(pHdr.allocSz))
   591  			}
   592  			mp.stats.RecordFree(mp.tag, int64(pHdr.allocSz))
   593  			globalStats.RecordFree(mp.tag, int64(pHdr.allocSz))
   594  			pHdr.allocSz = -1
   595  		}
   596  	} else {
   597  		// cross pool free.
   598  		otherPool, ok := globalPools.Load(pHdr.poolId)
   599  		if !ok {
   600  			panic(moerr.NewInternalErrorNoCtx("invalid mpool id %d", pHdr.poolId))
   601  		}
   602  		(otherPool.(*MPool)).Free(bs)
   603  	}
   604  }
   605  
   606  func (mp *MPool) Realloc(old []byte, sz int) ([]byte, error) {
   607  	if sz <= cap(old) {
   608  		return old[:sz], nil
   609  	}
   610  	ret, err := mp.Alloc(sz)
   611  	if err != nil {
   612  		return ret, err
   613  	}
   614  	copy(ret, old)
   615  	mp.Free(old)
   616  	return ret, nil
   617  }
   618  
   619  // alignUp rounds n up to a multiple of a. a must be a power of 2.
   620  func alignUp(n, a int) int {
   621  	return (n + a - 1) &^ (a - 1)
   622  }
   623  
   624  // divRoundUp returns ceil(n / a).
   625  func divRoundUp(n, a int) int {
   626  	// a is generally a power of two. This will get inlined and
   627  	// the compiler will optimize the division.
   628  	return (n + a - 1) / a
   629  }
   630  
   631  // Returns size of the memory block that mallocgc will allocate if you ask for the size.
   632  func roundupsize(size int) int {
   633  	if size < _MaxSmallSize {
   634  		if size <= smallSizeMax-8 {
   635  			return int(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
   636  		} else {
   637  			return int(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
   638  		}
   639  	}
   640  	if size+_PageSize < size {
   641  		return size
   642  	}
   643  	return alignUp(size, _PageSize)
   644  }
   645  
   646  // Grow is like Realloc but we try to be a little bit more aggressive on growing
   647  // the slice.
   648  func (mp *MPool) Grow(old []byte, sz int) ([]byte, error) {
   649  	if sz < len(old) {
   650  		return nil, moerr.NewInternalErrorNoCtx("mpool grow actually shrinks, %d, %d", len(old), sz)
   651  	}
   652  	if sz <= cap(old) {
   653  		return old[:sz], nil
   654  	}
   655  
   656  	// copy-paste go slice's grow strategy
   657  	newcap := cap(old)
   658  	doublecap := newcap + newcap
   659  	if sz > doublecap {
   660  		newcap = sz
   661  	} else {
   662  		const threshold = 256
   663  		if newcap < threshold {
   664  			newcap = doublecap
   665  		} else {
   666  			for 0 < newcap && newcap < sz {
   667  				newcap += (newcap + 3*threshold) / 4
   668  			}
   669  			if newcap <= 0 {
   670  				newcap = sz
   671  			}
   672  		}
   673  	}
   674  	newcap = roundupsize(newcap)
   675  
   676  	ret, err := mp.Realloc(old, newcap)
   677  	if err != nil {
   678  		return ret, err
   679  	}
   680  	return ret[:sz], nil
   681  }
   682  
   683  func (mp *MPool) Grow2(old []byte, old2 []byte, sz int) ([]byte, error) {
   684  	len1 := len(old)
   685  	len2 := len(old2)
   686  	if sz < len1+len2 {
   687  		return nil, moerr.NewInternalErrorNoCtx("mpool grow2 actually shrinks, %d+%d, %d", len1, len2, sz)
   688  	}
   689  	ret, err := mp.Grow(old, sz)
   690  	if err != nil {
   691  		return nil, err
   692  	}
   693  	copy(ret[len1:len1+len2], old2)
   694  	return ret, nil
   695  }
   696  
   697  func (mp *MPool) PutSels(sels []int64) {
   698  	mp.sels.Put(&sels)
   699  }
   700  
   701  func (mp *MPool) GetSels() []int64 {
   702  	ss := mp.sels.Get().(*[]int64)
   703  	return (*ss)[:0]
   704  }
   705  
   706  func (mp *MPool) Increase(nb int64) error {
   707  	gcurr := globalStats.RecordAlloc("global", nb)
   708  	if gcurr > GlobalCap() {
   709  		globalStats.RecordFree(mp.tag, nb)
   710  		return moerr.NewOOMNoCtx()
   711  	}
   712  
   713  	// check if it is under my cap
   714  	mycurr := mp.stats.RecordAlloc(mp.tag, nb)
   715  	if mycurr > mp.Cap() {
   716  		mp.stats.RecordFree(mp.tag, nb)
   717  		return moerr.NewInternalErrorNoCtx("mpool out of space, alloc %d bytes, cap %d", nb, mp.cap)
   718  	}
   719  	return nil
   720  }
   721  
   722  func (mp *MPool) Decrease(nb int64) {
   723  	mp.stats.RecordFree(mp.tag, nb)
   724  	globalStats.RecordFree("global", nb)
   725  }
   726  
   727  func MakeSliceWithCap[T any](n, cap int, mp *MPool) ([]T, error) {
   728  	var t T
   729  	tsz := unsafe.Sizeof(t)
   730  	bs, err := mp.Alloc(int(tsz) * cap)
   731  	if err != nil {
   732  		return nil, err
   733  	}
   734  	ptr := unsafe.Pointer(&bs[0])
   735  	tptr := (*T)(ptr)
   736  	ret := unsafe.Slice(tptr, cap)
   737  	return ret[:n:cap], nil
   738  }
   739  
   740  func MakeSlice[T any](n int, mp *MPool) ([]T, error) {
   741  	return MakeSliceWithCap[T](n, n, mp)
   742  }
   743  
   744  func MakeSliceArgs[T any](mp *MPool, args ...T) ([]T, error) {
   745  	ret, err := MakeSlice[T](len(args), mp)
   746  	if err != nil {
   747  		return ret, err
   748  	}
   749  	copy(ret, args)
   750  	return ret, nil
   751  }
   752  
   753  // Report memory usage in json.
   754  func ReportMemUsage(tag string) string {
   755  	gstat := fmt.Sprintf("{\"global\":%s}", globalStats.ReportJson())
   756  	if tag == "global" {
   757  		return "[" + gstat + "]"
   758  	}
   759  
   760  	var poolStats []string
   761  	if tag == "" {
   762  		poolStats = append(poolStats, gstat)
   763  	}
   764  
   765  	gather := func(key, value any) bool {
   766  		mp := value.(*MPool)
   767  		if tag == "" || tag == mp.tag {
   768  			poolStats = append(poolStats, mp.ReportJson())
   769  		}
   770  		return true
   771  	}
   772  	globalPools.Range(gather)
   773  
   774  	return "[" + strings.Join(poolStats, ",") + "]"
   775  }
   776  
   777  func MPoolControl(tag string, cmd string) string {
   778  	if tag == "" || tag == "global" {
   779  		return "Cannot enable detail on mpool global stats"
   780  	}
   781  
   782  	cmdFunc := func(key, value any) bool {
   783  		mp := value.(*MPool)
   784  		if tag == mp.tag {
   785  			switch cmd {
   786  			case "enable_detail":
   787  				mp.EnableDetailRecording()
   788  			case "disable_detail":
   789  				mp.DisableDetailRecording()
   790  			}
   791  		}
   792  		return true
   793  	}
   794  
   795  	globalPools.Range(cmdFunc)
   796  	return "ok"
   797  }