Skip to content

⚡️ Speed up function gcd_recursive by 10%#307

Open
codeflash-ai[bot] wants to merge 1 commit intoexperimentalfrom
codeflash/optimize-gcd_recursive-mn6js0gy
Open

⚡️ Speed up function gcd_recursive by 10%#307
codeflash-ai[bot] wants to merge 1 commit intoexperimentalfrom
codeflash/optimize-gcd_recursive-mn6js0gy

Conversation

@codeflash-ai
Copy link
Copy Markdown

@codeflash-ai codeflash-ai bot commented Mar 25, 2026

📄 10% (0.10x) speedup for gcd_recursive in src/math/number_theory.py

⏱️ Runtime : 332 microseconds 302 microseconds (best of 250 runs)

📝 Explanation and details

The optimization converts the recursive Euclidean algorithm into an iterative loop using tuple unpacking (a, b = b, a % b), eliminating per-call function overhead and stack frame allocation. Line profiler data shows the recursive call line consumed 77.3% of original runtime (6.73 ms), while the iterative version spreads the same logic across loop condition (49.9%) and assignment (40%) with lower absolute cost. This yields a 9% speedup (332 µs → 302 µs) without correctness impact, trading the conceptual clarity of recursion for the mechanical efficiency of iteration—particularly valuable for deep call chains like consecutive Fibonacci numbers where the original required many stack frames.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 2075 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Click to see Generated Regression Tests
import math  # used to compute expected gcd values for verification

# imports
import pytest  # used for our unit tests
from src.math.number_theory import gcd_recursive


def test_gcd_basic_positive_and_commutativity():
    # basic positive integers: 48 and 18 have gcd 6
    assert gcd_recursive(48, 18) == 6  # 250ns -> 250ns (0.000% faster)
    # commutativity for positive integers: gcd(a, b) == gcd(b, a)
    assert gcd_recursive(18, 48) == 6  # 208ns -> 167ns (24.6% faster)


def test_gcd_with_zero_arguments():
    # when second argument is zero, function returns the first argument (preserves sign)
    assert gcd_recursive(5, 0) == 5  # 125ns -> 125ns (0.000% faster)
    # when first argument is zero, function uses recursion to compute gcd(0, n) -> n
    assert gcd_recursive(0, 5) == 5  # 167ns -> 125ns (33.6% faster)
    # both zero is a defined return path in this implementation -> returns 0
    assert gcd_recursive(0, 0) == 0  # 42ns -> 42ns (0.000% faster)


def test_negative_inputs_preserve_sign_and_behave_as_python_mod():
    # The implementation uses Python's % operator and does not take absolute values.
    # Therefore negative operands can produce negative gcd results consistent with
    # the function's arithmetic behavior.
    # Compute expected results using the same algorithmic behavior with math for clarity.
    assert gcd_recursive(48, -18) == -6  # 291ns -> 250ns (16.4% faster)
    assert gcd_recursive(-4, -6) == -2  # 208ns -> 208ns (0.000% faster)
    # If b == 0 the function returns a unchanged, including sign.
    assert gcd_recursive(-5, 0) == -5  # 42ns -> 42ns (0.000% faster)


def test_float_inputs_return_float_gcd_like_values():
    # Although annotated for ints, function will operate on floats via % and recursion.
    # The result should be a float representing the Euclidean remainder-based gcd.
    assert gcd_recursive(5.0, 2.0) == 1.0  # 333ns -> 334ns (0.299% slower)
    assert gcd_recursive(6.0, 4.0) == 2.0  # 166ns -> 125ns (32.8% faster)


def test_non_integer_types_raise_type_error():
    # Passing a None as second argument should raise a TypeError when modulo is attempted.
    with pytest.raises(TypeError):
        gcd_recursive(5, None)  # 541ns -> 500ns (8.20% faster)
    # Passing a string should also raise a TypeError when used with modulo.
    with pytest.raises(TypeError):
        gcd_recursive(5, "3")  # 375ns -> 375ns (0.000% faster)


