Skip to content

⚡️ Speed up function gcdRecursive by 247%#1

Open
codeflash-ai[bot] wants to merge 1 commit intomainfrom
codeflash/optimize-gcdRecursive-mky1xs1y
Open

⚡️ Speed up function gcdRecursive by 247%#1
codeflash-ai[bot] wants to merge 1 commit intomainfrom
codeflash/optimize-gcdRecursive-mky1xs1y

Conversation

@codeflash-ai
Copy link
Copy Markdown

@codeflash-ai codeflash-ai bot commented Jan 28, 2026

📄 247% (2.47x) speedup for gcdRecursive in js/src/math/computation.js

⏱️ Runtime : 92.5 microseconds 26.7 microseconds (best of 250 runs)

📝 Explanation and details

This optimization achieves a 246% speedup (92.5μs → 26.7μs) by making two key changes to the GCD computation:

1. Recursion to Iteration Conversion
The original recursive implementation incurs function call overhead on every iteration of the Euclidean algorithm. Each recursive call creates a new stack frame, copies parameters, and manages return addresses. The optimized version replaces this with a simple while loop that performs the same mathematical operations without any call overhead. This is particularly impactful for:

  • Worst-case inputs like consecutive Fibonacci numbers (25.5μs → 2.00μs, 1177% faster) that require many algorithm steps
  • Multiple iterations where coprime or larger numbers need several reduction steps (e.g., 35,12 showing 187% improvement)

2. Math.abs Caching
By storing Math.abs in a local constant (const abs = Math.abs), the optimization eliminates repeated property lookups on the Math object. In the original code, Math.abs is accessed on every recursive call (lines 13-14 showing 72.7% of profiled time). The cached version reduces this to a single lookup per invocation, as evidenced by the optimized profiler showing only 7.4% time on the abs calls.

Why This Works:

  • Call stack elimination: No recursive overhead means lower memory pressure and faster execution
  • Property lookup reduction: One-time caching of Math.abs vs. repeated object property resolution
  • Tight loop efficiency: Modern JavaScript engines optimize simple while loops better than recursive function calls

Test Results Show:

  • Basic cases with few iterations: 78-227% faster (e.g., equal numbers, small coprimes)
  • Zero-handling edge cases: 20-86% faster (immediate termination benefits less from iteration conversion)
  • Large/worst-case inputs: 711-1177% faster (maximum benefit from eliminating deep recursion)
  • All 300 random large number iterations pass, confirming mathematical correctness is preserved

The optimization maintains identical mathematical behavior while dramatically improving runtime through lower-level execution efficiency.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 43 Passed
🌀 Generated Regression Tests 45 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
⚙️ Click to see Existing Unit Tests
Test File::Test Function Original ⏱️ Optimized ⏱️ Speedup
math.test.js::GCD and LCM gcdRecursive coprime numbers 1.79μs 583ns 207%✅
math.test.js::GCD and LCM gcdRecursive gcd with negative numbers 1.92μs 583ns 229%✅
math.test.js::GCD and LCM gcdRecursive gcd(0, 5) = 5 1.29μs 625ns 107%✅
math.test.js::GCD and LCM gcdRecursive gcd(48, 18) = 6 1.83μs 625ns 193%✅
math.test.js::GCD and LCM gcdRecursive gcd(5, 0) = 5 959ns 583ns 64.5%✅
🌀 Click to see Generated Regression Tests
// imports
const { gcdRecursive } = require('../src/math/computation');

