github.com/zhiqiangxu/go-ethereum@v1.9.16-0.20210824055606-be91cfdebc48/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/zhiqiangxu/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 return base / e.Factor * math.Pow(2, float64(int64(exp-e.Exp))) 67 } 68 69 // value calculates the value at the given moment. 70 func (e ExpiredValue) Value(logOffset Fixed64) uint64 { 71 offset := Uint64ToFixed64(e.Exp) - logOffset 72 return uint64(float64(e.Base) * offset.Pow2()) 73 } 74 75 // add adds a signed value at the given moment 76 func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { 77 integer, frac := logOffset.ToUint64(), logOffset.Fraction() 78 factor := frac.Pow2() 79 base := factor * float64(amount) 80 if integer < e.Exp { 81 base /= math.Pow(2, float64(e.Exp-integer)) 82 } 83 if integer > e.Exp { 84 e.Base >>= (integer - e.Exp) 85 e.Exp = integer 86 } 87 if base >= 0 || uint64(-base) <= e.Base { 88 // This is a temporary fix to circumvent a golang 89 // uint conversion issue on arm64, which needs to 90 // be investigated further. FIXME 91 e.Base = uint64(int64(e.Base) + int64(base)) 92 return amount 93 } 94 net := int64(-float64(e.Base) / factor) 95 e.Base = 0 96 return net 97 } 98 99 // addExp adds another ExpiredValue 100 func (e *ExpiredValue) AddExp(a ExpiredValue) { 101 if e.Exp > a.Exp { 102 a.Base >>= (e.Exp - a.Exp) 103 } 104 if e.Exp < a.Exp { 105 e.Base >>= (a.Exp - e.Exp) 106 e.Exp = a.Exp 107 } 108 e.Base += a.Base 109 } 110 111 // subExp subtracts another ExpiredValue 112 func (e *ExpiredValue) SubExp(a ExpiredValue) { 113 if e.Exp > a.Exp { 114 a.Base >>= (e.Exp - a.Exp) 115 } 116 if e.Exp < a.Exp { 117 e.Base >>= (a.Exp - e.Exp) 118 e.Exp = a.Exp 119 } 120 if e.Base > a.Base { 121 e.Base -= a.Base 122 } else { 123 e.Base = 0 124 } 125 } 126 127 // Expirer changes logOffset with a linear rate which can be changed during operation. 128 // It is not thread safe, if access by multiple goroutines is needed then it should be 129 // encapsulated into a locked structure. 130 // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset 131 // is thread safe. 132 type Expirer struct { 133 logOffset Fixed64 134 rate float64 135 lastUpdate mclock.AbsTime 136 } 137 138 // SetRate changes the expiration rate which is the inverse of the time constant in 139 // nanoseconds. 140 func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { 141 dt := now - e.lastUpdate 142 if dt > 0 { 143 e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) 144 } 145 e.lastUpdate = now 146 e.rate = rate 147 } 148 149 // SetLogOffset sets logOffset instantly. 150 func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) { 151 e.lastUpdate = now 152 e.logOffset = logOffset 153 } 154 155 // LogOffset returns the current logarithmic offset. 156 func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { 157 dt := now - e.lastUpdate 158 if dt <= 0 { 159 return e.logOffset 160 } 161 return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate) 162 } 163 164 // fixedFactor is the fixed point multiplier factor used by Fixed64. 165 const fixedFactor = 0x1000000 166 167 // Fixed64 implements 64-bit fixed point arithmetic functions. 168 type Fixed64 int64 169 170 // Uint64ToFixed64 converts uint64 integer to Fixed64 format. 171 func Uint64ToFixed64(f uint64) Fixed64 { 172 return Fixed64(f * fixedFactor) 173 } 174 175 // float64ToFixed64 converts float64 to Fixed64 format. 176 func Float64ToFixed64(f float64) Fixed64 { 177 return Fixed64(f * fixedFactor) 178 } 179 180 // toUint64 converts Fixed64 format to uint64. 181 func (f64 Fixed64) ToUint64() uint64 { 182 return uint64(f64) / fixedFactor 183 } 184 185 // fraction returns the fractional part of a Fixed64 value. 186 func (f64 Fixed64) Fraction() Fixed64 { 187 return f64 % fixedFactor 188 } 189 190 var ( 191 logToFixedFactor = float64(fixedFactor) / math.Log(2) 192 fixedToLogFactor = math.Log(2) / float64(fixedFactor) 193 ) 194 195 // pow2Fixed returns the base 2 power of the fixed point value. 196 func (f64 Fixed64) Pow2() float64 { 197 return math.Exp(float64(f64) * fixedToLogFactor) 198 }