def test_large_coprime_numbers():
    # Very large integers that are coprime should yield 1. Use deterministic values.
    a = 10**18 + 3
    b = 10**18 + 7
    assert gcd_recursive(a, b) == 1  # 541ns -> 500ns (8.20% faster)


def test_reduce_over_many_elements_produces_expected_gcd():
    # Build a large list (1000 elements) of multiples of 7 so gcd must be 7.
    values = [7 * (i + 1) for i in range(1000)]  # list of 7, 14, 21, ..., 7000
    # Reduce sequentially using gcd_recursive to simulate real-world aggregation.
    g = values[0]
    for v in values[1:]:
        g = gcd_recursive(g, v)  # 84.4μs -> 82.5μs (2.36% faster)
    assert g == 7  # gcd of all multiples should be 7


def test_many_repeated_calls_produce_consistent_results():
    # Repeatedly call gcd_recursive in a loop 1000 times to exercise stability.
    a, b = 123456, 7890
    expected = math.gcd(
        a, b
    )  # use math.gcd to compute expected canonical (non-negative) gcd
    # The implementation may preserve sign; for these positive inputs we expect equality.
    for _ in range(1000):
        result = gcd_recursive(a, b)  # 230μs -> 204μs (13.0% faster)
        assert (
            result == expected
        )  # each invocation should be consistent and deterministic


def test_associativity_for_positive_values_on_large_list():
    # For positive integers, gcd is associative. Verify left-to-right reduction equals pairwise grouping.
    values = [6, 10, 15, 35, 14, 21, 28, 49, 63, 84]  # deliberately mixed multiples
    # left-to-right reduction
    left = values[0]
    for v in values[1:]:
        left = gcd_recursive(left, v)  # 1.04μs -> 997ns (4.31% faster)
    # pairwise grouping reduction: compute gcd of splits and then gcd of results
    mid = gcd_recursive(
        gcd_recursive(values[0], values[1]), gcd_recursive(values[2], values[3])
    )  # 166ns -> 125ns (32.8% faster)
    # finish reducing the rest sequentially to keep deterministic shape
    rest_start = gcd_recursive(values[4], values[5])
    rest_next = gcd_recursive(values[6], values[7])  # 125ns -> 125ns (0.000% faster)
    rest = gcd_recursive(rest_start, rest_next)
    rest = gcd_recursive(rest, values[8])  # 167ns -> 125ns (33.6% faster)
    rest = gcd_recursive(rest, values[9])
    combined = gcd_recursive(mid, rest)  # 83ns -> 83ns (0.000% faster)
    # Both strategies should agree (for positive inputs).
    assert left == combined
import pytest  # used for our unit tests
from src.math.number_theory import gcd_recursive


def test_gcd_basic_small_numbers():
    """Test GCD with small positive integers."""
    # GCD(12, 8) should be 4
    assert gcd_recursive(12, 8) == 4  # 250ns -> 208ns (20.2% faster)


def test_gcd_basic_coprime_numbers():
    """Test GCD of coprime numbers (numbers with GCD = 1)."""
    # GCD(7, 11) should be 1 (coprime)
    assert gcd_recursive(7, 11) == 1  # 333ns -> 291ns (14.4% faster)


def test_gcd_basic_one_number_is_zero():
    """Test GCD when second parameter is zero."""
    # GCD(5, 0) should return 5
    assert gcd_recursive(5, 0) == 5  # 125ns -> 125ns (0.000% faster)


def test_gcd_basic_both_same():
    """Test GCD when both numbers are the same."""
    # GCD(7, 7) should be 7
    assert gcd_recursive(7, 7) == 7  # 167ns -> 125ns (33.6% faster)