// unit tests
describe('gcdRecursive', () => {
    // Basic Test Cases
    describe('Basic functionality', () => {
        test('should compute gcd for typical integer inputs (commutative)', () => {
            // 48 and 18 have gcd 6; also test the commutative property gcd(a,b) === gcd(b,a)
            expect(gcdRecursive(48, 18)).toBe(6);  // 3.92μs -> 1.25μs (213% faster)
            expect(gcdRecursive(18, 48)).toBe(6);
        });

        test('should return 1 for coprime numbers', () => {
            // 35 and 12 are coprime
            expect(gcdRecursive(35, 12)).toBe(1);  // 1.79μs -> 625ns (187% faster)
        });

        test('should return the absolute value when numbers are equal', () => {
            // gcd(7,7) == 7, also check negative equal numbers
            expect(gcdRecursive(7, 7)).toBe(7);  // 2.29μs -> 1.25μs (83.4% faster)
            expect(gcdRecursive(-7, -7)).toBe(7);
        });
    });

    // Edge Test Cases
    describe('Edge cases', () => {
        test('should handle zero values correctly', () => {
            // gcd(0, b) == |b|, gcd(a, 0) == |a|, gcd(0,0) == 0 per implementation
            expect(gcdRecursive(0, 5)).toBe(5);  // 2.67μs -> 1.75μs (52.3% faster)
            expect(gcdRecursive(5, 0)).toBe(5);
            expect(gcdRecursive(0, 0)).toBe(0);
        });

        test('should handle negative inputs by taking absolute values', () => {
            // Negative inputs should not affect result sign
            expect(gcdRecursive(-48, 18)).toBe(6);  // 5.29μs -> 1.88μs (182% faster)
            expect(gcdRecursive(48, -18)).toBe(6);
            expect(gcdRecursive(-48, -18)).toBe(6);
        });

        test('should handle floating point inputs that have exact fractional gcds', () => {
            // The algorithm uses Math.abs and % so it can work for some fractional values.
            // 4.5 and 1.5 -> gcd should be 1.5
            expect(gcdRecursive(4.5, 1.5)).toBe(1.5);  // 2.75μs -> 1.38μs (100% faster)

            // 4.5 and 3 -> gcd should also be 1.5 (4.5 % 3 = 1.5)
            expect(gcdRecursive(4.5, 3)).toBe(1.5);
        });

        test('should behave deterministically for swapped inputs', () => {
            // Ensure ordering doesn't affect result aside from commutativity already tested.
            const a = 270, b = 192; // gcd is 6
            expect(gcdRecursive(a, b)).toBe(gcdRecursive(b, a));  // 4.21μs -> 1.21μs (248% faster)
            expect(gcdRecursive(a, b)).toBe(6);
        });

        test('should return integer for integer inputs', () => {
            // For integer inputs, the result must be an integer (no fractional residue)
            const result = gcdRecursive(100, 25);
            expect(Number.isInteger(result)).toBe(true);
            expect(result).toBe(25);
        });
    });

    // Large Scale Test Cases
    describe('Performance tests', () => {
        test('should handle large Fibonacci consecutive inputs (worst-case-like for Euclidean algorithm)', () => {
            // Construct Fibonacci numbers up to the largest safe integer below Number.MAX_SAFE_INTEGER (2^53 - 1).
            // Consecutive Fibonacci numbers are known to be a worst-case sequence for the Euclidean algorithm
            // in terms of number of steps. Using such a pair tests recursion depth and correctness.
            const fibs = [1, 1];
            while (true) {
                const next = fibs[fibs.length - 1] + fibs[fibs.length - 2];
                if (!Number.isSafeInteger(next)) break;
                fibs.push(next);
            }
            // Use the largest consecutive pair we built (both are safe integers)
            const n = fibs.length - 1;
            const a = fibs[n];     // F_n
            const b = fibs[n - 1]; // F_{n-1}
            // Consecutive Fibonacci numbers are coprime -> gcd should be 1
            expect(gcdRecursive(a, b)).toBe(1);  // 25.5μs -> 2.00μs (1177% faster)
        });

        test('should match an iterative gcd implementation on many large random-ish inputs', () => {
            // Deterministic pseudo-random generator (LCG) to keep test reproducible.
            function lcg(seed) {
                let s = seed >>> 0;
                return () => {
                    // constants from Numerical Recipes
                    s = (1664525 * s + 1013904223) >>> 0;
                    return s;
                };
            }

            // Reference iterative gcd (robust and simple)
            function gcdIterative(x, y) {
                x = Math.abs(x);
                y = Math.abs(y);
                // Handle non-integers similarly to the recursive implementation (allow fractional gcds)
                // We'll compare numeric equality. Use a small tolerance for floating remainder propagation.
                // If both are zero, return 0.
                if (y === 0) return x;
                while (y !== 0) {
                    const r = x % y;
                    x = y;
                    y = r;
                }
                return x;
            }

            const rand = lcg(123456); // fixed seed
            // Run a moderate number of iterations (well below 1000 per instructions)
            const iterations = 300;
            for (let i = 0; i < iterations; i++) {
                // Produce large-ish numbers but within safe integer range:
                // combine two LCG outputs to create a 32-bit-like number, then mod to a large bound.
                const r1 = rand();
                const r2 = rand();
                // Build numbers up to about 1e12 safely
                const a = ((r1 << 20) ^ r2) % 1_000_000_000_000;
                const b = ((r2 << 18) ^ r1) % 1_000_000_000_000;

                // avoid both zeros (rare) to keep test meaningful
                const aa = a === 0 && b === 0 ? 123456789 : a;
                const bb = b === 0 ? 987654321 : b;

                const expected = gcdIterative(aa, bb);
                const actual = gcdRecursive(aa, bb);

                // Use strict equality for numeric results. For these integer-like inputs,
                // result should be integer and exactly equal.
                expect(actual).toBe(expected);
            }
        }, 20000); // give a larger timeout in case environment is slow (20s) but typically much faster
    });
});
const { gcdRecursive } = require('../src/math/computation');

