Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bench/alloy-bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "eth_comparison"
harness = false

[[bench]]
name = "u256_comparison"
harness = false
6 changes: 4 additions & 2 deletions bench/alloy-bench/benches/eth_comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ fn bench_u256(c: &mut Criterion) {
// mulDiv: (a * b) / c with full 512-bit intermediate (FullMath.mulDiv)
group.bench_function("mulDiv", |b| {
let liquidity = ONE_ETH;
let sqrt_price = U256::from_limbs([0, 79228162514264337593543950336u128 as u64, (79228162514264337593543950336u128 >> 64) as u64, 0]);
// Q96 = 2^96 = 79228162514264337593543950336
let sqrt_price = U256::from(79228162514264337593543950336u128);
let denom = ONE_ETH + U256::from(1_000_000u64);
b.iter(|| {
// True 512-bit intermediate: widen to U512, multiply, divide, narrow back
Expand All @@ -319,7 +320,8 @@ fn bench_u256(c: &mut Criterion) {
// that real swaps hit for typical pool parameters.
group.bench_function("uniswap_v4_swap", |b| {
let liquidity = ONE_ETH;
let sqrt_price = U256::from_limbs([0, 79228162514264337593543950336u128 as u64, (79228162514264337593543950336u128 >> 64) as u64, 0]);
// Q96 = 2^96 = 79228162514264337593543950336
let sqrt_price = U256::from(79228162514264337593543950336u128);
let amount_in = U256::from(1_000_000_000_000_000u64);

b.iter(|| {
Expand Down
148 changes: 148 additions & 0 deletions bench/alloy-bench/benches/u256_comparison.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/// u256-only benchmark: eth.zig vs alloy.rs (ruint)
///
/// All test values match bench/u256_bench.zig exactly.
/// alloy's U256 is ruint::Uint<256, 4> -- native [u64; 4] limb arithmetic.

use alloy_primitives::{U256, Uint};
use criterion::{black_box, criterion_group, criterion_main, Criterion};

type U512 = Uint<512, 8>;

// ================================================================
// Test values -- identical to u256_bench.zig
// ================================================================

const ONE_ETH: U256 = U256::from_limbs([1_000_000_000_000_000_000u64, 0, 0, 0]);

// 100 ETH = 100_000_000_000_000_000_000 = 0x56BC75E2D63100000
const RESERVE_IN: U256 = U256::from_limbs([0x6BC75E2D63100000, 5, 0, 0]);
const RESERVE_OUT: U256 = U256::from_limbs([200_000_000_000u64, 0, 0, 0]);

// 2^96 = 79228162514264337593543950336
const SQRT_PRICE: U256 = U256::from_limbs([0, 0x100000000, 0, 0]);
const AMOUNT_IN_SMALL: U256 = U256::from_limbs([1_000_000_000_000_000u64, 0, 0, 0]);

// Full-width 256-bit values
const FULL_A: U256 = U256::from_limbs([
0x12345678_9ABCDEF0,
0xDEADBEEF_CAFEBABE,
0x12345678_9ABCDEF0,
0xDEADBEEF_CAFEBABE,
]);
const FULL_B: U256 = U256::from_limbs([
0xDEADBEEF_CAFEBABE,
0x12345678_9ABCDEF0,
0xDEADBEEF_CAFEBABE,
0x12345678_9ABCDEF0,
]);
const FULL_C: U256 = U256::from_limbs([
0x00000000_00000001,
0x00000000_00000000,
0x00000000_00000000,
0x00000001_00000000,
]);

// ================================================================
// Benchmarks
// ================================================================

fn bench_u256(c: &mut Criterion) {
let mut group = c.benchmark_group("u256");

// --- Primitives ---

group.bench_function("add", |b| {
let a = ONE_ETH;
let b_val = U256::from(997_000_000_000_000_000u64);
b.iter(|| {
let result = black_box(a).wrapping_add(black_box(b_val));
black_box(result);
})
});

group.bench_function("mul_small", |b| {
let a = ONE_ETH;
b.iter(|| {
let result = black_box(a).wrapping_mul(U256::from(997u64));
black_box(result);
})
});

group.bench_function("mul_full", |b| {
b.iter(|| {
let result = black_box(FULL_A).wrapping_mul(black_box(FULL_B));
black_box(result);
})
});

group.bench_function("div_small", |b| {
let large = U256::from(997_000_000_000_000_000_000u128);
b.iter(|| {
let result = black_box(large).checked_div(black_box(ONE_ETH));
black_box(result);
})
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

group.bench_function("div_full", |b| {
b.iter(|| {
let result = black_box(FULL_A).checked_div(black_box(FULL_C));
black_box(result);
})
});

// --- UniswapV2 getAmountOut (naive: step-by-step u256 arithmetic) ---
// Both Zig and Rust do the exact same formula with wrapping u256 ops.
// This is the fair apples-to-apples comparison.

group.bench_function("uniswapv2_naive", |b| {
let amount_in = ONE_ETH;
let reserve_in = RESERVE_IN;
let reserve_out = RESERVE_OUT;
b.iter(|| {
let amount_in_with_fee = black_box(amount_in).wrapping_mul(U256::from(997u64));
let numerator = amount_in_with_fee.wrapping_mul(black_box(reserve_out));
let denominator =
black_box(reserve_in).wrapping_mul(U256::from(1000u64)).wrapping_add(amount_in_with_fee);
let amount_out = numerator / denominator;
black_box(amount_out);
})
});

// --- mulDiv: (a * b) / c with true 512-bit intermediate ---

group.bench_function("mulDiv", |b| {
let liquidity = ONE_ETH;
let sqrt_price = SQRT_PRICE;
let denom = ONE_ETH.wrapping_add(U256::from(1_000_000u64));
b.iter(|| {
let a = U512::from(black_box(liquidity));
let b_val = U512::from(black_box(sqrt_price));
let d = U512::from(black_box(denom));
let result = U256::from((a * b_val) / d);
black_box(result);
})
});

// --- UniswapV4 getNextSqrtPriceFromAmount0RoundingUp ---
// product = amount_in * sqrt_price (u256, no overflow for these values)
// denominator = liquidity + product
// next_sqrt_price = (liquidity * sqrt_price) / denominator (via U512)

group.bench_function("uniswapv4_swap", |b| {
let liquidity = ONE_ETH;
let sqrt_price = SQRT_PRICE;
let amount_in = AMOUNT_IN_SMALL;
b.iter(|| {
let product = black_box(amount_in).wrapping_mul(black_box(sqrt_price));
let denominator = black_box(liquidity).wrapping_add(product);
let num = U512::from(black_box(liquidity)) * U512::from(black_box(sqrt_price));
let next_sqrt_price = U256::from(num / U512::from(denominator));
black_box(next_sqrt_price);
})
});

group.finish();
}

criterion_group!(benches, bench_u256);
criterion_main!(benches);
155 changes: 155 additions & 0 deletions bench/compare_u256.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
ALLOY_DIR="$SCRIPT_DIR/alloy-bench"

command -v zig >/dev/null 2>&1 || { echo "ERROR: zig not found"; exit 1; }
command -v cargo >/dev/null 2>&1 || { echo "ERROR: cargo not found"; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "ERROR: python3 not found"; exit 1; }

echo ""
echo "================================================================"
echo " u256 Benchmark: eth.zig vs alloy.rs (ruint)"
echo "================================================================"
echo ""

# -- Step 1: eth-zig u256 benchmarks --
echo "[1/3] Running eth-zig u256 benchmarks (ReleaseFast)..."
ZIG_OUTPUT=$(cd "$ROOT_DIR" && zig build bench-u256 2>&1)
echo "$ZIG_OUTPUT" | grep -v "^BENCH_JSON"
echo ""

# -- Step 2: alloy.rs u256 benchmarks --
echo "[2/3] Running alloy.rs u256 benchmarks (cargo bench --release)..."
RUST_OUTPUT=$(cd "$ALLOY_DIR" && cargo bench --bench u256_comparison 2>&1)
echo " Done."
echo ""

# -- Step 3: Compare --
echo "[3/3] Comparing results..."
echo ""

python3 - "$ZIG_OUTPUT" "$RUST_OUTPUT" << 'PYTHON_SCRIPT'
Comment thread
koko1123 marked this conversation as resolved.
import sys
import json
import re

zig_output = sys.argv[1]
rust_output = sys.argv[2]

def parse_ns(value_str, unit_str):
v = float(value_str)
if unit_str == 'ns':
return round(v)
elif unit_str in ('us', 'µs'):
return round(v * 1000)
elif unit_str == 'ms':
return round(v * 1_000_000)
return round(v)

# Parse BENCH_JSON lines from Zig output
zig_ns = {}
for line in zig_output.split('\n'):
if line.startswith('BENCH_JSON|'):
try:
data = json.loads(line[len('BENCH_JSON|'):])
zig_ns[data['name']] = data['ns_per_op']
except (json.JSONDecodeError, KeyError):
pass

# Parse criterion output
alloy_ns = {}
for line in rust_output.split('\n'):
m = re.match(r'^([a-zA-Z0-9_]+/[a-zA-Z0-9_]+)\s+time:\s+\[[\d.]+ \w+\s+([\d.]+)\s+(ns|µs|ms)', line.strip())
if m:
alloy_ns[m.group(1)] = parse_ns(m.group(2), m.group(3))

# Name mapping: zig -> criterion
name_map = {
'u256_add': 'u256/add',
'u256_mul_small': 'u256/mul_small',
'u256_mul_full': 'u256/mul_full',
'u256_div_small': 'u256/div_small',
'u256_div_full': 'u256/div_full',
'u256_uniswapv2_naive': 'u256/uniswapv2_naive',
'u256_mulDiv': 'u256/mulDiv',
'u256_uniswapv4_swap': 'u256/uniswapv4_swap',
}

bench_order = [
'u256_add',
'u256_mul_small',
'u256_mul_full',
'u256_div_small',
'u256_div_full',
'u256_uniswapv2_naive',
'u256_mulDiv',
'u256_uniswapv4_swap',
]

GREEN = '\033[0;32m'
RED = '\033[0;31m'
YELLOW = '\033[0;33m'
BOLD = '\033[1m'
NC = '\033[0m'

print(f"\n{BOLD}=== Apples-to-apples: same formula, same u256 ops ==={NC}\n")
print(f"{BOLD}{'Benchmark':<28} {'eth-zig':>10} {'alloy.rs':>10} {'Result':>20}{NC}")
print(f"{'-'*28} {'-'*10} {'-'*10} {'-'*20}")

zig_wins = 0
alloy_wins = 0
ties = 0
total = 0

for zig_name in bench_order:
alloy_name = name_map.get(zig_name, '')
z = zig_ns.get(zig_name)
a = alloy_ns.get(alloy_name)

if z is not None and a is not None:
total += 1
if z == a or (z > 0 and a > 0 and abs(z - a) / max(z, a) < 0.1):
label = 'tie'
color = NC
ties += 1
elif z < a:
ratio = a / z if z > 0 else 999.99
label = f'zig {ratio:.2f}x'
color = GREEN
zig_wins += 1
else:
ratio = z / a if a > 0 else 999.99
label = f'rs {ratio:.2f}x'
color = RED
alloy_wins += 1
print(f"{zig_name:<28} {z:>7} ns {a:>7} ns {color}{label:>20}{NC}")
elif z is not None:
print(f"{zig_name:<28} {z:>7} ns {'---':>10} {'(zig only)':>20}")

print(f"\n{BOLD}{'='*28} {'='*10} {'='*10} {'='*20}{NC}")
print(f"\n{BOLD}Score: eth-zig {zig_wins}/{total} | alloy.rs {alloy_wins}/{total} | tied {ties}/{total}{NC}")

# Show zig-only optimized benchmark
z_opt = zig_ns.get('u256_uniswapv2_optimized')
z_naive = zig_ns.get('u256_uniswapv2_naive')
a_naive = alloy_ns.get('u256/uniswapv2_naive')

if z_opt is not None:
print(f"\n{BOLD}=== eth.zig compound limb optimization ==={NC}\n")
print(f"{'u256_uniswapv2_optimized':<28} {z_opt:>7} ns (stays in [4]u64 limb space)")
if z_naive is not None and z_naive > 0:
print(f"{'u256_uniswapv2_naive':<28} {z_naive:>7} ns (step-by-step u256, same as alloy)")
speedup = z_naive / z_opt if z_opt > 0 else 0
print(f"{'Optimization speedup':<28} {YELLOW}{speedup:.2f}x{NC}")
if a_naive is not None and z_opt > 0:
vs_rust = a_naive / z_opt
if vs_rust >= 1:
print(f"{'vs alloy.rs naive':<28} {GREEN}{vs_rust:.2f}x faster{NC}")
else:
print(f"{'vs alloy.rs naive':<28} {RED}{1/vs_rust:.2f}x slower{NC}")

print()
PYTHON_SCRIPT
Loading