github.com/koko1123/flow-go-1@v0.29.6/module/mempool/stdmap/backDataHeapBenchmark_test.go (about) 1 package stdmap_test 2 3 import ( 4 "runtime" 5 "runtime/debug" 6 "testing" 7 "time" 8 9 lru "github.com/hashicorp/golang-lru" 10 zlog "github.com/rs/zerolog/log" 11 "github.com/stretchr/testify/require" 12 13 "github.com/koko1123/flow-go-1/model/flow" 14 herocache "github.com/koko1123/flow-go-1/module/mempool/herocache/backdata" 15 "github.com/koko1123/flow-go-1/module/mempool/herocache/backdata/heropool" 16 "github.com/koko1123/flow-go-1/module/mempool/stdmap" 17 "github.com/koko1123/flow-go-1/module/metrics" 18 "github.com/koko1123/flow-go-1/utils/unittest" 19 ) 20 21 // BenchmarkBaselineLRU benchmarks heap allocation performance of 22 // hashicorp LRU cache with 50K capacity against writing 100M entities, 23 // with Garbage Collection (GC) disabled. 24 func BenchmarkBaselineLRU(b *testing.B) { 25 unittest.SkipBenchmarkUnless(b, unittest.BENCHMARK_EXPERIMENT, "skips benchmarking baseline LRU, set environment variable to enable") 26 27 defer debug.SetGCPercent(debug.SetGCPercent(-1)) // disable GC 28 29 limit := uint(50) 30 backData := stdmap.NewBackend( 31 stdmap.WithBackData(newBaselineLRU(int(limit))), 32 stdmap.WithLimit(limit)) 33 34 entities := unittest.EntityListFixture(uint(100_000)) 35 testAddEntities(b, limit, backData, entities) 36 37 unittest.PrintHeapInfo(unittest.Logger()) // heap info after writing 100M entities 38 gcAndWriteHeapProfile() // runs garbage collection 39 unittest.PrintHeapInfo(unittest.Logger()) // heap info after running garbage collection 40 } 41 42 // BenchmarkArrayBackDataLRU benchmarks heap allocation performance of 43 // ArrayBackData-based cache (aka heroCache) with 50K capacity against writing 100M entities, 44 // with Garbage Collection (GC) disabled. 45 func BenchmarkArrayBackDataLRU(b *testing.B) { 46 defer debug.SetGCPercent(debug.SetGCPercent(-1)) // disable GC 47 limit := uint(50_000) 48 49 backData := stdmap.NewBackend( 50 stdmap.WithBackData( 51 herocache.NewCache( 52 uint32(limit), 53 8, 54 heropool.LRUEjection, 55 unittest.Logger(), 56 metrics.NewNoopCollector())), 57 stdmap.WithLimit(limit)) 58 59 entities := unittest.EntityListFixture(uint(100_000_000)) 60 testAddEntities(b, limit, backData, entities) 61 62 unittest.PrintHeapInfo(unittest.Logger()) // heap info after writing 100M entities 63 gcAndWriteHeapProfile() // runs garbage collection 64 unittest.PrintHeapInfo(unittest.Logger()) // heap info after running garbage collection 65 } 66 67 func gcAndWriteHeapProfile() { 68 // see <https://pkg.go.dev/runtime/pprof> 69 t1 := time.Now() 70 runtime.GC() // get up-to-date statistics 71 elapsed := time.Since(t1).Seconds() 72 zlog.Info(). 73 Float64("gc-elapsed-time", elapsed). 74 Msg("garbage collection done") 75 } 76 77 // testAddEntities is a test helper that checks entities are added successfully to the backdata. 78 // and each entity is retrievable right after it is written to backdata. 79 func testAddEntities(t testing.TB, limit uint, b *stdmap.Backend, entities []*unittest.MockEntity) { 80 // adding elements 81 t1 := time.Now() 82 for i, e := range entities { 83 require.False(t, b.Has(e.ID())) 84 // adding each element must be successful. 85 require.True(t, b.Add(*e)) 86 87 if uint(i) < limit { 88 // when we are below limit the total of 89 // backdata should be incremented by each addition. 90 require.Equal(t, b.Size(), uint(i+1)) 91 } else { 92 // when we cross the limit, the ejection kicks in, and 93 // size must be steady at the limit. 94 require.Equal(t, uint(b.Size()), limit) 95 } 96 97 // entity should be immediately retrievable 98 actual, ok := b.ByID(e.ID()) 99 require.True(t, ok) 100 require.Equal(t, *e, actual) 101 } 102 elapsed := time.Since(t1) 103 zlog.Info().Dur("interaction_time", elapsed).Msg("adding elements done") 104 } 105 106 // baseLineLRU implements a BackData wrapper around hashicorp lru, which makes 107 // it compliant to be used as BackData component in mempool.Backend. Note that 108 // this is used only as an experimental baseline, and so it's not exported for 109 // production. 110 type baselineLRU struct { 111 c *lru.Cache // used to incorporate an LRU cache 112 limit int 113 } 114 115 func newBaselineLRU(limit int) *baselineLRU { 116 var err error 117 c, err := lru.New(limit) 118 if err != nil { 119 panic(err) 120 } 121 122 return &baselineLRU{ 123 c: c, 124 limit: limit, 125 } 126 } 127 128 // Has checks if we already contain the item with the given hash. 129 func (b *baselineLRU) Has(entityID flow.Identifier) bool { 130 _, ok := b.c.Get(entityID) 131 return ok 132 } 133 134 // Add adds the given item to the pool. 135 func (b *baselineLRU) Add(entityID flow.Identifier, entity flow.Entity) bool { 136 b.c.Add(entityID, entity) 137 return true 138 } 139 140 // Remove will remove the item with the given hash. 141 func (b *baselineLRU) Remove(entityID flow.Identifier) (flow.Entity, bool) { 142 e, ok := b.c.Get(entityID) 143 if !ok { 144 return nil, false 145 } 146 entity, ok := e.(flow.Entity) 147 if !ok { 148 return nil, false 149 } 150 151 return entity, b.c.Remove(entityID) 152 } 153 154 // Adjust will adjust the value item using the given function if the given key can be found. 155 // Returns a bool which indicates whether the value was updated as well as the updated value 156 func (b *baselineLRU) Adjust(entityID flow.Identifier, f func(flow.Entity) flow.Entity) (flow.Entity, bool) { 157 entity, removed := b.Remove(entityID) 158 if !removed { 159 return nil, false 160 } 161 162 newEntity := f(entity) 163 newEntityID := newEntity.ID() 164 165 b.Add(newEntityID, newEntity) 166 167 return newEntity, true 168 } 169 170 // ByID returns the given item from the pool. 171 func (b *baselineLRU) ByID(entityID flow.Identifier) (flow.Entity, bool) { 172 e, ok := b.c.Get(entityID) 173 if !ok { 174 return nil, false 175 } 176 177 entity, ok := e.(flow.Entity) 178 if !ok { 179 return nil, false 180 } 181 return entity, ok 182 } 183 184 // Size will return the total of the backend. 185 func (b baselineLRU) Size() uint { 186 return uint(b.c.Len()) 187 } 188 189 // All returns all entities from the pool. 190 func (b baselineLRU) All() map[flow.Identifier]flow.Entity { 191 all := make(map[flow.Identifier]flow.Entity) 192 for _, entityID := range b.c.Keys() { 193 id, ok := entityID.(flow.Identifier) 194 if !ok { 195 panic("could not assert to entity id") 196 } 197 198 entity, ok := b.ByID(id) 199 if !ok { 200 panic("could not retrieve entity from mempool") 201 } 202 all[id] = entity 203 } 204 205 return all 206 } 207 208 func (b baselineLRU) Identifiers() flow.IdentifierList { 209 ids := make(flow.IdentifierList, b.c.Len()) 210 entityIds := b.c.Keys() 211 total := len(entityIds) 212 for i := 0; i < total; i++ { 213 id, ok := entityIds[i].(flow.Identifier) 214 if !ok { 215 panic("could not assert to entity id") 216 } 217 ids[i] = id 218 } 219 return ids 220 } 221 222 func (b baselineLRU) Entities() []flow.Entity { 223 entities := make([]flow.Entity, b.c.Len()) 224 entityIds := b.c.Keys() 225 total := len(entityIds) 226 for i := 0; i < total; i++ { 227 id, ok := entityIds[i].(flow.Identifier) 228 if !ok { 229 panic("could not assert to entity id") 230 } 231 232 entity, ok := b.ByID(id) 233 if !ok { 234 panic("could not retrieve entity from mempool") 235 } 236 entities[i] = entity 237 } 238 return entities 239 } 240 241 // Clear removes all entities from the pool. 242 func (b *baselineLRU) Clear() { 243 var err error 244 b.c, err = lru.New(b.limit) 245 if err != nil { 246 panic(err) 247 } 248 }