expiredvalue.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. "github.com/ethereum/go-ethereum/common/mclock"
  20. )
  21. // ExpiredValue is a scalar value that is continuously expired (decreased
  22. // exponentially) based on the provided logarithmic expiration offset value.
  23. //
  24. // The formula for value calculation is: base*2^(exp-logOffset). In order to
  25. // simplify the calculation of ExpiredValue, its value is expressed in the form
  26. // of an exponent with a base of 2.
  27. //
  28. // Also here is a trick to reduce a lot of calculations. In theory, when a value X
  29. // decays over time and then a new value Y is added, the final result should be
  30. // X*2^(exp-logOffset)+Y. However it's very hard to represent in memory.
  31. // So the trick is using the idea of inflation instead of exponential decay. At this
  32. // moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential
  33. // decay when we actually want to calculate the value.
  34. //
  35. // e.g.
  36. // t0: V = 100
  37. // t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient
  38. // t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40
  39. type ExpiredValue struct {
  40. Base, Exp uint64 // rlp encoding works by default
  41. }
  42. // ExpirationFactor is calculated from logOffset. 1 <= Factor < 2 and Factor*2^Exp
  43. // describes the multiplier applicable for additions and the divider for readouts.
  44. // If logOffset changes slowly then it saves some expensive operations to not calculate
  45. // them for each addition and readout but cache this intermediate form for some time.
  46. // It is also useful for structures where multiple values are expired with the same
  47. // Expirer.
  48. type ExpirationFactor struct {
  49. Exp uint64
  50. Factor float64
  51. }
  52. // ExpFactor calculates ExpirationFactor based on logOffset
  53. func ExpFactor(logOffset Fixed64) ExpirationFactor {
  54. return ExpirationFactor{Exp: logOffset.ToUint64(), Factor: logOffset.Fraction().Pow2()}
  55. }
  56. // Value calculates the expired value based on a floating point base and integer
  57. // power-of-2 exponent. This function should be used by multi-value expired structures.
  58. func (e ExpirationFactor) Value(base float64, exp uint64) float64 {
  59. return base / e.Factor * math.Pow(2, float64(int64(exp-e.Exp)))
  60. }
  61. // value calculates the value at the given moment.
  62. func (e ExpiredValue) Value(logOffset Fixed64) uint64 {
  63. offset := Uint64ToFixed64(e.Exp) - logOffset
  64. return uint64(float64(e.Base) * offset.Pow2())
  65. }
  66. // add adds a signed value at the given moment
  67. func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 {
  68. integer, frac := logOffset.ToUint64(), logOffset.Fraction()
  69. factor := frac.Pow2()
  70. base := factor * float64(amount)
  71. if integer < e.Exp {
  72. base /= math.Pow(2, float64(e.Exp-integer))
  73. }
  74. if integer > e.Exp {
  75. e.Base >>= (integer - e.Exp)
  76. e.Exp = integer
  77. }
  78. if base >= 0 || uint64(-base) <= e.Base {
  79. // This is a temporary fix to circumvent a golang
  80. // uint conversion issue on arm64, which needs to
  81. // be investigated further. FIXME
  82. e.Base = uint64(int64(e.Base) + int64(base))
  83. return amount
  84. }
  85. net := int64(-float64(e.Base) / factor)
  86. e.Base = 0
  87. return net
  88. }
  89. // addExp adds another ExpiredValue
  90. func (e *ExpiredValue) AddExp(a ExpiredValue) {
  91. if e.Exp > a.Exp {
  92. a.Base >>= (e.Exp - a.Exp)
  93. }
  94. if e.Exp < a.Exp {
  95. e.Base >>= (a.Exp - e.Exp)
  96. e.Exp = a.Exp
  97. }
  98. e.Base += a.Base
  99. }
  100. // subExp subtracts another ExpiredValue
  101. func (e *ExpiredValue) SubExp(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. if e.Base > a.Base {
  110. e.Base -= a.Base
  111. } else {
  112. e.Base = 0
  113. }
  114. }
  115. // Expirer changes logOffset with a linear rate which can be changed during operation.
  116. // It is not thread safe, if access by multiple goroutines is needed then it should be
  117. // encapsulated into a locked structure.
  118. // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset
  119. // is thread safe.
  120. type Expirer struct {
  121. logOffset Fixed64
  122. rate float64
  123. lastUpdate mclock.AbsTime
  124. }
  125. // SetRate changes the expiration rate which is the inverse of the time constant in
  126. // nanoseconds.
  127. func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) {
  128. dt := now - e.lastUpdate
  129. if dt > 0 {
  130. e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate)
  131. }
  132. e.lastUpdate = now
  133. e.rate = rate
  134. }
  135. // SetLogOffset sets logOffset instantly.
  136. func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) {
  137. e.lastUpdate = now
  138. e.logOffset = logOffset
  139. }
  140. // LogOffset returns the current logarithmic offset.
  141. func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 {
  142. dt := now - e.lastUpdate
  143. if dt <= 0 {
  144. return e.logOffset
  145. }
  146. return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate)
  147. }
  148. // fixedFactor is the fixed point multiplier factor used by Fixed64.
  149. const fixedFactor = 0x1000000
  150. // Fixed64 implements 64-bit fixed point arithmetic functions.
  151. type Fixed64 int64
  152. // Uint64ToFixed64 converts uint64 integer to Fixed64 format.
  153. func Uint64ToFixed64(f uint64) Fixed64 {
  154. return Fixed64(f * fixedFactor)
  155. }
  156. // float64ToFixed64 converts float64 to Fixed64 format.
  157. func Float64ToFixed64(f float64) Fixed64 {
  158. return Fixed64(f * fixedFactor)
  159. }
  160. // toUint64 converts Fixed64 format to uint64.
  161. func (f64 Fixed64) ToUint64() uint64 {
  162. return uint64(f64) / fixedFactor
  163. }
  164. // fraction returns the fractional part of a Fixed64 value.
  165. func (f64 Fixed64) Fraction() Fixed64 {
  166. return f64 % fixedFactor
  167. }
  168. var (
  169. logToFixedFactor = float64(fixedFactor) / math.Log(2)
  170. fixedToLogFactor = math.Log(2) / float64(fixedFactor)
  171. )
  172. // pow2Fixed returns the base 2 power of the fixed point value.
  173. func (f64 Fixed64) Pow2() float64 {
  174. return math.Exp(float64(f64) * fixedToLogFactor)
  175. }