rust-lang / rust-lang/rust

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

Open
#141,360 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-iterators C-bug C-optimization I-slow T-compiler
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the issue's sieve1 and sieve2 examples, using the reported M value to reproduce the runtime-variable step_by comparison. Trace the step_by path and measure whether nested or variable-step loops retain avoidable overhead; done means the equivalent loops no longer show the reported performance gap, with a regression measurement demonstrating the result.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.