describe('gcdRecursive', () => {
    // Basic Test Cases
    describe('Basic functionality', () => {
        test('should return GCD of two positive integers', () => {
            expect(gcdRecursive(48, 18)).toBe(6);  // 1.71μs -> 583ns (193% faster)
        });

        test('should return GCD of two coprime numbers', () => {
            expect(gcdRecursive(17, 19)).toBe(1);  // 2.00μs -> 625ns (220% faster)
        });

        test('should return the larger number when one is multiple of the other', () => {
            expect(gcdRecursive(100, 25)).toBe(25);  // 1.08μs -> 584ns (85.4% faster)
        });

        test('should return GCD of two equal numbers', () => {
            expect(gcdRecursive(42, 42)).toBe(42);  // 1.04μs -> 583ns (78.7% faster)
        });

        test('should return GCD with small numbers', () => {
            expect(gcdRecursive(2, 3)).toBe(1);  // 1.71μs -> 583ns (193% faster)
        });

        test('should handle GCD of consecutive numbers', () => {
            expect(gcdRecursive(10, 11)).toBe(1);  // 1.67μs -> 583ns (186% faster)
        });

        test('should return correct GCD for larger coprime numbers', () => {
            expect(gcdRecursive(121, 77)).toBe(11);  // 2.04μs -> 625ns (227% faster)
        });
    });

    // Edge Test Cases
    describe('Edge cases', () => {
        test('should handle zero as second argument', () => {
            expect(gcdRecursive(42, 0)).toBe(42);  // 750ns -> 625ns (20.0% faster)
        });

        test('should handle zero as first argument', () => {
            expect(gcdRecursive(0, 42)).toBe(42);  // 1.08μs -> 583ns (85.8% faster)
        });

        test('should handle both arguments as zero', () => {
            expect(gcdRecursive(0, 0)).toBe(0);  // 708ns -> 583ns (21.4% faster)
        });

        test('should handle negative first argument', () => {
            expect(gcdRecursive(-48, 18)).toBe(6);  // 1.67μs -> 583ns (186% faster)
        });

        test('should handle negative second argument', () => {
            expect(gcdRecursive(48, -18)).toBe(6);  // 1.67μs -> 583ns (186% faster)
        });

        test('should handle both arguments negative', () => {
            expect(gcdRecursive(-48, -18)).toBe(6);  // 1.67μs -> 542ns (208% faster)
        });

        test('should handle single negative and zero', () => {
            expect(gcdRecursive(-42, 0)).toBe(42);  // 750ns -> 583ns (28.6% faster)
        });

        test('should handle GCD of 1 and any number', () => {
            expect(gcdRecursive(1, 100)).toBe(1);  // 1.38μs -> 583ns (136% faster)
        });

        test('should handle GCD of any number and 1', () => {
            expect(gcdRecursive(100, 1)).toBe(1);  // 1.08μs -> 541ns (100% faster)
        });

        test('should handle GCD with 1 and 1', () => {
            expect(gcdRecursive(1, 1)).toBe(1);  // 1.08μs -> 583ns (85.8% faster)
        });

        test('should handle swapped arguments (commutativity)', () => {
            expect(gcdRecursive(48, 18)).toBe(gcdRecursive(18, 48));  // 1.67μs -> 583ns (186% faster)
        });

        test('should handle negative numbers with swapped arguments', () => {
            expect(gcdRecursive(-48, 18)).toBe(gcdRecursive(18, -48));  // 1.67μs -> 583ns (186% faster)
        });
    });

    // Large Scale Test Cases
    describe('Performance tests', () => {
        test('should handle large positive integers efficiently', () => {
            // Test with large Fibonacci-like numbers which stress the algorithm
            const result = gcdRecursive(987654321, 123456789);
            expect(result).toBe(gcdRecursive(123456789, 987654321));
            expect(result).toBeGreaterThan(0);
        });

        test('should handle very large numbers within reasonable time', () => {
            const start = Date.now();
            const result = gcdRecursive(1000000007, 1000000009);
            const end = Date.now();
            
            expect(result).toBe(1);
            expect(end - start).toBeLessThan(100); // Should complete in under 100ms
        });

        test('should handle multiple GCD calculations efficiently', () => {
            const start = Date.now();
            
            for (let i = 0; i < 100; i++) {
                gcdRecursive(123456 + i, 789012 + i);
            }
            
            const end = Date.now();
            expect(end - start).toBeLessThan(500); // 100 calculations should be fast
        });

        test('should handle large numbers with large GCD', () => {
            // Both numbers are multiples of 1001
            const result = gcdRecursive(1001000, 2002000);
            expect(result).toBe(1001000);
        });

        test('should efficiently compute GCD of Fibonacci numbers', () => {
            // GCD of consecutive Fibonacci numbers is always 1
            expect(gcdRecursive(233, 377)).toBe(1);  // 9.79μs -> 1.21μs (711% faster)
            expect(gcdRecursive(987, 1597)).toBe(1);
        });

        test('should handle large numbers where one divides the other', () => {
            const result = gcdRecursive(1000000, 10000);
            expect(result).toBe(10000);
        });

        test('should handle large negative numbers efficiently', () => {
            const start = Date.now();
            const result = gcdRecursive(-987654321, -123456789);
            const end = Date.now();
            
            expect(result).toBeGreaterThan(0);
            expect(end - start).toBeLessThan(100);
        });
    });
});

