Skip to content

Commit

Permalink
optimize gas estimation performance (#2879)
Browse files Browse the repository at this point in the history
* add commit mutex

* add pgu threshold

* add regression of hgu bounding

* fix ut

* pack one tx at least

* simulation gas wanted must less than gas limit

* persist estimated gas and refactor pgu

* record hgu block num

* add pgu logger

* fix bug

* add ut

* fix ut

* update

* fix bug of simulation

* temp

* update

* merge dev

* pgu twice

---------

Co-authored-by: KamiD <[email protected]>
  • Loading branch information
yann-sjtu and KamiD authored Jul 20, 2023
1 parent 39c86b1 commit c2fa4a0
Show file tree
Hide file tree
Showing 14 changed files with 592 additions and 91 deletions.
54 changes: 54 additions & 0 deletions app/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ type OecConfig struct {
maxGasUsedPerBlock int64
// mempool.enable-pgu
enablePGU bool
// mempool.pgu-percentage-threshold
pguPercentageThreshold int64
// mempool.pgu-concurrency
pguConcurrency int
// mempool.pgu-adjustment
pguAdjustment float64
// mempool.pgu-persist
pguPersist bool
// mempool.node_key_whitelist
nodeKeyWhitelist []string
//mempool.check_tx_cost
Expand Down Expand Up @@ -141,7 +147,10 @@ const (
FlagMaxTxNumPerBlock = "mempool.max_tx_num_per_block"
FlagMaxGasUsedPerBlock = "mempool.max_gas_used_per_block"
FlagEnablePGU = "mempool.enable-pgu"
FlagPGUPercentageThreshold = "mempool.pgu-percentage-threshold"
FlagPGUConcurrency = "mempool.pgu-concurrency"
FlagPGUAdjustment = "mempool.pgu-adjustment"
FlagPGUPersist = "mempool.pgu-persist"
FlagNodeKeyWhitelist = "mempool.node_key_whitelist"
FlagMempoolCheckTxCost = "mempool.check_tx_cost"
FlagMempoolEnableDeleteMinGPTx = "mempool.enable_delete_min_gp_tx"
Expand Down Expand Up @@ -287,7 +296,10 @@ func (c *OecConfig) loadFromConfig() {
c.SetPendingPoolBlacklist(viper.GetString(FlagPendingPoolBlacklist))
c.SetMaxGasUsedPerBlock(viper.GetInt64(FlagMaxGasUsedPerBlock))
c.SetEnablePGU(viper.GetBool(FlagEnablePGU))
c.SetPGUPercentageThreshold(viper.GetInt64(FlagPGUPercentageThreshold))
c.SetPGUConcurrency(viper.GetInt(FlagPGUConcurrency))
c.SetPGUAdjustment(viper.GetFloat64(FlagPGUAdjustment))
c.SetPGUPersist(viper.GetBool(FlagPGUPersist))
c.SetGasLimitBuffer(viper.GetUint64(FlagGasLimitBuffer))

c.SetEnableDynamicGp(viper.GetBool(FlagEnableDynamicGp))
Expand Down Expand Up @@ -504,12 +516,30 @@ func (c *OecConfig) updateFromKVStr(k, v string) {
return
}
c.SetEnablePGU(r)
case FlagPGUPercentageThreshold:
r, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return
}
c.SetPGUPercentageThreshold(r)
case FlagPGUConcurrency:
r, err := strconv.Atoi(v)
if err != nil {
return
}
c.SetPGUConcurrency(r)
case FlagPGUAdjustment:
r, err := strconv.ParseFloat(v, 64)
if err != nil {
return
}
c.SetPGUAdjustment(r)
case FlagPGUPersist:
r, err := strconv.ParseBool(v)
if err != nil {
return
}
c.SetPGUPersist(r)
case FlagGasLimitBuffer:
r, err := strconv.ParseUint(v, 10, 64)
if err != nil {
Expand Down Expand Up @@ -834,6 +864,22 @@ func (c *OecConfig) SetEnablePGU(value bool) {
c.enablePGU = value
}

func (c *OecConfig) GetPGUPercentageThreshold() int64 {
return c.pguPercentageThreshold
}

func (c *OecConfig) SetPGUPercentageThreshold(value int64) {
c.pguPercentageThreshold = value
}

func (c *OecConfig) GetPGUConcurrency() int {
return c.pguConcurrency
}

func (c *OecConfig) SetPGUConcurrency(value int) {
c.pguConcurrency = value
}

func (c *OecConfig) GetPGUAdjustment() float64 {
return c.pguAdjustment
}
Expand All @@ -842,6 +888,14 @@ func (c *OecConfig) SetPGUAdjustment(value float64) {
c.pguAdjustment = value
}

func (c *OecConfig) GetPGUPersist() bool {
return c.pguPersist
}

func (c *OecConfig) SetPGUPersist(value bool) {
c.pguPersist = value
}

func (c *OecConfig) GetGasLimitBuffer() uint64 {
return c.gasLimitBuffer
}
Expand Down
27 changes: 22 additions & 5 deletions libs/cosmos-sdk/baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -1019,25 +1019,42 @@ func (app *BaseApp) GetRealTxFromRawTx(rawTx tmtypes.Tx) abci.TxEssentials {
return nil
}

func (app *BaseApp) GetTxHistoryGasUsed(rawTx tmtypes.Tx) int64 {
func (app *BaseApp) GetTxHistoryGasUsed(rawTx tmtypes.Tx, gasLimit int64) (int64, bool) {
tx, err := app.txDecoder(rawTx)
if err != nil {
return -1
return -1, false
}

txFnSig, toDeployContractSize := tx.GetTxFnSignatureInfo()
if txFnSig == nil {
return -1
return -1, false
}

hgu := InstanceOfHistoryGasUsedRecordDB().GetHgu(txFnSig)
if hgu == nil {
return -1, false
}
precise := true
if hgu.BlockNum < preciseBlockNum ||
(hgu.MaxGas-hgu.MovingAverageGas)*100/hgu.MovingAverageGas > cfg.DynamicConfig.GetPGUPercentageThreshold() ||
(hgu.MovingAverageGas-hgu.MinGas)*100/hgu.MinGas > cfg.DynamicConfig.GetPGUPercentageThreshold() {
precise = false
}

var gasWanted int64
if toDeployContractSize > 0 {
// if deploy contract case, the history gas used value is unit gas used
return hgu*int64(toDeployContractSize) + int64(1000)
gasWanted = hgu.MovingAverageGas*int64(toDeployContractSize) + int64(1000)
} else {
gasWanted = hgu.MovingAverageGas
}

// hgu gas can not be greater than gasLimit
if gasWanted > gasLimit {
gasWanted = gasLimit
}

return hgu
return gasWanted, precise
}

func (app *BaseApp) MsgServiceRouter() *MsgServiceRouter { return app.msgServiceRouter }
Expand Down
99 changes: 64 additions & 35 deletions libs/cosmos-sdk/baseapp/gasuseddb.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package baseapp

import (
"encoding/binary"
"log"
"path/filepath"
"sync"

"github.com/gogo/protobuf/proto"
lru "github.com/hashicorp/golang-lru"
"github.com/okex/exchain/libs/cosmos-sdk/client/flags"
sdk "github.com/okex/exchain/libs/cosmos-sdk/types"
Expand All @@ -17,13 +18,15 @@ const (
HistoryGasUsedDBName = "hgu"

FlagGasUsedFactor = "gu_factor"
preciseBlockNum = 20
)

var (
once sync.Once
GasUsedFactor = 0.4
jobQueueLen = 10
cacheSize = 10000
once sync.Once
GasUsedFactor = 0.4
regressionFactor = 0.05
jobQueueLen = 10
cacheSize = 10000

historyGasUsedRecordDB HistoryGasUsedRecordDB
)
Expand All @@ -35,7 +38,7 @@ type gasKey struct {

type HistoryGasUsedRecordDB struct {
latestGuMtx sync.Mutex
latestGu map[string]int64
latestGu map[string][]int64
cache *lru.Cache
guDB db.DB

Expand All @@ -46,7 +49,7 @@ func InstanceOfHistoryGasUsedRecordDB() *HistoryGasUsedRecordDB {
once.Do(func() {
cache, _ := lru.New(cacheSize)
historyGasUsedRecordDB = HistoryGasUsedRecordDB{
latestGu: make(map[string]int64),
latestGu: make(map[string][]int64),
cache: cache,
guDB: initDb(),
jobQueue: make(chan func(), jobQueueLen),
Expand All @@ -58,13 +61,13 @@ func InstanceOfHistoryGasUsedRecordDB() *HistoryGasUsedRecordDB {

func (h *HistoryGasUsedRecordDB) UpdateGasUsed(key []byte, gasUsed int64) {
h.latestGuMtx.Lock()
h.latestGu[string(key)] = gasUsed
h.latestGu[string(key)] = append(h.latestGu[string(key)], gasUsed)
h.latestGuMtx.Unlock()
}

func (h *HistoryGasUsedRecordDB) GetHgu(key []byte) int64 {
func (h *HistoryGasUsedRecordDB) GetHgu(key []byte) *HguRecord {
hgu, cacheHit := h.getHgu(key)
if !cacheHit && hgu != -1 {
if hgu != nil && !cacheHit {
// add to cache before returning hgu
h.cache.Add(string(key), hgu)
}
Expand All @@ -75,43 +78,71 @@ func (h *HistoryGasUsedRecordDB) FlushHgu() {
if len(h.latestGu) == 0 {
return
}
latestGasKeys := make([]gasKey, len(h.latestGu))
index := 0
for key, gas := range h.latestGu {
latestGasKeys[index] = gasKey{
gas: gas,
latestGasKeys := make([]gasKey, 0, len(h.latestGu))
for key, allGas := range h.latestGu {
latestGasKeys = append(latestGasKeys, gasKey{
gas: meanGas(allGas),
key: key,
}
index++
})
delete(h.latestGu, key)
}
h.jobQueue <- func() { h.flushHgu(latestGasKeys...) } // closure
}

func (h *HistoryGasUsedRecordDB) getHgu(key []byte) (hgu int64, fromCache bool) {
func (h *HistoryGasUsedRecordDB) getHgu(key []byte) (hgu *HguRecord, fromCache bool) {
v, ok := h.cache.Get(string(key))
if ok {
return v.(int64), true
return v.(*HguRecord), true
}

data, err := h.guDB.Get(key)
if err != nil || len(data) == 0 {
return -1, false
return nil, false
}

return bytesToInt64(data), false
var r HguRecord
err = proto.Unmarshal(data, &r)
if err != nil {
return nil, false
}
return &r, false
}

func (h *HistoryGasUsedRecordDB) flushHgu(gks ...gasKey) {
for _, gk := range gks {
hgu, cacheHit := h.getHgu([]byte(gk.key))
// avgGas = 0.4 * newGas + 0.6 * oldGas.The value of wasm store contract is too small and need to be rounded up.
avgGas := int64(GasUsedFactor*float64(gk.gas) + (1.0-GasUsedFactor)*float64(hgu) + 0.6)
// add to cache if hit
if cacheHit {
h.cache.Add(gk.key, avgGas)
if hgu == nil {
hgu = &HguRecord{
MaxGas: gk.gas,
MinGas: gk.gas,
MovingAverageGas: gk.gas,
}
} else {
// MovingAverageGas = 0.4 * newGas + 0.6 * oldMovingAverageGas
hgu.MovingAverageGas = int64(GasUsedFactor*float64(gk.gas) + (1.0-GasUsedFactor)*float64(hgu.MovingAverageGas))
// MaxGas = 0.05 * MovingAverageGas + 0.95 * oldMaxGas
hgu.MaxGas = int64(regressionFactor*float64(hgu.MovingAverageGas) + (1.0-regressionFactor)*float64(hgu.MaxGas))
// MinGas = 0.05 * MovingAverageGas + 0.95 * oldMinGas
hgu.MinGas = int64(regressionFactor*float64(hgu.MovingAverageGas) + (1.0-regressionFactor)*float64(hgu.MinGas))
hgu.BlockNum++
if gk.gas > hgu.MaxGas {
hgu.MaxGas = gk.gas
} else if gk.gas < hgu.MinGas {
hgu.MinGas = gk.gas
}
// add to cache if hit
if cacheHit {
h.cache.Add(gk.key, hgu)
}
}

data, err := proto.Marshal(hgu)
if err != nil {
log.Println("flushHgu marshal error:", err)
continue
}
h.guDB.Set([]byte(gk.key), int64ToBytes(avgGas))

h.guDB.Set([]byte(gk.key), data)
}
}

Expand All @@ -132,12 +163,10 @@ func initDb() db.DB {
return db
}

func int64ToBytes(i int64) []byte {
var buf = make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(i))
return buf
}

func bytesToInt64(buf []byte) int64 {
return int64(binary.BigEndian.Uint64(buf))
func meanGas(allGas []int64) int64 {
var totalGas int64
for _, gas := range allGas {
totalGas += gas
}
return totalGas / int64(len(allGas))
}
Loading

0 comments on commit c2fa4a0

Please sign in to comment.