Skip to content

Commit

Permalink
perf: improve EIP-3155 tracer (#2033)
Browse files Browse the repository at this point in the history
- Buffer stderr
- Allocate less
  • Loading branch information
DaniPopes authored Jan 28, 2025
1 parent 6e98049 commit 03f84f3
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 69 deletions.
7 changes: 3 additions & 4 deletions bins/revme/src/cmd/statetest/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ pub fn execute_test_suite(

let timer = Instant::now();
let res = ctx.inspect_commit_previous(
TracerEip3155::new(Box::new(stderr())).without_summary(),
TracerEip3155::buffered(stderr()).without_summary(),
);
*elapsed.lock().unwrap() += timer.elapsed();

Expand Down Expand Up @@ -497,9 +497,8 @@ pub fn execute_test_suite(
.with_tx(&tx)
.with_cfg(&cfg);

let _ = ctx.inspect_commit_previous(
TracerEip3155::new(Box::new(stderr())).without_summary(),
);
let _ = ctx
.inspect_commit_previous(TracerEip3155::buffered(stderr()).without_summary());

println!("\nExecution result: {exec_result:#?}");
println!("\nExpected exception: {:?}", test.expect_exception);
Expand Down
139 changes: 74 additions & 65 deletions crates/inspector/src/eip3155.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub struct TracerEip3155<CTX, INTR> {
// The CUT MUST output a `json` object for EACH operation.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Output {
struct Output<'a> {
// Required fields:
/// Program counter
pc: u64,
Expand All @@ -48,22 +48,26 @@ struct Output {
/// OpCode
op: u8,
/// Gas left before executing this operation
gas: String,
#[serde(serialize_with = "serde_hex_u64")]
gas: u64,
/// Gas cost of this operation
gas_cost: String,
#[serde(serialize_with = "serde_hex_u64")]
gas_cost: u64,
/// Array of all values on the stack
stack: Vec<String>,
stack: &'a [U256],
/// Depth of the call stack
depth: u64,
/// Depth of the EOF function call stack
#[serde(default, skip_serializing_if = "Option::is_none")]
function_depth: Option<u64>,
/// Data returned by the function call
return_data: String,
return_data: &'static str,
/// Amount of **global** gas refunded
refund: String,
#[serde(serialize_with = "serde_hex_u64")]
refund: u64,
/// Size of memory array
mem_size: String,
#[serde(serialize_with = "serde_hex_u64")]
mem_size: u64,

// Optional fields:
/// Name of the operation
Expand Down Expand Up @@ -93,7 +97,8 @@ struct Summary {
/// Return values of the function
output: String,
/// All gas used by the transaction
gas_used: String,
#[serde(serialize_with = "serde_hex_u64")]
gas_used: u64,
/// Bool whether transaction was executed successfully
pass: bool,

Expand All @@ -111,35 +116,13 @@ where
CTX: CfgGetter + TransactionGetter,
INTR:,
{
/// Sets the writer to use for the output.
pub fn set_writer(&mut self, writer: Box<dyn Write>) {
self.output = writer;
}

/// Resets the Tracer to its initial state of [Self::new].
/// This makes the inspector ready to be used again.
pub fn clear(&mut self) {
let Self {
gas_inspector,
stack,
pc,
opcode,
gas,
refunded,
mem_size,
skip,
..
} = self;
*gas_inspector = GasInspector::new();
stack.clear();
*pc = 0;
*opcode = 0;
*gas = 0;
*refunded = 0;
*mem_size = 0;
*skip = false;
/// Creates a new EIP-3155 tracer with the given output writer, by first wrapping it in a
/// [`BufWriter`](std::io::BufWriter).
pub fn buffered(output: impl Write + 'static) -> Self {
Self::new(Box::new(std::io::BufWriter::new(output)))
}

/// Creates a new EIP-3155 tracer with the given output writer.
pub fn new(output: Box<dyn Write>) -> Self {
Self {
output,
Expand All @@ -160,6 +143,11 @@ where
}
}

/// Sets the writer to use for the output.
pub fn set_writer(&mut self, writer: Box<dyn Write>) {
self.output = writer;
}

/// Don't include a summary at the end of the trace
pub fn without_summary(mut self) -> Self {
self.print_summary = false;
Expand All @@ -172,10 +160,29 @@ where
self
}

fn write_value(&mut self, value: &impl serde::Serialize) -> std::io::Result<()> {
serde_json::to_writer(&mut *self.output, value)?;
self.output.write_all(b"\n")?;
self.output.flush()
/// Resets the tracer to its initial state of [`Self::new`].
///
/// This makes the inspector ready to be used again.
pub fn clear(&mut self) {
let Self {
gas_inspector,
stack,
pc,
opcode,
gas,
refunded,
mem_size,
skip,
..
} = self;
*gas_inspector = GasInspector::new();
stack.clear();
*pc = 0;
*opcode = 0;
*gas = 0;
*refunded = 0;
*mem_size = 0;
*skip = false;
}

fn print_summary(&mut self, result: &InterpreterResult, context: &mut CTX) {
Expand All @@ -185,23 +192,27 @@ where
let value = Summary {
state_root: B256::ZERO.to_string(),
output: result.output.to_string(),
gas_used: hex_number(gas_limit - self.gas_inspector.gas_remaining()),
gas_used: gas_limit - self.gas_inspector.gas_remaining(),
pass: result.is_ok(),
time: None,
fork: Some(spec.to_string()),
};
let _ = self.write_value(&value);
}
}

fn write_value(&mut self, value: &impl serde::Serialize) -> std::io::Result<()> {
write_value(&mut *self.output, value)
}
}

pub trait CloneStack {
fn clone_from(&self) -> Vec<U256>;
fn clone_into(&self, stack: &mut Vec<U256>);
}

impl CloneStack for Stack {
fn clone_from(&self) -> Vec<U256> {
self.data().to_vec()
fn clone_into(&self, stack: &mut Vec<U256>) {
stack.extend_from_slice(self.data());
}
}

Expand All @@ -216,10 +227,11 @@ where

fn step(&mut self, interp: &mut Interpreter<INTR>, _: &mut CTX) {
self.gas_inspector.step(interp.control.gas());
self.stack = interp.stack.clone_from();
self.stack.clear();
interp.stack.clone_into(&mut self.stack);
self.memory = if self.include_memory {
Some(hex::encode_prefixed(
interp.memory.slice(0..usize::MAX).as_ref(),
interp.memory.slice(0..interp.memory.size()).as_ref(),
))
} else {
None
Expand Down Expand Up @@ -252,14 +264,14 @@ where
pc: self.pc,
section: self.section,
op: self.opcode,
gas: hex_number(self.gas),
gas_cost: hex_number(self.gas_inspector.last_gas_cost()),
stack: self.stack.iter().map(hex_number_u256).collect(),
gas: self.gas,
gas_cost: self.gas_inspector.last_gas_cost(),
stack: &self.stack,
depth: context.journal().depth() as u64,
function_depth: self.function_depth,
return_data: "0x".to_string(),
refund: hex_number(self.refunded as u64),
mem_size: self.mem_size.to_string(),
return_data: "0x",
refund: self.refunded as u64,
mem_size: self.mem_size as u64,

op_name: OpCode::new(self.opcode).map(|i| i.as_str()),
error: if !interp.control.instruction_result().is_ok() {
Expand All @@ -271,15 +283,15 @@ where
storage: None,
return_stack: None,
};
let _ = self.write_value(&value);
let _ = write_value(&mut self.output, &value);
}

fn call_end(&mut self, context: &mut CTX, _: &CallInputs, outcome: &mut CallOutcome) {
self.gas_inspector.call_end(outcome);

if context.journal().depth() == 0 {
self.print_summary(&outcome.result, context);
// Clear the state if we are at the top level
// Clear the state if we are at the top level.
self.clear();
}
}
Expand All @@ -289,23 +301,20 @@ where

if context.journal().depth() == 0 {
self.print_summary(&outcome.result, context);

// Clear the state if we are at the top level
// Clear the state if we are at the top level.
self.clear();
}
}
}

fn hex_number(uint: u64) -> String {
format!("0x{uint:x}")
fn write_value(
output: &mut dyn std::io::Write,
value: &impl serde::Serialize,
) -> std::io::Result<()> {
serde_json::to_writer(&mut *output, value)?;
output.write_all(b"\n")
}

fn hex_number_u256(b: &U256) -> String {
let s = hex::encode(b.to_be_bytes::<32>());
let s = s.trim_start_matches('0');
if s.is_empty() {
"0x0".to_string()
} else {
format!("0x{s}")
}
fn serde_hex_u64<S: serde::Serializer>(n: &u64, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&format!("{:#x}", *n))
}

0 comments on commit 03f84f3

Please sign in to comment.