7

I have a program that finds, for all integers less than or equal to the input, numbers that can be represented as the sum of two cubes, twice, aka the Ramanujan's number problem.

I have written this in Java and Rust, however, it runs more than twice as slow in Rust as compared to Java.

Is there anything I can do to make it perform better, or otherwise improve it?

Rust code:

use num_integer::Roots;
fn main() {
    let v = 984067;
    // let v = 87539319;
    for i in 1..=v {
        ramanujan(i)
    }
}
fn ramanujan(m: i32) {
    let maxcube = m.cbrt();
    let mut res1 = 0;
    let mut res2 = 0;
    let mut _res3 = 0;
    let mut _res4 = 0;
    for i in 1..=maxcube {
        for j in 1..=maxcube {
            if i * i * i + j * j * j == m {
                res1 = i;
                res2 = j;
                break;
            }
        }
    }
    for k in 1..=maxcube {
        for l in 1..=maxcube {
            if k == res1 || k == res2 || l == res1 || l == res2 {
                continue;
            }
            if k * k * k + l * l * l == m {
                _res3 = k;
                _res4 = l;
                break;
            }
        }
    }
    // if ((res1 * res1 * res1) + (res2 * res2 * res2) == m) && ((res3 * res3 * res3) + (res4 * res4 * res4) == m) {
    //     println!("{} is representable as the sums of two different sets of two cubes!\nThese values are {}, {}, and {}, {}.", m, res1, res2, res3, res4);
    // }
}

Java code:

public class Ramun {
    public static void main(String[] args) {
        int v = 984067;
        // int v = 87539319;
        for (int i = 1; i <= v; i++) {
            ramanujan(i);
        }
    }

    public static void ramanujan(int m) {
        int maxcube = (int) Math.round(Math.cbrt(m));
        int res1 = 0, res2 = 0, res3 = 0, res4 = 0;
        for (int i = 1; i <= maxcube; i++) {
            for (int j = 1; j <= maxcube; j++) {
                if (((i * i * i) + (j * j * j)) == m) {
                    res1 = i;
                    res2 = j;
                    break;
                }
            }
        }
        for (int k = 1; k <= maxcube; k++) {
            for (int l = 1; l <= maxcube; l++) {
                if (k == res1 || k == res2 || l == res1 || l == res2)
                    continue;
                if (((k * k * k) + (l * l * l)) == m) {
                    res3 = k;
                    res4 = l;
                    break;
                }
            }
        }
        // if (((res1 * res1 * res1) + (res2 * res2 * res2) == m) && ((res3 * res3 * res3) + (res4 * res4 * res4) == m)) {
        //     System.out.printf("%d is representable as the sums of two different sets of two cubes!%nThese values are %d, %d, and %d, %d.%n", m, res1, res2, res3, res4);
        // }
    }
}

Time output for both programs

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • 2
    Does this answer your question? [Why is my Rust program slower than the equivalent Java program?](https://stackoverflow.com/questions/25255736/why-is-my-rust-program-slower-than-the-equivalent-java-program) – Stargateur Dec 29 '21 at 00:11
  • Nope. As I show in the attached image, the 18 second run time is with the --release flag. It takes at least a minute to run without that flag. – boiimakillu Dec 29 '21 at 00:23
  • 1
    please use text and not image, [edit] your question to do so – Stargateur Dec 29 '21 at 00:48
  • Side comment: you calculate `i*i*i` and `l*l*l` repeatedly and unnecessarily... not sure if the optimizer can hoist this out of the inner loop, or if it takes a while for JIT to do that. – Jim Garrison Dec 29 '21 at 01:31

1 Answers1

16

The problem lies in RangeInclusive which can be expensive.

Here's a version avoiding it:

fn ramanujan(m: i32) {
    let maxcube = m.cbrt() + 1; // we know it can't overflow
    let mut res1 = 0;
    let mut res2 = 0;
    let mut res3 = 0;
    let mut res4 = 0;

    for i in 1..maxcube {
        for j in 1..maxcube {
            if i * i * i + j * j * j == m {
                res1 = i;
                res2 = j;
                break;
            }
        }
    }

    for k in 1..maxcube {
        for l in 1..maxcube {
            if k == res1 || k == res2 || l == res1 || l == res2 {
                continue;
            }
            if k * k * k + l * l * l == m {
                res3 = k;
                res4 = l;
                break;
            }
        }
    }
}

Result:

From: 0.01s user 0.00s system 0% cpu 17.993 total
To: 0.00s user 0.01s system 0% cpu 3.494 total

I added a comment to #45222 to draw attention to this issue.


Looks like for_each() allows better performance too (as for loops are more natural and should have the same performance, it should be considered as a bug):

fn ramanujan(m: i32) {
    let maxcube = m.cbrt();
    let mut res1 = 0;
    let mut res2 = 0;
    let mut res3 = 0;
    let mut res4 = 0;

    (1..=maxcube).for_each(|i| {
        (1..=maxcube).try_for_each(|j| {
            if i * i * i + j * j * j == m {
                res1 = i;
                res2 = j;
                ControlFlow::Break(())
            } else {
                ControlFlow::Continue(())
            }
        });
    });
    (1..=maxcube).for_each(|k| {
        (1..=maxcube).try_for_each(|l| {
            if k != res1 && k != res2 && l != res1 && l != res2 && k * k * k + l * l * l == m {
                res3 = k;
                res4 = l;
                ControlFlow::Break(())
            } else {
                ControlFlow::Continue(())
            }
        });
    });
}
0.00s user 0.01s system 0% cpu 4.029 total
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • 2
    Wow, thank for the help! That was definitely it. Changing from ..=var to ..var+1 halved the run time, making the Rust version twice as fast as the Java, and nearly 6 times faster than the original Rust. When running it with the print statements their logic added back in, the Rust version is only ~1 second faster than the Java, but that's still an improvement. – boiimakillu Dec 29 '21 at 01:15
  • Try to use buffering. Java uses buffered I/O AFAIK, and Rust doesn't. – Chayim Friedman Dec 29 '21 at 05:21