To edit these changes git checkout codeflash/optimize-gcdRecursive-mky1xs1y and push.

Codeflash Static Badge

This optimization achieves a **246% speedup** (92.5μs → 26.7μs) by making two key changes to the GCD computation:

**1. Recursion to Iteration Conversion**
The original recursive implementation incurs function call overhead on every iteration of the Euclidean algorithm. Each recursive call creates a new stack frame, copies parameters, and manages return addresses. The optimized version replaces this with a simple `while` loop that performs the same mathematical operations without any call overhead. This is particularly impactful for:
- **Worst-case inputs** like consecutive Fibonacci numbers (25.5μs → 2.00μs, **1177% faster**) that require many algorithm steps
- **Multiple iterations** where coprime or larger numbers need several reduction steps (e.g., 35,12 showing 187% improvement)

**2. Math.abs Caching**
By storing `Math.abs` in a local constant (`const abs = Math.abs`), the optimization eliminates repeated property lookups on the Math object. In the original code, `Math.abs` is accessed on every recursive call (lines 13-14 showing 72.7% of profiled time). The cached version reduces this to a single lookup per invocation, as evidenced by the optimized profiler showing only 7.4% time on the abs calls.

**Why This Works:**
- **Call stack elimination**: No recursive overhead means lower memory pressure and faster execution
- **Property lookup reduction**: One-time caching of `Math.abs` vs. repeated object property resolution
- **Tight loop efficiency**: Modern JavaScript engines optimize simple while loops better than recursive function calls

**Test Results Show:**
- Basic cases with few iterations: 78-227% faster (e.g., equal numbers, small coprimes)
- Zero-handling edge cases: 20-86% faster (immediate termination benefits less from iteration conversion)
- Large/worst-case inputs: 711-1177% faster (maximum benefit from eliminating deep recursion)
- All 300 random large number iterations pass, confirming mathematical correctness is preserved

The optimization maintains identical mathematical behavior while dramatically improving runtime through lower-level execution efficiency.
@codeflash-ai codeflash-ai bot requested a review from KRRT7 January 28, 2026 13:19
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Jan 28, 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