From 346dd85ba378dd616dc00e5e9aa18085065fecb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Wed, 21 Aug 2024 11:55:06 +0300 Subject: [PATCH] Reapply "refactor: reduce number of heap allocations in tracing (#952)" (#959) This reverts commit 2ba5ea30584a72007ef9e8bd245f3cbd276c028a. --- core/types/l2trace.go | 6 ++-- core/vm/logger.go | 62 +++++++++++++++++++++------------------ eth/tracers/api_test.go | 10 +++---- go.mod | 2 +- go.sum | 4 +-- rollup/tracing/tracing.go | 4 ++- 6 files changed, 48 insertions(+), 40 deletions(-) diff --git a/core/types/l2trace.go b/core/types/l2trace.go index b5a1ebd8d0538..08e36f97892b6 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -70,7 +70,7 @@ type ExecutionResult struct { // currently they are just `from` and `to` account AccountsAfter []*AccountWrapper `json:"accountAfter"` - StructLogs []*StructLogRes `json:"structLogs"` + StructLogs []StructLogRes `json:"structLogs"` CallTrace json.RawMessage `json:"callTrace"` } @@ -91,8 +91,8 @@ type StructLogRes struct { // NewStructLogResBasic Basic StructLogRes skeleton, Stack&Memory&Storage&ExtraData are separated from it for GC optimization; // still need to fill in with Stack&Memory&Storage&ExtraData -func NewStructLogResBasic(pc uint64, op string, gas, gasCost uint64, depth int, refundCounter uint64, err error) *StructLogRes { - logRes := &StructLogRes{ +func NewStructLogResBasic(pc uint64, op string, gas, gasCost uint64, depth int, refundCounter uint64, err error) StructLogRes { + logRes := StructLogRes{ Pc: pc, Op: op, Gas: gas, diff --git a/core/vm/logger.go b/core/vm/logger.go index 740c7b93cc058..c5f02bd9151a2 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -23,6 +23,7 @@ import ( "io" "math/big" "strings" + "sync" "time" "github.com/holiman/uint256" @@ -79,8 +80,8 @@ type StructLog struct { Err error `json:"-"` } -func NewStructlog(pc uint64, op OpCode, gas, cost uint64, depth int, err error) *StructLog { - return &StructLog{ +func NewStructlog(pc uint64, op OpCode, gas, cost uint64, depth int, err error) StructLog { + return StructLog{ Pc: pc, Op: op, Gas: gas, @@ -90,14 +91,6 @@ func NewStructlog(pc uint64, op OpCode, gas, cost uint64, depth int, err error) } } -func (s *StructLog) clean() { - s.Memory.Reset() - s.Stack = s.Stack[:0] - s.ReturnData.Reset() - s.Storage = nil - s.Err = nil -} - // overrides for gencodec type structLogMarshaling struct { Gas math.HexOrDecimal64 @@ -158,10 +151,11 @@ type StructLogger struct { storage map[common.Address]Storage createdAccount *types.AccountWrapper - callStackLogInd []int - logs []*StructLog - output []byte - err error + callStackLogInd []int + pooledLogSliceHeader *[]StructLog + logs []StructLog + output []byte + err error } // NewStructLogger returns a new logger @@ -178,16 +172,26 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { return logger } -// Reset clears the data held by the logger. -func (l *StructLogger) Reset() { - l.bytecodes = make(map[common.Hash]CodeInfo) - l.storage = make(map[common.Address]Storage) - l.statesAffected = make(map[common.Address]struct{}) - l.output = make([]byte, 0) - l.logs = l.logs[:0] - l.callStackLogInd = nil - l.err = nil - l.createdAccount = nil +var logsPool = sync.Pool{ + New: func() any { + return new([]StructLog) + }, +} + +// InitLogsFromPool initiliazes logs buffer from a pool instead of unconditionally allocating +// a new buffer every time +func (l *StructLogger) InitLogsFromPool() { + l.pooledLogSliceHeader = logsPool.Get().(*[]StructLog) + l.logs = *l.pooledLogSliceHeader +} + +// ReleaseLogsToPool releases the logs back to the pool +// Keep in mind after calling this any copies of the internal StructLog buffer that you may +// have acquired by calling StructLogs() is invalid. Do not interact with it in any way. +func (l *StructLogger) ReleaseLogsToPool() { + *l.pooledLogSliceHeader = l.logs[:0] + l.logs = nil + logsPool.Put(l.pooledLogSliceHeader) } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -359,7 +363,7 @@ func (l *StructLogger) TracedBytecodes() map[common.Hash]CodeInfo { func (l *StructLogger) CreatedAccount() *types.AccountWrapper { return l.createdAccount } // StructLogs returns the captured log entries. -func (l *StructLogger) StructLogs() []*StructLog { return l.logs } +func (l *StructLogger) StructLogs() []StructLog { return l.logs } // Error returns the VM error captured by the trace. func (l *StructLogger) Error() error { return l.err } @@ -368,7 +372,7 @@ func (l *StructLogger) Error() error { return l.err } func (l *StructLogger) Output() []byte { return l.output } // WriteTrace writes a formatted trace to the given writer -func WriteTrace(writer io.Writer, logs []*StructLog) { +func WriteTrace(writer io.Writer, logs []StructLog) { for _, log := range logs { fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost) if log.Err != nil { @@ -488,9 +492,11 @@ func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Addre func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} // FormatLogs formats EVM returned structured logs for json output -func FormatLogs(logs []*StructLog) []*types.StructLogRes { - formatted := make([]*types.StructLogRes, 0, len(logs)) +func FormatLogs(logs []StructLog) []types.StructLogRes { + formatted := make([]types.StructLogRes, 0, len(logs)) + // note: make sure you don't retain a reference to any memory that originally belongs to `logs` as they might be pooled and + // reused for _, trace := range logs { logRes := types.NewStructLogResBasic(trace.Pc, trace.Op.String(), trace.Gas, trace.GasCost, trace.Depth, trace.RefundCounter, trace.Err) for _, stackValue := range trace.Stack { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index e9928434a981b..a020d8c3d43a3 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -227,7 +227,7 @@ func TestTraceCall(t *testing.T) { Gas: params.TxGas, Failed: false, ReturnValue: "", - StructLogs: []*types.StructLogRes{}, + StructLogs: []types.StructLogRes{}, }, }, // Standard JSON trace upon the head, plain transfer. @@ -245,7 +245,7 @@ func TestTraceCall(t *testing.T) { Gas: params.TxGas, Failed: false, ReturnValue: "", - StructLogs: []*types.StructLogRes{}, + StructLogs: []types.StructLogRes{}, }, }, // Standard JSON trace upon the non-existent block, error expects @@ -275,7 +275,7 @@ func TestTraceCall(t *testing.T) { Gas: params.TxGas, Failed: false, ReturnValue: "", - StructLogs: []*types.StructLogRes{}, + StructLogs: []types.StructLogRes{}, }, }, // Standard JSON trace upon the pending block @@ -293,7 +293,7 @@ func TestTraceCall(t *testing.T) { Gas: params.TxGas, Failed: false, ReturnValue: "", - StructLogs: []*types.StructLogRes{}, + StructLogs: []types.StructLogRes{}, }, }, } @@ -347,7 +347,7 @@ func TestTraceTransaction(t *testing.T) { Gas: params.TxGas, Failed: false, ReturnValue: "", - StructLogs: []*types.StructLogRes{}, + StructLogs: []types.StructLogRes{}, }) { t.Error("Transaction tracing result is different") } diff --git a/go.mod b/go.mod index 7fdfe3f6da9d7..d9b8b7c7aef94 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v1.7.0 github.com/scroll-tech/da-codec v0.1.1-0.20240718144756-1875fd490923 - github.com/scroll-tech/zktrie v0.8.4 + github.com/scroll-tech/zktrie v0.8.5-0.20240821080004-fe110072a0a1 github.com/shirou/gopsutil v3.21.11+incompatible github.com/sourcegraph/conc v0.3.0 github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 diff --git a/go.sum b/go.sum index d6162b1a5ec51..e922a8f500f3f 100644 --- a/go.sum +++ b/go.sum @@ -394,8 +394,8 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/scroll-tech/da-codec v0.1.1-0.20240718144756-1875fd490923 h1:A1ItzpnFDCHMh4g6cpeBZf7/fPf2lfwHbhjr/FSpk2w= github.com/scroll-tech/da-codec v0.1.1-0.20240718144756-1875fd490923/go.mod h1:D6XEESeNVJkQJlv3eK+FyR+ufPkgVQbJzERylQi53Bs= -github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE= -github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk= +github.com/scroll-tech/zktrie v0.8.5-0.20240821080004-fe110072a0a1 h1:1WofmDyxxynpzfWkguUp2qAW4Dxaa0nYOrQ1tdYPYZU= +github.com/scroll-tech/zktrie v0.8.5-0.20240821080004-fe110072a0a1/go.mod h1:F5gbEGfKRMpjdTfaAnUHD51wfCmLnoSYokiJJpUtfBw= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go index 3feea62ba3884..973e644181441 100644 --- a/rollup/tracing/tracing.go +++ b/rollup/tracing/tracing.go @@ -69,7 +69,7 @@ type TraceEnv struct { // The following Mutexes are used to protect against parallel read/write, // since txs are executed in parallel. pMu sync.Mutex // for `TraceEnv.StorageTrace.Proofs` - sMu sync.Mutex // for `TraceEnv.state`` + sMu sync.Mutex // for `TraceEnv.state` cMu sync.Mutex // for `TraceEnv.Codes` *types.StorageTrace @@ -325,6 +325,8 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B applyMessageStart := time.Now() structLogger := vm.NewStructLogger(env.logConfig) + structLogger.InitLogsFromPool() + defer structLogger.ReleaseLogsToPool() tracer := NewMuxTracer(structLogger, callTracer) // Run the transaction with tracing enabled. vmenv := vm.NewEVM(env.blockCtx, txContext, state, env.chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})