为什么在Rust中使用range / loop比Java慢?

7

我有一个程序,可以找到小于或等于输入值的所有整数中,能够被表示成两个立方数之和的数的两倍,也就是拉马努金数字问题。

我已经用Java和Rust编写了这个程序,但是与Java相比,在Rust中运行的速度要慢两倍以上。

有什么方法可以让它表现得更好,或者进行其他改进吗?

Rust 代码:

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 代码:

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);
        // }
    }
}

两个程序的时间输出


2
这个回答解决了你的问题吗?为什么我的Rust程序比等价的Java程序慢? - Stargateur
不是的。正如我在附加的图片中展示的那样,18秒的运行时间是使用了“--release”标志。如果没有该标志,至少需要一分钟才能运行。 - boiimakillu
1
请使用文本而不是图像,[编辑]您的问题以执行此操作。 - Stargateur
旁注:您重复且不必要地计算了i*i*il*l*l...不确定优化器是否可以将其提升出内部循环,或者JIT需要一段时间才能完成。 - Jim Garrison
1个回答

16
问题出在RangeInclusive上,这可能会很耗费资源。
下面是一个避免使用它的版本:
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;
            }
        }
    }
}

结果:

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

我在#45222中添加了一条评论以引起对此问题的关注。
看起来for_each()也允许更好的性能(由于for循环更自然且应该具有相同的性能,因此它应被视为错误)。
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

2
哇,感谢你的帮助!那肯定就是它了。从.. =变量改为.. var +1使运行时间减半,使Rust版本比Java快两倍,比原始的Rust快近6倍。当运行带有逻辑添加的打印语句时,Rust 版本仅比 Java 快约1秒,但这仍然是一种进步。 - boiimakillu
尝试使用缓冲区。据我所知,Java使用缓冲I/O,而Rust则不使用。 - Chayim Friedman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接