From 2574e46cd2c72d806cb6cf5e36c71c8c5141ef37 Mon Sep 17 00:00:00 2001 From: David Finkel Date: Fri, 29 Jul 2022 10:44:08 -0400 Subject: [PATCH] lru: swap linked-list and map fields The Garbage-Collector enqueues objects to scan to a work-queue in-order. Place the map before the LRU-stack linked-list so it has an extra head-start on scanning the much more efficient-to-scan datastructure (the map). The GC's mark-phase's traversal of the linked-list is a bit expensive because it'll have to jump one link at a time, enqueuing the next and/or previous pointers. By traversing the map first, a pseudo-random assortment of entry-points are used, rather than just the first and last. If large-ish (where we actually care) the map will be broken up into a number of pieces for scanning, so the first chunk will be scanned; enqueuing those linked-list elements before the head and tail get scanned. This swap will only have a modest effect because the map is already scanned, but hopefully we can reduce the recursion-depth by one. (We'll have to switch to something more CLOCK-ish without a linked-list to be GC-friendly; this change is just making the LRU modestly less GC-hostile) --- lru/lru.go | 5 ++++- lru/typed_lru.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lru/lru.go b/lru/lru.go index a9f42c3f..19e2dea5 100644 --- a/lru/lru.go +++ b/lru/lru.go @@ -31,8 +31,11 @@ type Cache struct { // executed when an entry is purged from the cache. OnEvicted func(key Key, value interface{}) - ll *list.List + // cache comes first so the GC enqueues marking the map-contents first + // (which will mark the contents of the linked-list much more + // efficiently than traversing the linked-list directly) cache map[interface{}]*list.Element + ll *list.List } // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators diff --git a/lru/typed_lru.go b/lru/typed_lru.go index 89696301..e596b1b1 100644 --- a/lru/typed_lru.go +++ b/lru/typed_lru.go @@ -29,8 +29,11 @@ type TypedCache[K comparable, V any] struct { // executed when an typedEntry is purged from the cache. OnEvicted func(key K, value V) - ll linkedList[typedEntry[K, V]] + // cache comes first so the GC enqueues marking the map-contents first + // (which will mark the contents of the linked-list much more + // efficiently than traversing the linked-list directly) cache map[K]*llElem[typedEntry[K, V]] + ll linkedList[typedEntry[K, V]] } type typedEntry[K comparable, V any] struct {