为什么我的 Rust 程序比等效的 Java 程序慢? [英] Why is my Rust program slower than the equivalent Java program?

查看:26
本文介绍了为什么我的 Rust 程序比等效的 Java 程序慢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在

当你在没有优化的情况下构建时,它通常会比在 Java 中慢.但是用优化(rustc -Ocargo --release)构建它,它应该会快得多.如果它的标准版本最终仍然变慢,则应该仔细检查以找出变慢的地方 - 可能正在内联不应该或不应该的某些内容,或者可能是预期的某些优化没有发生.

I was playing around with binary serialization and deserialization in Rust and noticed that binary deserialization is several orders of magnitude slower than with Java. To eliminate the possibility of overhead due to, for example, allocations and overheads, I'm simply reading a binary stream from each program. Each program reads from a binary file on disk which contains a 4-byte integer containing the number of input values, and a contiguous chunk of 8-byte big-endian IEEE 754-encoded floating point numbers. Here's the Java implementation:

import java.io.*;

public class ReadBinary {
    public static void main(String[] args) throws Exception {
        DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(args[0])));
        int inputLength = input.readInt();
        System.out.println("input length: " + inputLength);
        try {
            for (int i = 0; i < inputLength; i++) {
                double d = input.readDouble();
                if (i == inputLength - 1) {
                    System.out.println(d);
                }
            }
        } finally {
            input.close()
        }
    }
}

Here's the Rust implementation:

use std::fs::File;
use std::io::{BufReader, Read};
use std::path::Path;

fn main() {
    let args = std::env::args_os();
    let fname = args.skip(1).next().unwrap();
    let path = Path::new(&fname);
    let mut file = BufReader::new(File::open(&path).unwrap());
    let input_length: i32 = read_int(&mut file);
    for i in 0..input_length {
        let d = read_double_slow(&mut file);
        if i == input_length - 1 {
            println!("{}", d);
        }
    }
}

fn read_int<R: Read>(input: &mut R) -> i32 {
    let mut bytes = [0; std::mem::size_of::<i32>()];
    input.read_exact(&mut bytes).unwrap();
    i32::from_be_bytes(bytes)
}

fn read_double_slow<R: Read>(input: &mut R) -> f64 {
    let mut bytes = [0; std::mem::size_of::<f64>()];
    input.read_exact(&mut bytes).unwrap();
    f64::from_be_bytes(bytes)
}

I'm outputting the last value to make sure that all of the input is actually being read. On my machine, when the file contains (the same) 30 million randomly-generated doubles, the Java version runs in 0.8 seconds, while the Rust version runs in 40.8 seconds.

Suspicious of inefficiencies in Rust's byte interpretation itself, I retried it with a custom floating point deserialization implementation. The internals are almost exactly the same as what's being done in Rust's Reader, without the IoResult wrappers:

fn read_double<R : Reader>(input: &mut R, buffer: &mut [u8]) -> f64 {
    use std::mem::transmute;
    match input.read_at_least(8, buffer) {
        Ok(n) => if n > 8 { fail!("n > 8") },
        Err(e) => fail!(e)
    };
    let mut val = 0u64;
    let mut i = 8;
    while i > 0 {
        i -= 1;
        val += buffer[7-i] as u64 << i * 8;
    }
    unsafe {
        transmute::<u64, f64>(val);
    }
}

The only change I made to the earlier Rust code in order to make this work was create an 8-byte slice to be passed in and (re)used as a buffer in the read_double function. This yielded a significant performance gain, running in about 5.6 seconds on average. Unfortunately, this is still noticeably slower (and more verbose!) than the Java version, making it difficult to scale up to larger input sets. Is there something that can be done to make this run faster in Rust? More importantly, is it possible to make these changes in such a way that they can be merged into the default Reader implementation itself to make binary I/O less painful?

For reference, here's the code I'm using to generate the input file:

import java.io.*;
import java.util.Random;

public class MakeBinary {
    public static void main(String[] args) throws Exception {
        DataOutputStream output = new DataOutputStream(new BufferedOutputStream(System.out));
        int outputLength = Integer.parseInt(args[0]);
        output.writeInt(outputLength);
        Random rand = new Random();
        for (int i = 0; i < outputLength; i++) {
            output.writeDouble(rand.nextDouble() * 10 + 1);
        }
        output.flush();
    }
}

(Note that generating the random numbers and writing them to disk only takes 3.8 seconds on my test machine.)

解决方案

When you build without optimisations, it will often be slower than it would be in Java. But build it with optimisations (rustc -O or cargo --release) and it should be very much faster. If the standard version of it still ends up slower, it’s something that should be examined carefully to figure out where the slowness is—perhaps something is being inlined that shouldn’t be, or not that should be, or perhaps some optimisation that was expected is not occurring.

这篇关于为什么我的 Rust 程序比等效的 Java 程序慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