Performance Comparison: Python vs Rust for Fibonacci Calculation

2024-12-30

When it comes to computational performance, choosing the right programming language can make a significant difference. This post explores a practical comparison between Python and Rust implementations of the Fibonacci sequence calculator, complete with instrumentation for precise performance measurements.

graph TD; A[Start] --> B[End];

Project Structure

  graph TD;
    A[Project Root] --> B[Python Implementation];
    A --> C[Rust Implementation];
    A --> D[Profiling Script];
    B --> E[fib2.py];
    C --> F[fib2.rs];
    D --> G[profiler.sh];
    G --> H[performance_metrics.csv];

Performance Overview

  gantt
    title Performance Comparison: Python vs Rust (Fibonacci 40)
    dateFormat X
    axisFormat %s
section Python
7.47 seconds: 0, 7.47

section Rust
0.174 seconds: 0, 0.174

Code Implementation

Let's look at our baseline implementations. All code is available on Codeberg.

Python Implementation (fib2.py)

import time

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

start_time = time.time()
fibonacci(40)
end_time = time.time()
execution_time = end_time - start_time
print(execution_time)

Rust Implementation (fib2.rs)

use std::time::Instant;

fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

fn main() {
    let start = Instant::now();
    fibonacci(40);
    let duration = start.elapsed();
    println!("{:.4}", duration.as_secs_f64());
}

Profiling Script (profiler.sh)

#!/bin/bash

# File to store performance metrics
output_file="performance_metrics.csv"

# Create the CSV file with headers if it doesn't exist
if [ ! -f "$output_file" ]; then
    echo "Timestamp,Language,ExecutionTime" > "$output_file"
fi

# Number of iterations to run
iterations=10

# Function to run Python script and log execution time
run_python() {
    start_time=$(date +%Y-%m-%dT%H:%M:%S.%3N)
    execution_time=$(python3 fib2.py)
    echo "$start_time,Python,$execution_time" >> "$output_file"
}

# Function to run Rust script and log execution time
run_rust() {
    start_time=$(date +%Y-%m-%dT%H:%M:%S.%3N)
    execution_time=$(./fib2)
    echo "$start_time,Rust,$execution_time" >> "$output_file"
}

# Run Python and Rust scripts for specified iterations
for ((i=1; i<=iterations; i++))
do
    echo "Running iteration $i..."
    run_python
    run_rust
done

echo "Performance metrics saved to $output_file"

Results Analysis

  graph TD;
    subgraph Python[Python Execution Times]
    A[Run 1: 7.29s] --> B[Run 2: 7.38s];
    B --> C[Run 3: 7.34s];
    C --> D[Run 4: 7.41s];
    D --> E[Run 5: 7.33s];
    end
subgraph Rust[Rust Execution Times]
F[Run 1: 0.174s] --&gt; G[Run 2: 0.173s];
G --&gt; H[Run 3: 0.174s];
H --&gt; I[Run 4: 0.173s];
I --&gt; J[Run 5: 0.175s];
end

Key Findings

  graph LR;
    A[Performance Results] --> B[Python ~7.47s];
    A --> C[Rust ~0.174s];
    B --> D[Standard Library];
    C --> E[Optimized Binary];
    D --> F[CPU Bound];
    E --> G[43x Faster];

Performance Analysis Setup

The experiment uses a bash-based profiler to ensure consistent timing measurements across both languages. This centralized approach provides several benefits:

Implementation Details

Both implementations calculate the 40th Fibonacci number using recursive approaches. The profiler executes each implementation 10 times and records:

Raw performance data sample:

2024-12-30T06:11:39.250,Python,7.291918516159058
2024-12-30T06:11:46.559,Rust,0.1744
2024-12-30T06:11:46.736,Python,7.383427619934082
2024-12-30T06:11:54.137,Rust,0.1739

Optimization Opportunities

For Python:

For Rust:

Conclusion

This performance comparison demonstrates the significant advantages of Rust for computationally intensive tasks, while also highlighting the importance of proper instrumentation and measurement in performance analysis. Even with unoptimized recursive implementations, the performance difference is substantial.