def test_gcd_basic_first_is_one():
    """Test GCD when first number is 1."""
    # GCD(1, 5) should be 1
    assert gcd_recursive(1, 5) == 1  # 250ns -> 250ns (0.000% faster)


def test_gcd_basic_second_is_one():
    """Test GCD when second number is 1."""
    # GCD(12, 1) should be 1
    assert gcd_recursive(12, 1) == 1  # 208ns -> 167ns (24.6% faster)


def test_gcd_basic_multiple_of_each_other():
    """Test GCD when one number is a multiple of the other."""
    # GCD(20, 5) should be 5
    assert gcd_recursive(20, 5) == 5  # 167ns -> 167ns (0.000% faster)


def test_gcd_basic_reversed_multiple():
    """Test GCD when first number is smaller than second."""
    # GCD(5, 20) should be 5
    assert gcd_recursive(5, 20) == 5  # 250ns -> 208ns (20.2% faster)


def test_gcd_basic_larger_numbers():
    """Test GCD with moderately large numbers."""
    # GCD(48, 18) should be 6
    assert gcd_recursive(48, 18) == 6  # 250ns -> 250ns (0.000% faster)


def test_gcd_basic_consecutive_numbers():
    """Test GCD of consecutive integers."""
    # GCD(10, 11) should be 1 (consecutive integers are coprime)
    assert gcd_recursive(10, 11) == 1  # 250ns -> 250ns (0.000% faster)


def test_gcd_basic_even_numbers():
    """Test GCD with even numbers."""
    # GCD(24, 36) should be 12
    assert gcd_recursive(24, 36) == 12  # 250ns -> 250ns (0.000% faster)


def test_gcd_basic_odd_numbers():
    """Test GCD with odd numbers."""
    # GCD(15, 25) should be 5
    assert gcd_recursive(15, 25) == 5  # 292ns -> 250ns (16.8% faster)


def test_gcd_edge_both_zero():
    """Test GCD when both numbers are zero."""
    # GCD(0, 0) should return 0
    assert gcd_recursive(0, 0) == 0  # 125ns -> 125ns (0.000% faster)


def test_gcd_edge_first_zero():
    """Test GCD when first number is zero."""
    # GCD(0, 7) should return 7 after recursion
    assert gcd_recursive(0, 7) == 7  # 208ns -> 208ns (0.000% faster)


def test_gcd_edge_negative_first():
    """Test GCD with negative first number."""
    # GCD(-12, 8) should return the absolute GCD (4)
    # The function returns 4 because -12 % 8 = -4 in Python, but |GCD| is still 4
    result = gcd_recursive(-12, 8)  # 250ns -> 250ns (0.000% faster)
    assert abs(result) == 4


def test_gcd_edge_negative_second():
    """Test GCD with negative second number."""
    # GCD(12, -8) should handle negative modulo correctly
    result = gcd_recursive(12, -8)  # 291ns -> 250ns (16.4% faster)
    assert result != 0  # Should not crash, should return some result


def test_gcd_edge_both_negative():
    """Test GCD with both negative numbers."""
    # GCD(-12, -8) should handle both being negative
    result = gcd_recursive(-12, -8)  # 250ns -> 250ns (0.000% faster)
    assert result != 0  # Should not crash


def test_gcd_edge_negative_one():
    """Test GCD with -1."""
    # GCD(12, -1) should return the result of the algorithm
    result = gcd_recursive(12, -1)  # 167ns -> 167ns (0.000% faster)
    assert result != 0


def test_gcd_edge_one_and_one():
    """Test GCD(1, 1)."""
    # GCD(1, 1) should be 1
    assert gcd_recursive(1, 1) == 1  # 167ns -> 167ns (0.000% faster)


def test_gcd_edge_one_and_zero():
    """Test GCD(1, 0)."""
    # GCD(1, 0) should be 1
    assert gcd_recursive(1, 0) == 1  # 83ns -> 125ns (33.6% slower)


