github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/cache/size_utils.go (about) 1 // Copyright 2017 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package cache 6 7 import ( 8 "math" 9 "reflect" 10 ) 11 12 /* 13 14 Following struct/const definitions are from src/runtime/hashmap.go. We don't 15 use them directly, but the estimation of map size is based on them. 16 17 const ( 18 // Maximum number of key/value pairs a bucket can hold. 19 bucketCntBits = 3 20 bucketCnt = 1 << bucketCntBits 21 22 // Maximum average load of a bucket that triggers growth. 23 loadFactor = 6.5 24 ... 25 ) 26 27 // A header for a Go map. 28 type hmap struct { 29 // Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and 30 // ../reflect/type.go. Don't change this structure without also changing that code! 31 count int // # live cells == size of map. Must be first (used by len() builtin) 32 flags uint8 33 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) 34 noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details 35 hash0 uint32 // hash seed 36 37 buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0. 38 oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing 39 nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) 40 41 // If both key and value do not contain pointers and are inline, then we mark bucket 42 // type as containing no pointers. This avoids scanning such maps. 43 // However, bmap.overflow is a pointer. In order to keep overflow buckets 44 // alive, we store pointers to all overflow buckets in hmap.overflow. 45 // Overflow is used only if key and value do not contain pointers. 46 // overflow[0] contains overflow buckets for hmap.buckets. 47 // overflow[1] contains overflow buckets for hmap.oldbuckets. 48 // The first indirection allows us to reduce static size of hmap. 49 // The second indirection allows to store a pointer to the slice in hiter. 50 overflow *[2]*[]*bmap 51 } 52 53 // A bucket for a Go map. 54 type bmap struct { 55 // tophash generally contains the top byte of the hash value 56 // for each key in this bucket. If tophash[0] < minTopHash, 57 // tophash[0] is a bucket evacuation state instead. 58 tophash [bucketCnt]uint8 59 // Followed by bucketCnt keys and then bucketCnt values. 60 // NOTE: packing all the keys together and then all the values together makes the 61 // code a bit more complicated than alternating key/value/key/value/... but it allows 62 // us to eliminate padding which would be needed for, e.g., map[int64]int8. 63 // Followed by an overflow pointer. 64 } 65 */ 66 67 const ( 68 // MB is a short cut for 1024 * 1024. 69 MB = 1024 * 1024 70 71 // PtrSize is the number of bytes a pointer takes. 72 PtrSize = 4 << (^uintptr(0) >> 63) // stolen from runtime/internal/sys 73 // IntSize is the number of bytes an int or uint takes. 74 IntSize = 4 << (^uint(0) >> 63) 75 76 hmapStructSize = IntSize + // count int 77 1 + 1 + 2 + 4 + // flags, B, noverflow, hash0 78 PtrSize*3 + // buckets, oldbuckets, nevacuate 79 PtrSize + 2*PtrSize // overflow (estimate; not counting the slice) 80 81 bucketSizeWithoutIndirectPointerOverhead = 1 << 3 // tophash only 82 83 mapLoadFactor = 6.5 84 ) 85 86 func mapKeyOrValueSizeWithIndirectPointerOverhead(rawSize int) int { 87 if rawSize > 128 { 88 // In Go maps, if key or value is larger than 128 bytes, a pointer type 89 // is used. 90 return rawSize + PtrSize 91 } 92 return rawSize 93 } 94 95 // StaticSizeOfMap provides a best-effort estimate of number of bytes that a 96 // map takes in memory. It only includes statically sized content (i.e. struct, 97 // array, int types, pointer address itself, slice/map's reference address 98 // itself, etc.). If needed, dynamic sized stuff (slice/map content, pointer 99 // content should be calculated separately by caller. 100 func StaticSizeOfMap( 101 zeroValueKey, zeroValueValue interface{}, count int) (bytes int) { 102 return StaticSizeOfMapWithSize(int(reflect.TypeOf(zeroValueKey).Size()), 103 int(reflect.TypeOf(zeroValueValue).Size()), count) 104 } 105 106 // StaticSizeOfMapWithSize is a slightly more efficient version of 107 // StaticSizeOfMap for when the caller knows the static size of key and value 108 // without having to use `reflect`. 109 func StaticSizeOfMapWithSize( 110 keyStaticSize, valueStaticSize int, count int) (bytes int) { 111 keySize := mapKeyOrValueSizeWithIndirectPointerOverhead(keyStaticSize) 112 valueSize := mapKeyOrValueSizeWithIndirectPointerOverhead(valueStaticSize) 113 114 // See the comment of `B` field of `hmap` struct above. 115 B := math.Ceil(math.Log2(float64(count) / mapLoadFactor)) 116 numBuckets := int(math.Exp2(B)) 117 118 return hmapStructSize + 119 bucketSizeWithoutIndirectPointerOverhead*numBuckets + 120 (keySize+valueSize)*count 121 }