Skip to content

Performance problem in for loops with step_by(run-time-variable) #141360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
leonardo-m opened this issue May 21, 2025 · 2 comments
Open

Performance problem in for loops with step_by(run-time-variable) #141360

leonardo-m opened this issue May 21, 2025 · 2 comments
Labels
C-bug Category: This is a bug. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged.

Comments

@leonardo-m
Copy link

leonardo-m commented May 21, 2025

A system language should perform basic for loops efficiently. A problem was shown in issue #45222, another performance pitfall is created when you need steps larger than 1 and such step size is a run-time variable. The equivalent of the (efficient) C loop:

for (size_t j = i * 2; j < m; j += i) {...}

This shows the problem in a simple way, using a sieve (this code isn't meant to show an efficient sieve implementation). sieve1 uses step_by(variable) while sieve2 uses a while loop that should be equivalent:

fn sieve1(m: usize) -> Vec<bool> {
    let mut primes = vec![true; m];
    primes[0] = false;
    primes[1] = false;
    for i in 2 .. m {
        if primes[i] {
            for j in (i * 2 .. m).step_by(i) {
                primes[j] = false;
            }
        }
    }
    primes
}

fn sieve2(m: usize) -> Vec<bool> {
    let mut primes = vec![true; m];
    primes[0] = false;
    primes[1] = false;
    for i in 2 .. m {
        if primes[i] {
            let mut j = i * 2;
            while j < m {
                primes[j] = false;
                j += i;
            }
        }
    }
    primes
}

fn main() {
    const M: usize = 150_000_000;
    println!("{}", sieve1(M).into_iter().filter(|&b| b).count()); // 2.93s
    println!("{}", sieve2(M).into_iter().filter(|&b| b).count()); // 2.86s
}

Commenting out the two lines in the main() shows a performance difference. The difference is small, but if you nest more than one for loop both using step_by the problem compounds. I think LLVM is able to remove this overhead from step_by is the step size is a compile-time constant and you have only one un-nested step_by.

@leonardo-m leonardo-m added the C-bug Category: This is a bug. label May 21, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label May 21, 2025
@the8472
Copy link
Member

the8472 commented May 22, 2025

How does it perform if you use for_each instead of for _ in?

@leonardo-m
Copy link
Author

If you mean something like this, it's a little slower than sieve1:

fn sieve1(m: usize) -> Vec<bool> {
    let mut primes = vec![true; m];
    primes[0] = false;
    primes[1] = false;
    for i in 2 .. m {
        if primes[i] {
            for j in (i * 2 .. m).step_by(i) {
                primes[j] = false;
            }
        }
    }
    primes
}

fn sieve2(m: usize) -> Vec<bool> {
    let mut primes = vec![true; m];
    primes[0] = false;
    primes[1] = false;
    for i in 2 .. m {
        if primes[i] {
            let mut j = i * 2;
            while j < m {
                primes[j] = false;
                j += i;
            }
        }
    }
    primes
}

fn sieve3(m: usize) -> Vec<bool> {
    let mut primes = vec![true; m];
    primes[0] = false;
    primes[1] = false;
    for i in 2 .. m {
        if primes[i] {
            (i * 2 .. m)
            .step_by(i)
            .for_each(|j| primes[j] = false);
        }
    }
    primes
}

fn main() {
    const M: usize = 150_000_000;
    //println!("{}", sieve1(M).into_iter().filter(|&b| b).count()); // 2.93s
    //println!("{}", sieve2(M).into_iter().filter(|&b| b).count()); // 2.86s
    println!("{}", sieve3(M).into_iter().filter(|&b| b).count()); // 2.95s
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged.
Projects
None yet
Development

No branches or pull requests

3 participants