gpu: optimize opCache to avoid expensive map lookups

Benchmarking showed that the double map access calls
were a bottleneck. Rework the cache to avoid half of them.

The simplest, naive approach would have been to store a
pointer to a struct with a keep field in the map, allowing cheap
update and frame() operation. Benchmarking showed that the
increased GC pressure of that approach decreased performance
however.

Signed-off-by: Viktor <viktor.ogeman@gmail.com>
This commit is contained in:
Viktor
2020-06-20 23:29:57 +02:00
committed by Elias Naur
parent f11a656426
commit 062cb210ea
+53 -24
View File
@@ -14,15 +14,22 @@ type resourceCache struct {
newRes map[interface{}]resource newRes map[interface{}]resource
} }
// opCache is like a resourceCache but using concrete types. // opCache is like a resourceCache but using concrete types and a
// freelist instead of two maps to avoid runtime.mapaccess2 calls
// since benchmarking showed them as a bottleneck.
type opCache struct { type opCache struct {
res map[ops.Key]opCacheValue // store the index + 1 in cache this key is stored in
newRes map[ops.Key]opCacheValue index map[ops.Key]int
// list of indexes in cache that are free and can be used
freelist []int
cache []opCacheValue
} }
type opCacheValue struct { type opCacheValue struct {
data *pathData data *pathData
bounds f32.Rectangle bounds f32.Rectangle
key ops.Key
keep bool
} }
func newResourceCache() *resourceCache { func newResourceCache() *resourceCache {
@@ -71,41 +78,63 @@ func (r *resourceCache) release() {
func newOpCache() *opCache { func newOpCache() *opCache {
return &opCache{ return &opCache{
res: make(map[ops.Key]opCacheValue), index: make(map[ops.Key]int),
newRes: make(map[ops.Key]opCacheValue), freelist: make([]int, 0),
cache: make([]opCacheValue, 0),
} }
} }
func (r *opCache) get(key ops.Key) (opCacheValue, bool) { func (r *opCache) get(key ops.Key) (o opCacheValue, exist bool) {
v, exists := r.res[key] v := r.index[key]
if exists { if v == 0 {
r.newRes[key] = v return
} }
return v, exists r.cache[v-1].keep = true
return r.cache[v-1], true
} }
func (r *opCache) put(key ops.Key, val opCacheValue) { func (r *opCache) put(key ops.Key, val opCacheValue) {
r.res[key] = val v := r.index[key]
r.newRes[key] = val val.keep = true
val.key = key
if v == 0 {
// not in cache
i := len(r.cache)
if len(r.freelist) > 0 {
i = r.freelist[len(r.freelist)-1]
r.freelist = r.freelist[:len(r.freelist)-1]
r.cache[i] = val
} else {
r.cache = append(r.cache, val)
}
r.index[key] = i + 1
} else {
r.cache[v-1] = val
}
} }
func (r *opCache) frame() { func (r *opCache) frame() {
for k, v := range r.res { r.freelist = r.freelist[:0]
if _, exists := r.newRes[k]; !exists { for i, v := range r.cache {
delete(r.res, k) r.cache[i].keep = false
v.data.release() if v.keep {
continue
} }
} if v.data != nil {
for k, v := range r.newRes { v.data.release()
delete(r.newRes, k) r.cache[i].data = nil
r.res[k] = v }
delete(r.index, v.key)
r.freelist = append(r.freelist, i)
} }
} }
func (r *opCache) release() { func (r *opCache) release() {
for _, v := range r.newRes { for i := range r.cache {
v.data.release() r.cache[i].keep = false
} }
r.newRes = nil r.frame()
r.res = nil r.index = nil
r.freelist = nil
r.cache = nil
} }