github.com/aswedchain/aswed@v1.0.1/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/aswedchain/aswed/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. FIXME 92 e.Base = uint64(int64(e.Base) + int64(base)) 93 return amount 94 } 95 net := int64(-float64(e.Base) / factor) 96 e.Base = 0 97 return net 98 } 99 100 // addExp adds another ExpiredValue 101 func (e *ExpiredValue) AddExp(a ExpiredValue) { 102 if e.Exp > a.Exp { 103 a.Base >>= (e.Exp - a.Exp) 104 } 105 if e.Exp < a.Exp { 106 e.Base >>= (a.Exp - e.Exp) 107 e.Exp = a.Exp 108 } 109 e.Base += a.Base 110 } 111 112 // subExp subtracts another ExpiredValue 113 func (e *ExpiredValue) SubExp(a ExpiredValue) { 114 if e.Exp > a.Exp { 115 a.Base >>= (e.Exp - a.Exp) 116 } 117 if e.Exp < a.Exp { 118 e.Base >>= (a.Exp - e.Exp) 119 e.Exp = a.Exp 120 } 121 if e.Base > a.Base { 122 e.Base -= a.Base 123 } else { 124 e.Base = 0 125 } 126 } 127 128 // IsZero returns true if the value is zero 129 func (e *ExpiredValue) IsZero() bool { 130 return e.Base == 0 131 } 132 133 // LinearExpiredValue is very similar with the expiredValue which the value 134 // will continuously expired. But the different part is it's expired linearly. 135 type LinearExpiredValue struct { 136 Offset uint64 // The latest time offset 137 Val uint64 // The remaining value, can never be negative 138 Rate mclock.AbsTime `rlp:"-"` // Expiration rate(by nanosecond), will ignored by RLP 139 } 140 141 // value calculates the value at the given moment. This function always has the 142 // assumption that the given timestamp shouldn't less than the recorded one. 143 func (e LinearExpiredValue) Value(now mclock.AbsTime) uint64 { 144 offset := uint64(now / e.Rate) 145 if e.Offset < offset { 146 diff := offset - e.Offset 147 if e.Val >= diff { 148 e.Val -= diff 149 } else { 150 e.Val = 0 151 } 152 } 153 return e.Val 154 } 155 156 // add adds a signed value at the given moment. This function always has the 157 // assumption that the given timestamp shouldn't less than the recorded one. 158 func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 { 159 offset := uint64(now / e.Rate) 160 if e.Offset < offset { 161 diff := offset - e.Offset 162 if e.Val >= diff { 163 e.Val -= diff 164 } else { 165 e.Val = 0 166 } 167 e.Offset = offset 168 } 169 if amount < 0 && uint64(-amount) > e.Val { 170 e.Val = 0 171 } else { 172 e.Val = uint64(int64(e.Val) + amount) 173 } 174 return e.Val 175 } 176 177 // ValueExpirer controls value expiration rate 178 type ValueExpirer interface { 179 SetRate(now mclock.AbsTime, rate float64) 180 SetLogOffset(now mclock.AbsTime, logOffset Fixed64) 181 LogOffset(now mclock.AbsTime) Fixed64 182 } 183 184 // Expirer changes logOffset with a linear rate which can be changed during operation. 185 // It is not thread safe, if access by multiple goroutines is needed then it should be 186 // encapsulated into a locked structure. 187 // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset 188 // is thread safe. 189 type Expirer struct { 190 lock sync.RWMutex 191 logOffset Fixed64 192 rate float64 193 lastUpdate mclock.AbsTime 194 } 195 196 // SetRate changes the expiration rate which is the inverse of the time constant in 197 // nanoseconds. 198 func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { 199 e.lock.Lock() 200 defer e.lock.Unlock() 201 202 dt := now - e.lastUpdate 203 if dt > 0 { 204 e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) 205 } 206 e.lastUpdate = now 207 e.rate = rate 208 } 209 210 // SetLogOffset sets logOffset instantly. 211 func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) { 212 e.lock.Lock() 213 defer e.lock.Unlock() 214 215 e.lastUpdate = now 216 e.logOffset = logOffset 217 } 218 219 // LogOffset returns the current logarithmic offset. 220 func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { 221 e.lock.RLock() 222 defer e.lock.RUnlock() 223 224 dt := now - e.lastUpdate 225 if dt <= 0 { 226 return e.logOffset 227 } 228 return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate) 229 } 230 231 // fixedFactor is the fixed point multiplier factor used by Fixed64. 232 const fixedFactor = 0x1000000 233 234 // Fixed64 implements 64-bit fixed point arithmetic functions. 235 type Fixed64 int64 236 237 // Uint64ToFixed64 converts uint64 integer to Fixed64 format. 238 func Uint64ToFixed64(f uint64) Fixed64 { 239 return Fixed64(f * fixedFactor) 240 } 241 242 // float64ToFixed64 converts float64 to Fixed64 format. 243 func Float64ToFixed64(f float64) Fixed64 { 244 return Fixed64(f * fixedFactor) 245 } 246 247 // toUint64 converts Fixed64 format to uint64. 248 func (f64 Fixed64) ToUint64() uint64 { 249 return uint64(f64) / fixedFactor 250 } 251 252 // fraction returns the fractional part of a Fixed64 value. 253 func (f64 Fixed64) Fraction() Fixed64 { 254 return f64 % fixedFactor 255 } 256 257 var ( 258 logToFixedFactor = float64(fixedFactor) / math.Log(2) 259 fixedToLogFactor = math.Log(2) / float64(fixedFactor) 260 ) 261 262 // pow2Fixed returns the base 2 power of the fixed point value. 263 func (f64 Fixed64) Pow2() float64 { 264 return math.Exp(float64(f64) * fixedToLogFactor) 265 }