Why functional approach generate more optimize output?

Coming from this playground;

I noticed that functional approach generate more optimize code then Imperative one.

const UPPER : u32 = 1000;
fn is_odd(n : u32)->bool { n % 2 == 1 }
#[no_mangle]
pub fn main() {
    // functional_approach();
    // imperative_approach();
}
fn functional_approach() {
    let sum_of_squared_odd_numbers: u32 =
        (0..).map(|n| n * n)                             // All natural numbers squared
             .take_while(|&n_squared| n_squared < UPPER) // Below upper limit
             .filter(|&n_squared| is_odd(n_squared))     // That are odd
             .fold(0, |acc, n_squared| acc + n_squared); // Sum them
    
    println!("functional style: {}", sum_of_squared_odd_numbers);
}
fn imperative_approach() {
    let mut acc = 0;
    for n in 0.. { // Iterate: 0, 1, 2, ... to infinity
        let n_squared = n * n; // Square the number
        // Break loop if exceeded the upper limit
        if n_squared >= UPPER { break; } 
        // Accumulate value, if it's odd
        else if is_odd(n_squared) { acc += n_squared; }
    }
    println!("imperative style: {}", acc);
}

Where functional approach generate:

main:
        sub     rsp, 72
        mov     dword ptr [rsp + 4], 5456
        lea     rax, [rsp + 4]
        mov     qword ptr [rsp + 8], rax
        mov     rax, qword ptr [rip + _ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$u32$GT$3fmt17hd0f54e1eb87dc8ebE@GOTPCREL]
        mov     qword ptr [rsp + 16], rax
        lea     rax, [rip + .L__unnamed_1]
        mov     qword ptr [rsp + 24], rax
        mov     qword ptr [rsp + 32], 2
        mov     qword ptr [rsp + 40], 0
        lea     rax, [rsp + 8]
        mov     qword ptr [rsp + 56], rax
        mov     qword ptr [rsp + 64], 1
        lea     rdi, [rsp + 24]
        call    qword ptr [rip + _ZN3std2io5stdio6_print17h7e18c69e87ebeedbE@GOTPCREL]
        add     rsp, 72
        ret

Where Imperative approach generate more verbose output:

main:
        sub     rsp, 72
        mov     dword ptr [rsp + 4], 0
        xor     eax, eax
        mov     ecx, 2
        xor     edx, edx
        jmp     .LBB0_1
.LBB0_3:
        imul    esi, esi
        add     eax, esi
        mov     dword ptr [rsp + 4], eax
        mov     edx, ecx
        imul    edx, ecx
        add     ecx, 2
        cmp     ecx, 34
        je      .LBB0_4
.LBB0_1:
        lea     esi, [rcx - 1]
        test    dl, 1
        je      .LBB0_3
        add     eax, edx
        mov     dword ptr [rsp + 4], eax
        jmp     .LBB0_3
.LBB0_4:
        lea     rax, [rsp + 4]
        mov     qword ptr [rsp + 8], rax
        mov     rax, qword ptr [rip + _ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$u32$GT$3fmt17hd0f54e1eb87dc8ebE@GOTPCREL]
        mov     qword ptr [rsp + 16], rax
        lea     rax, [rip + .L__unnamed_1]
        mov     qword ptr [rsp + 24], rax
        mov     qword ptr [rsp + 32], 2
        mov     qword ptr [rsp + 40], 0
        lea     rax, [rsp + 8]
        mov     qword ptr [rsp + 56], rax
        mov     qword ptr [rsp + 64], 1
        lea     rdi, [rsp + 24]
        call    qword ptr [rip + _ZN3std2io5stdio6_print17h7e18c69e87ebeedbE@GOTPCREL]
        add     rsp, 72
        ret

The compiler just constant-folded the iterators approach and printed a constant. I suspect the reason it didn't do that for the C-style loop is because of the break.

A more fair comparison is this (godbolt):

pub fn functional_approach(upper: u32) -> u32 {
    let sum_of_squared_odd_numbers =
        (0..).map(|n| n * n)                             // All natural numbers squared
             .take_while(|&n_squared| n_squared < upper) // Below upper limit
             .filter(|&n_squared| n_squared % 2 == 1)     // That are odd
             .fold(0, |acc, n_squared| acc + n_squared); // Sum them
    sum_of_squared_odd_numbers
}

pub fn imperative_approach(upper: u32) -> u32 {
    let mut acc = 0;
    for n in 0.. { // Iterate: 0, 1, 2, ... to infinity
        let n_squared = n * n; // Square the number
        // Break loop if exceeded the upper limit
        if n_squared >= upper { break; } 
        // Accumulate value, if it's odd
        else if n_squared % 2 == 1 { acc += n_squared; }
    }
    acc
}

Which generates the exact same code.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.