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