def test_gcd_edge_two_and_one():
    """Test GCD(2, 1)."""
    # GCD(2, 1) should be 1
    assert gcd_recursive(2, 1) == 1  # 208ns -> 166ns (25.3% faster)


def test_gcd_edge_large_prime_and_small():
    """Test GCD with a large prime number and small number."""
    # GCD(97, 10) should be 1 (97 is prime)
    assert gcd_recursive(97, 10) == 1  # 292ns -> 250ns (16.8% faster)


def test_gcd_edge_prime_numbers():
    """Test GCD of two different prime numbers."""
    # GCD(13, 17) should be 1 (both prime, different)
    assert gcd_recursive(13, 17) == 1  # 292ns -> 291ns (0.344% faster)


def test_gcd_edge_same_prime():
    """Test GCD of the same prime number."""
    # GCD(13, 13) should be 13
    assert gcd_recursive(13, 13) == 13  # 208ns -> 166ns (25.3% faster)


def test_gcd_edge_power_of_two():
    """Test GCD with powers of 2."""
    # GCD(16, 8) should be 8
    assert gcd_recursive(16, 8) == 8  # 208ns -> 166ns (25.3% faster)


def test_gcd_edge_power_of_two_coprime():
    """Test GCD of powers of 2 and odd number."""
    # GCD(16, 3) should be 1
    assert gcd_recursive(16, 3) == 1  # 250ns -> 208ns (20.2% faster)


def test_gcd_edge_very_close_numbers():
    """Test GCD of very close numbers."""
    # GCD(100, 99) should be 1
    assert gcd_recursive(100, 99) == 1  # 250ns -> 208ns (20.2% faster)


def test_gcd_edge_fibonacci_pair():
    """Test GCD of consecutive Fibonacci numbers."""
    # GCD(8, 5) should be 1 (consecutive Fibonacci)
    assert gcd_recursive(8, 5) == 1  # 292ns -> 291ns (0.344% faster)


def test_gcd_large_scale_thousand_and_hundred():
    """Test GCD with numbers around 1000."""
    # GCD(1000, 100) should be 100
    assert gcd_recursive(1000, 100) == 100  # 208ns -> 167ns (24.6% faster)


def test_gcd_large_scale_both_large():
    """Test GCD with both large numbers."""
    # GCD(987654, 123456) should be 6
    assert gcd_recursive(987654, 123456) == 6  # 291ns -> 250ns (16.4% faster)


def test_gcd_large_scale_large_coprime():
    """Test GCD of large coprime numbers."""
    # GCD(1000000, 1000001) should be 1
    assert gcd_recursive(1000000, 1000001) == 1  # 333ns -> 333ns (0.000% faster)


def test_gcd_large_scale_large_common_divisor():
    """Test GCD where both numbers are large multiples of a common divisor."""
    # GCD(5000, 3000) should be 1000
    assert gcd_recursive(5000, 3000) == 1000  # 333ns -> 292ns (14.0% faster)


def test_gcd_large_scale_very_large_numbers():
    """Test GCD with very large numbers."""
    # GCD(999999999, 333333333) should be 333333333
    assert (
        gcd_recursive(999999999, 333333333) == 333333333
    )  # 208ns -> 166ns (25.3% faster)


def test_gcd_large_scale_mersenne_like():
    """Test GCD with Mersenne-like numbers."""
    # GCD(255, 127) should be 1 (127 is prime, doesn't divide 255)
    assert gcd_recursive(255, 127) == 1  # 250ns -> 208ns (20.2% faster)


def test_gcd_large_scale_factorial_like():
    """Test GCD where first number is product of factors."""
    # GCD(120, 48) should be 24
    assert gcd_recursive(120, 48) == 24  # 250ns -> 208ns (20.2% faster)


