gitlab.com/yannislg/go-pulse@v0.0.0-20210722055913-a3e24e95638d/les/utils/expiredvalue.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package utils 18 19 import ( 20 "math" 21 22 "github.com/ethereum/go-ethereum/common/mclock" 23 ) 24 25 // ExpiredValue is a scalar value that is continuously expired (decreased 26 // exponentially) based on the provided logarithmic expiration offset value. 27 // 28 // The formula for value calculation is: base*2^(exp-logOffset). In order to 29 // simplify the calculation of ExpiredValue, its value is expressed in the form 30 // of an exponent with a base of 2. 31 // 32 // Also here is a trick to reduce a lot of calculations. In theory, when a value X 33 // decays over time and then a new value Y is added, the final result should be 34 // X*2^(exp-logOffset)+Y. However it's very hard to represent in memory. 35 // So the trick is using the idea of inflation instead of exponential decay. At this 36 // moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential 37 // decay when we actually want to calculate the value. 38 // 39 // e.g. 40 // t0: V = 100 41 // t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient 42 // t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40 43 type ExpiredValue struct { 44 Base, Exp uint64 // rlp encoding works by default 45 } 46 47 // ExpirationFactor is calculated from logOffset. 1 <= Factor < 2 and Factor*2^Exp 48 // describes the multiplier applicable for additions and the divider for readouts. 49 // If logOffset changes slowly then it saves some expensive operations to not calculate 50 // them for each addition and readout but cache this intermediate form for some time. 51 // It is also useful for structures where multiple values are expired with the same 52 // Expirer. 53 type ExpirationFactor struct { 54 Exp uint64 55 Factor float64 56 } 57 58 // ExpFactor calculates ExpirationFactor based on logOffset 59 func ExpFactor(logOffset Fixed64) ExpirationFactor { 60 return ExpirationFactor{Exp: logOffset.ToUint64(), Factor: logOffset.Fraction().Pow2()} 61 } 62 63 // Value calculates the expired value based on a floating point base and integer 64 // power-of-2 exponent. This function should be used by multi-value expired structures. 65 func (e ExpirationFactor) Value(base float64, exp uint64) float64 { 66 res := base / e.Factor 67 if exp > e.Exp { 68 res *= float64(uint64(1) << (exp - e.Exp)) 69 } 70 if exp < e.Exp { 71 res /= float64(uint64(1) << (e.Exp - exp)) 72 } 73 return res 74 } 75 76 // value calculates the value at the given moment. 77 func (e ExpiredValue) Value(logOffset Fixed64) uint64 { 78 offset := Uint64ToFixed64(e.Exp) - logOffset 79 return uint64(float64(e.Base) * offset.Pow2()) 80 } 81 82 // add adds a signed value at the given moment 83 func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { 84 integer, frac := logOffset.ToUint64(), logOffset.Fraction() 85 factor := frac.Pow2() 86 base := factor * float64(amount) 87 if integer < e.Exp { 88 base /= math.Pow(2, float64(e.Exp-integer)) 89 } 90 if integer > e.Exp { 91 e.Base >>= (integer - e.Exp) 92 e.Exp = integer 93 } 94 if base >= 0 || uint64(-base) <= e.Base { 95 // This is a temporary fix to circumvent a golang 96 // uint conversion issue on arm64, which needs to 97 // be investigated further. FIXME 98 e.Base = uint64(int64(e.Base) + int64(base)) 99 return amount 100 } 101 net := int64(-float64(e.Base) / factor) 102 e.Base = 0 103 return net 104 } 105 106 // addExp adds another ExpiredValue 107 func (e *ExpiredValue) AddExp(a ExpiredValue) { 108 if e.Exp > a.Exp { 109 a.Base >>= (e.Exp - a.Exp) 110 } 111 if e.Exp < a.Exp { 112 e.Base >>= (a.Exp - e.Exp) 113 e.Exp = a.Exp 114 } 115 e.Base += a.Base 116 } 117 118 // subExp subtracts another ExpiredValue 119 func (e *ExpiredValue) SubExp(a ExpiredValue) { 120 if e.Exp > a.Exp { 121 a.Base >>= (e.Exp - a.Exp) 122 } 123 if e.Exp < a.Exp { 124 e.Base >>= (a.Exp - e.Exp) 125 e.Exp = a.Exp 126 } 127 if e.Base > a.Base { 128 e.Base -= a.Base 129 } else { 130 e.Base = 0 131 } 132 } 133 134 // Expirer changes logOffset with a linear rate which can be changed during operation. 135 // It is not thread safe, if access by multiple goroutines is needed then it should be 136 // encapsulated into a locked structure. 137 // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset 138 // is thread safe. 139 type Expirer struct { 140 logOffset Fixed64 141 rate float64 142 lastUpdate mclock.AbsTime 143 } 144 145 // SetRate changes the expiration rate which is the inverse of the time constant in 146 // nanoseconds. 147 func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { 148 dt := now - e.lastUpdate 149 if dt > 0 { 150 e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) 151 } 152 e.lastUpdate = now 153 e.rate = rate 154 } 155 156 // SetLogOffset sets logOffset instantly. 157 func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) { 158 e.lastUpdate = now 159 e.logOffset = logOffset 160 } 161 162 // LogOffset returns the current logarithmic offset. 163 func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { 164 dt := now - e.lastUpdate 165 if dt <= 0 { 166 return e.logOffset 167 } 168 return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate) 169 } 170 171 // fixedFactor is the fixed point multiplier factor used by Fixed64. 172 const fixedFactor = 0x1000000 173 174 // Fixed64 implements 64-bit fixed point arithmetic functions. 175 type Fixed64 int64 176 177 // Uint64ToFixed64 converts uint64 integer to Fixed64 format. 178 func Uint64ToFixed64(f uint64) Fixed64 { 179 return Fixed64(f * fixedFactor) 180 } 181 182 // float64ToFixed64 converts float64 to Fixed64 format. 183 func Float64ToFixed64(f float64) Fixed64 { 184 return Fixed64(f * fixedFactor) 185 } 186 187 // toUint64 converts Fixed64 format to uint64. 188 func (f64 Fixed64) ToUint64() uint64 { 189 return uint64(f64) / fixedFactor 190 } 191 192 // fraction returns the fractional part of a Fixed64 value. 193 func (f64 Fixed64) Fraction() Fixed64 { 194 return f64 % fixedFactor 195 } 196 197 var ( 198 logToFixedFactor = float64(fixedFactor) / math.Log(2) 199 fixedToLogFactor = math.Log(2) / float64(fixedFactor) 200 ) 201 202 // pow2Fixed returns the base 2 power of the fixed point value. 203 func (f64 Fixed64) Pow2() float64 { 204 return math.Exp(float64(f64) * fixedToLogFactor) 205 }