expiredvalue.go 8.2 KB

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