github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/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 "sync" 22 23 "github.com/kisexp/xdchain/common/mclock" 24 ) 25 26 // ExpiredValue is a scalar value that is continuously expired (decreased 27 // exponentially) based on the provided logarithmic expiration offset value. 28 // 29 // The formula for value calculation is: base*2^(exp-logOffset). In order to 30 // simplify the calculation of ExpiredValue, its value is expressed in the form 31 // of an exponent with a base of 2. 32 // 33 // Also here is a trick to reduce a lot of calculations. In theory, when a value X 34 // decays over time and then a new value Y is added, the final result should be 35 // X*2^(exp-logOffset)+Y. However it's very hard to represent in memory. 36 // So the trick is using the idea of inflation instead of exponential decay. At this 37 // moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential 38 // decay when we actually want to calculate the value. 39 // 40 // e.g. 41 // t0: V = 100 42 // t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient 43 // t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40 44 type ExpiredValue struct { 45 Base, Exp uint64 // rlp encoding works by default 46 } 47 48 // ExpirationFactor is calculated from logOffset. 1 <= Factor < 2 and Factor*2^Exp 49 // describes the multiplier applicable for additions and the divider for readouts. 50 // If logOffset changes slowly then it saves some expensive operations to not calculate 51 // them for each addition and readout but cache this intermediate form for some time. 52 // It is also useful for structures where multiple values are expired with the same 53 // Expirer. 54 type ExpirationFactor struct { 55 Exp uint64 56 Factor float64 57 } 58 59 // ExpFactor calculates ExpirationFactor based on logOffset 60 func ExpFactor(logOffset Fixed64) ExpirationFactor { 61 return ExpirationFactor{Exp: logOffset.ToUint64(), Factor: logOffset.Fraction().Pow2()} 62 } 63 64 // Value calculates the expired value based on a floating point base and integer 65 // power-of-2 exponent. This function should be used by multi-value expired structures. 66 func (e ExpirationFactor) Value(base float64, exp uint64) float64 { 67 return base / e.Factor * math.Pow(2, float64(int64(exp-e.Exp))) 68 } 69 70 // value calculates the value at the given moment. 71 func (e ExpiredValue) Value(logOffset Fixed64) uint64 { 72 offset := Uint64ToFixed64(e.Exp) - logOffset 73 return uint64(float64(e.Base) * offset.Pow2()) 74 } 75 76 // add adds a signed value at the given moment 77 func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { 78 integer, frac := logOffset.ToUint64(), logOffset.Fraction() 79 factor := frac.Pow2() 80 base := factor * float64(amount) 81 if integer < e.Exp { 82 base /= math.Pow(2, float64(e.Exp-integer)) 83 } 84 if integer > e.Exp { 85 e.Base >>= (integer - e.Exp) 86 e.Exp = integer 87 } 88 if base >= 0 || uint64(-base) <= e.Base { 89 // This is a temporary fix to circumvent a golang 90 // uint conversion issue on arm64, which needs to 91 // be investigated further. More details at: 92 // https://github.com/golang/go/issues/43047 93 e.Base += uint64(int64(base)) 94 return amount 95 } 96 net := int64(-float64(e.Base) / factor) 97 e.Base = 0 98 return net 99 } 100 101 // addExp adds another ExpiredValue 102 func (e *ExpiredValue) AddExp(a ExpiredValue) { 103 if e.Exp > a.Exp { 104 a.Base >>= (e.Exp - a.Exp) 105 } 106 if e.Exp < a.Exp { 107 e.Base >>= (a.Exp - e.Exp) 108 e.Exp = a.Exp 109 } 110 e.Base += a.Base 111 } 112 113 // subExp subtracts another ExpiredValue 114 func (e *ExpiredValue) SubExp(a ExpiredValue) { 115 if e.Exp > a.Exp { 116 a.Base >>= (e.Exp - a.Exp) 117 } 118 if e.Exp < a.Exp { 119 e.Base >>= (a.Exp - e.Exp) 120 e.Exp = a.Exp 121 } 122 if e.Base > a.Base { 123 e.Base -= a.Base 124 } else { 125 e.Base = 0 126 } 127 } 128 129 // IsZero returns true if the value is zero 130 func (e *ExpiredValue) IsZero() bool { 131 return e.Base == 0 132 } 133 134 // LinearExpiredValue is very similar with the expiredValue which the value 135 // will continuously expired. But the different part is it's expired linearly. 136 type LinearExpiredValue struct { 137 Offset uint64 // The latest time offset 138 Val uint64 // The remaining value, can never be negative 139 Rate mclock.AbsTime `rlp:"-"` // Expiration rate(by nanosecond), will ignored by RLP 140 } 141 142 // value calculates the value at the given moment. This function always has the 143 // assumption that the given timestamp shouldn't less than the recorded one. 144 func (e LinearExpiredValue) Value(now mclock.AbsTime) uint64 { 145 offset := uint64(now / e.Rate) 146 if e.Offset < offset { 147 diff := offset - e.Offset 148 if e.Val >= diff { 149 e.Val -= diff 150 } else { 151 e.Val = 0 152 } 153 } 154 return e.Val 155 } 156 157 // add adds a signed value at the given moment. This function always has the 158 // assumption that the given timestamp shouldn't less than the recorded one. 159 func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 { 160 offset := uint64(now / e.Rate) 161 if e.Offset < offset { 162 diff := offset - e.Offset 163 if e.Val >= diff { 164 e.Val -= diff 165 } else { 166 e.Val = 0 167 } 168 e.Offset = offset 169 } 170 if amount < 0 && uint64(-amount) > e.Val { 171 e.Val = 0 172 } else { 173 e.Val = uint64(int64(e.Val) + amount) 174 } 175 return e.Val 176 } 177 178 // ValueExpirer controls value expiration rate 179 type ValueExpirer interface { 180 SetRate(now mclock.AbsTime, rate float64) 181 SetLogOffset(now mclock.AbsTime, logOffset Fixed64) 182 LogOffset(now mclock.AbsTime) Fixed64 183 } 184 185 // Expirer changes logOffset with a linear rate which can be changed during operation. 186 // It is not thread safe, if access by multiple goroutines is needed then it should be 187 // encapsulated into a locked structure. 188 // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset 189 // is thread safe. 190 type Expirer struct { 191 lock sync.RWMutex 192 logOffset Fixed64 193 rate float64 194 lastUpdate mclock.AbsTime 195 } 196 197 // SetRate changes the expiration rate which is the inverse of the time constant in 198 // nanoseconds. 199 func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { 200 e.lock.Lock() 201 defer e.lock.Unlock() 202 203 dt := now - e.lastUpdate 204 if dt > 0 { 205 e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) 206 } 207 e.lastUpdate = now 208 e.rate = rate 209 } 210 211 // SetLogOffset sets logOffset instantly. 212 func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) { 213 e.lock.Lock() 214 defer e.lock.Unlock() 215 216 e.lastUpdate = now 217 e.logOffset = logOffset 218 } 219 220 // LogOffset returns the current logarithmic offset. 221 func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { 222 e.lock.RLock() 223 defer e.lock.RUnlock() 224 225 dt := now - e.lastUpdate 226 if dt <= 0 { 227 return e.logOffset 228 } 229 return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate) 230 } 231 232 // fixedFactor is the fixed point multiplier factor used by Fixed64. 233 const fixedFactor = 0x1000000 234 235 // Fixed64 implements 64-bit fixed point arithmetic functions. 236 type Fixed64 int64 237 238 // Uint64ToFixed64 converts uint64 integer to Fixed64 format. 239 func Uint64ToFixed64(f uint64) Fixed64 { 240 return Fixed64(f * fixedFactor) 241 } 242 243 // float64ToFixed64 converts float64 to Fixed64 format. 244 func Float64ToFixed64(f float64) Fixed64 { 245 return Fixed64(f * fixedFactor) 246 } 247 248 // toUint64 converts Fixed64 format to uint64. 249 func (f64 Fixed64) ToUint64() uint64 { 250 return uint64(f64) / fixedFactor 251 } 252 253 // fraction returns the fractional part of a Fixed64 value. 254 func (f64 Fixed64) Fraction() Fixed64 { 255 return f64 % fixedFactor 256 } 257 258 var ( 259 logToFixedFactor = float64(fixedFactor) / math.Log(2) 260 fixedToLogFactor = math.Log(2) / float64(fixedFactor) 261 ) 262 263 // pow2Fixed returns the base 2 power of the fixed point value. 264 func (f64 Fixed64) Pow2() float64 { 265 return math.Exp(float64(f64) * fixedToLogFactor) 266 }