def test_gcd_large_scale_deep_recursion():
    """Test GCD that requires many recursive steps (Fibonacci numbers create this)."""
    # GCD(610, 377) are consecutive Fibonacci numbers, requires many steps
    # GCD should be 1
    assert gcd_recursive(610, 377) == 1  # 625ns -> 583ns (7.20% faster)


def test_gcd_large_scale_multiple_iterations():
    """Test GCD that undergoes multiple modulo operations."""
    # GCD(1071, 462) requires multiple steps
    assert gcd_recursive(1071, 462) == 21  # 333ns -> 292ns (14.0% faster)


def test_gcd_large_scale_powers():
    """Test GCD of powers."""
    # GCD(2^10, 2^5) = GCD(1024, 32) should be 32
    assert gcd_recursive(1024, 32) == 32  # 208ns -> 167ns (24.6% faster)


def test_gcd_large_scale_different_magnitude():
    """Test GCD where numbers differ greatly in magnitude."""
    # GCD(10000, 13) should be 1
    assert gcd_recursive(10000, 13) == 1  # 292ns -> 250ns (16.8% faster)


def test_gcd_large_scale_reversed_large():
    """Test GCD with large reversed parameters."""
    # GCD(100, 1000) should be 100
    assert gcd_recursive(100, 1000) == 100  # 250ns -> 250ns (0.000% faster)


def test_gcd_large_scale_near_max_int():
    """Test GCD with very large integers (near Python's int limits in practice)."""
    # GCD(999999999999, 333333333333) should be 333333333333
    assert (
        gcd_recursive(999999999999, 333333333333) == 333333333333
    )  # 333ns -> 250ns (33.2% faster)


def test_gcd_large_scale_chain_computation():
    """Test GCD matches associative property conceptually."""
    # Verify GCD(a, b) by checking that it divides both numbers
    a, b = 2024, 506
    result = gcd_recursive(a, b)  # 167ns -> 166ns (0.602% faster)
    # The result should divide both a and b evenly
    assert a % result == 0
    assert b % result == 0


def test_gcd_large_scale_verified_divisibility():
    """Test that GCD result correctly divides both inputs."""
    # For GCD(2100, 1400), verify divisibility
    a, b = 2100, 1400
    result = gcd_recursive(a, b)  # 291ns -> 250ns (16.4% faster)
    assert a % result == 0  # result divides a
    assert b % result == 0  # result divides b
    # Verify it's the actual GCD by checking a/result and b/result are coprime
    a_reduced = a // result
    b_reduced = b // result  # 208ns -> 167ns (24.6% faster)
    assert gcd_recursive(a_reduced, b_reduced) == 1  # They should be coprime


def test_gcd_large_scale_commutative_property():
    """Test that GCD is commutative: GCD(a, b) == GCD(b, a)."""
    # Test commutativity with various pairs
    pairs = [(240, 180), (1071, 462), (997, 13)]
    for a, b in pairs:
        assert gcd_recursive(a, b) == gcd_recursive(
            b, a
        )  # 625ns -> 583ns (7.20% faster)
from src.math.number_theory import gcd_recursive


def test_gcd_recursive():
    gcd_recursive(116, -420)

To edit these changes git checkout codeflash/optimize-gcd_recursive-mn6js0gy and push.

Codeflash Static Badge

The optimization converts the recursive Euclidean algorithm into an iterative loop using tuple unpacking (`a, b = b, a % b`), eliminating per-call function overhead and stack frame allocation. Line profiler data shows the recursive call line consumed 77.3% of original runtime (6.73 ms), while the iterative version spreads the same logic across loop condition (49.9%) and assignment (40%) with lower absolute cost. This yields a 9% speedup (332 µs → 302 µs) without correctness impact, trading the conceptual clarity of recursion for the mechanical efficiency of iteration—particularly valuable for deep call chains like consecutive Fibonacci numbers where the original required many stack frames.
@codeflash-ai codeflash-ai bot requested a review from aseembits93 March 25, 2026 21:20
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants