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  }