/
perf_cache.go
178 lines (162 loc) · 4.94 KB
/
perf_cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright 2020 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vdl
import (
"fmt"
"reflect"
"sync"
)
type perfReflectCacheT struct {
sync.RWMutex
rtmap map[reflect.Type]perfReflectInfo
}
type perfReflectInfo struct {
keyType reflect.Type
fieldIndexMap map[string]int
implementsIfc implementsBitMasks
implementsIsSet implementsBitMasks
nativeInfo *nativeInfo
nativeInfoIsSet bool
}
var perfReflectCache = &perfReflectCacheT{
rtmap: make(map[reflect.Type]perfReflectInfo),
}
type implementsBitMasks uint16
const (
rtIsZeroerBitMask implementsBitMasks = 1 << iota
rtVDLWriterBitMask
rtNativeIsZeroBitMask
rtErrorBitMask
rtNativeConverterBitMask
rtIsZeroerPtrToBitMask implementsBitMasks = 8 << iota
rtVDLWriterPtrToBitMask
rtNativeIsZeroPtrToBitMask
)
// getPerReflectInfo returns the currently cached info, however, that may be
// empty or incomplete and the per-field methods used to determine if a
// specified field is set or not and to fill it in if not set. This API is used
// to minimize the number of lock aquistions required for the common case where
// the data is already available and to allow data to be filled in incrementally
// as required, since the common case is that most of the fields are not
// used for a given type.
//
// For example, the following only requires one lock operation for the case
// where both required fields are already available.
//
// pri := perfReflectCache.perfReflectInfo(rt)
// ....
// idxMap := perfReflectCache.fieldIndexMap(pri, rt)
// ....
// hasZeroer := perfReflectCache.implements(pri, rt, rtIsZeroerBitMask)
// ....
// hasPtrZeroer := perfReflectCache.implements(pri, rt, rtIsZeroerBitMask|rtIsPtrToMask)
func (prc *perfReflectCacheT) perfReflectInfo(rt reflect.Type) perfReflectInfo {
prc.RLock()
r := prc.rtmap[rt]
prc.RUnlock()
return r
}
// fieldIndexMap returns a map the index of the struct field in rt with the given
// name.
//
// This function is purely a performance optimization; the current
// implementation of reflect.Type.Field(index) causes an allocation, which is
// avoided in the common case by caching the result.
//
// REQUIRES: rt.Kind() == reflect.Struct
func (prc *perfReflectCacheT) fieldIndexMap(pri perfReflectInfo, rt reflect.Type) map[string]int {
if pri.keyType == rt && pri.fieldIndexMap != nil {
return pri.fieldIndexMap
}
return prc.cacheFieldIndexMap(rt)
}
//go:noinline
func (prc *perfReflectCacheT) cacheFieldIndexMap(rt reflect.Type) map[string]int {
prc.Lock()
defer prc.Unlock()
// benign race between read and write locks.
pri, ok := prc.rtmap[rt]
if ok && pri.fieldIndexMap != nil {
return pri.fieldIndexMap
}
pri.fieldIndexMap = createFieldIndexMap(rt)
pri.keyType = rt
prc.rtmap[rt] = pri
return pri.fieldIndexMap
}
func createFieldIndexMap(rt reflect.Type) map[string]int {
if numField := rt.NumField(); numField > 0 {
m := make(map[string]int, numField)
for i := 0; i < numField; i++ {
m[rt.Field(i).Name] = i
}
return m
}
return nil
}
func (prc *perfReflectCacheT) implementsBuiltinInterface(pri perfReflectInfo, rt reflect.Type, mask implementsBitMasks) bool {
if pri.keyType == rt && (pri.implementsIsSet&mask) != 0 {
return (pri.implementsIfc & mask) != 0
}
return prc.cacheImplementsBuiltinInterface(rt, mask)
}
//go:noinline
func (prc *perfReflectCacheT) cacheImplementsBuiltinInterface(rt reflect.Type, mask implementsBitMasks) bool {
prc.Lock()
defer prc.Unlock()
// benign race between read and write locks.
pri := prc.rtmap[rt]
if (pri.implementsIsSet & mask) != 0 {
return (pri.implementsIfc & mask) != 0
}
var target reflect.Type
switch mask {
case rtIsZeroerBitMask, rtIsZeroerPtrToBitMask:
target = rtIsZeroer
case rtNativeIsZeroBitMask, rtNativeIsZeroPtrToBitMask:
target = rtNativeIsZero
case rtVDLWriterBitMask, rtVDLWriterPtrToBitMask:
target = rtVDLWriter
case rtNativeConverterBitMask:
target = rtNativeConverter
case rtErrorBitMask:
target = rtError
default:
panic(fmt.Sprintf("invalid bit mask for interface implementation test: %b", mask))
}
result := false
if mask >= rtIsZeroerPtrToBitMask { // first PtrTo type.
result = reflect.PtrTo(rt).Implements(target)
} else {
result = rt.Implements(target)
}
if result {
pri.implementsIfc |= mask
}
pri.implementsIsSet |= mask
pri.keyType = rt
prc.rtmap[rt] = pri
return result
}
func (prc *perfReflectCacheT) nativeInfo(pri perfReflectInfo, rt reflect.Type) *nativeInfo {
if pri.nativeInfoIsSet {
return pri.nativeInfo
}
return prc.cacheNativeInfo(rt)
}
//go:noinline
func (prc *perfReflectCacheT) cacheNativeInfo(rt reflect.Type) *nativeInfo {
prc.Lock()
defer prc.Unlock()
// benign race between read and write locks.
pri := prc.rtmap[rt]
if pri.nativeInfoIsSet {
return pri.nativeInfo
}
pri.nativeInfo = nativeInfoFromNative(rt)
pri.nativeInfoIsSet = true
pri.keyType = rt
prc.rtmap[rt] = pri
return pri.nativeInfo
}