examples/performance/tail_recursion_benchmark.py
#!/usr/bin/env python3
"""
Tail Recursion Performance Benchmark
Compares:
1. Tail-recursive function with automatic transformation
2. Manual while loop equivalent
3. Python native iterative version
Shows that Catnip's automatic tail recursion → loop transformation
achieves near-native loop performance.
"""
import time
import os
from catnip import Catnip
# Force AST mode for consistent benchmarking
os.environ["CATNIP_EXECUTOR"] = "ast"
def benchmark(name, cat_instance, iterations=100):
"""Run benchmark and return average time."""
# Warm up
for _ in range(3):
cat_instance.execute()
# Benchmark
start = time.perf_counter()
for _ in range(iterations):
result = cat_instance.execute()
end = time.perf_counter()
avg_time = (end - start) / iterations * 1000
return avg_time, result
def main():
print("=" * 70)
print("Tail Recursion Performance Benchmark")
print("=" * 70)
# Test value
N = 1000
# 1. Tail-recursive with automatic transformation
print(f"\n1. Tail-recursive factorial({N}) - AUTO TRANSFORMATION")
print("-" * 70)
code_auto = f"""
factorial = (n, acc=1) => {{
if n <= 1 {{ acc }}
else {{ factorial(n - 1, n * acc) }}
}}
factorial({N})
"""
cat_auto = Catnip()
cat_auto.parse(code_auto, semantic=True)
time_auto, result_auto = benchmark("Auto transform", cat_auto)
print(f"Average time: {time_auto:.2f}ms")
print(f"Result: {len(str(result_auto))} digits")
# 2. Manual while loop
print(f"\n2. Manual while loop factorial({N})")
print("-" * 70)
code_manual = f"""
factorial_manual = (n, acc=1) => {{
while True {{
if n <= 1 {{ return acc }}
tmp_n = n - 1
tmp_acc = n * acc
n = tmp_n
acc = tmp_acc
}}
}}
factorial_manual({N})
"""
cat_manual = Catnip()
cat_manual.parse(code_manual, semantic=True)
time_manual, result_manual = benchmark("Manual loop", cat_manual)
print(f"Average time: {time_manual:.2f}ms")
print(f"Result: {len(str(result_manual))} digits")
# 3. Python native iterative
print(f"\n3. Python native iterative factorial({N})")
print("-" * 70)
def python_factorial(n):
acc = 1
while n > 1:
acc *= n
n -= 1
return acc
# Warm up
for _ in range(3):
python_factorial(N)
start = time.perf_counter()
for _ in range(100):
result_python = python_factorial(N)
end = time.perf_counter()
time_python = (end - start) / 100 * 1000
print(f"Average time: {time_python:.2f}ms")
print(f"Result: {len(str(result_python))} digits")
# Summary
print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
# Verify correctness
assert result_auto == result_manual == result_python, "Results don't match!"
print(f"✓ All implementations return the same result")
# Performance comparison
print(f"\nPerformance relative to manual while loop:")
print(f" Auto transform: {time_auto:.2f}ms ({time_auto/time_manual:.2f}x)")
print(f" Manual loop: {time_manual:.2f}ms (1.00x baseline)")
print(f" Python native: {time_python:.2f}ms ({time_python/time_manual:.2f}x)")
# Speedup over trampoline (historical data: ~3ms for N=1000)
trampoline_time = 3.0
print(f"\nSpeedup over trampoline runtime (~{trampoline_time}ms):")
print(f" Auto transform: {trampoline_time/time_auto:.2f}x faster")
# Analysis
print("\n" + "=" * 70)
print("ANALYSIS")
print("=" * 70)
overhead_pct = (time_auto - time_manual) / time_manual * 100
if abs(overhead_pct) < 20:
print(f"✓ Auto transformation overhead: {overhead_pct:.1f}%")
print(" Performance is comparable to manual while loop!")
else:
print(f"⚠ Auto transformation overhead: {overhead_pct:.1f}%")
print(f"\nBenefits of automatic transformation:")
print(f" - No manual conversion needed")
print(f" - O(1) stack space (no stack overflow)")
print(f" - Near-native loop performance")
print(f" - Works transparently with existing code")
if __name__ == "__main__":
main()