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