【问题标题】:Random string generator with minimal allocations分配最少的随机字符串生成器
【发布时间】:2020-09-04 22:16:30
【问题描述】:

我想在给定参数的情况下生成一个包含伪随机 ASCII 字符的大文件:每行大小和行数。如果不为每一行分配新的Strings,我无法找到一种方法。这就是我所拥有的:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=42f5b803910e3a15ff20561117bf9176

use rand::{Rng, SeedableRng};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let mut data: Vec<u8> = Vec::new();
    write_random_lines(&mut data, 10, 10)?;
    println!("{}", std::str::from_utf8(&data)?);
    Ok(())
}

fn write_random_lines<W>(
    file: &mut W,
    line_size: usize,
    line_count: usize,
) -> Result<(), Box<dyn Error>>
where
    W: std::io::Write,
{
    for _ in 0..line_count {
        let mut s: String = rand::rngs::SmallRng::from_entropy()
            .sample_iter(rand::distributions::Alphanumeric)
            .take(line_size)
            .collect();
        s.push('\n');
        file.write(s.as_bytes())?;
    }
    Ok(())
}

我正在每行创建一个新的String,所以我认为这不是内存效率。有fn fill_bytes(&amp;mut self, dest: &amp;mut [u8]),但这是字节。

我最好不要为每一行创建一个新的SmallRng,但它是在循环中使用的,SmallRng 不能被复制。

如何以更节省内存和时间的方式生成随机文件?

【问题讨论】:

    标签: random rust


    【解决方案1】:

    您可以通过在循环外创建String 并在使用内容后clear 来轻松地在循环中重用它:

        // Use Kevin's suggestion not to make a new `SmallRng` each time:
        let mut rng_iter =
            rand::rngs::SmallRng::from_entropy().sample_iter(rand::distributions::Alphanumeric);
        let mut s = String::with_capacity(line_size + 1);  // allocate the buffer
        for _ in 0..line_count {
            s.extend(rng_iter.by_ref().take(line_size));   // fill the buffer
            s.push('\n');
            file.write(s.as_bytes())?;                     // use the contents
            s.clear();                                     // clear the buffer
        }
    

    String::clear 会擦除 String 的内容(必要时删除),但不会释放其后备缓冲区,因此无需重新分配即可重复使用。

    另见

    【讨论】:

    • 谢谢;我决定将它与BufWriter 一起使用,因为它是最简单直接的方法。在速度方面,这比凯文的快 15%。 by_ref() 在循环中将是超级少数。
    【解决方案2】:

    你的代码的这个修改不会分配任何Strings,也不会每次都构造一个新的SmallRng,但我没有对其进行基准测试:

    fn write_random_lines<W>(
        file: &mut W,
        line_size: usize,
        line_count: usize,
    ) -> Result<(), Box<dyn Error>>
    where
        W: std::io::Write,
    {
        // One random data iterator.
        let mut rng_iter = rand::rngs::SmallRng::from_entropy()
            .sample_iter(rand::distributions::Alphanumeric);
    
        // Temporary storage for encoding of chars. If the characters used
        // are not all ASCII then its size should be increased to 4.
        let mut char_buffer = [0; 1];
    
        for _ in 0..line_count {
            for _ in 0..line_size {
                file.write(
                    rng_iter.next()
                        .unwrap()  // iterator is infinite so this never fails
                        .encode_utf8(&mut char_buffer)
                        .as_bytes())?;
            }
            file.write("\n".as_bytes())?;
        }
        Ok(())
    }
    

    我是 Rust 的新手,所以它可能缺少一些整理方法。另外,请注意,这一次只写入一个字符;如果您的 W 每次操作比内存缓冲区更昂贵,您可能希望将其包装在 std::io::BufWriter 中,这将批量写入目标(使用需要分配的缓冲区,但只分配一次)。

    【讨论】:

    • 感谢您的想法。我对此进行了测试,似乎并没有更好。请查看我的编辑结果。
    • 我的错,我犯了一个愚蠢的错误。您的速度更快,但分配数量相同。这可能是因为在您的方法中重复使用了迭代器。
    • 我想知道为什么两者的内存分配是一样的。我预计我的情况会更糟。
    • @Makoto 您的过程几乎肯定会一遍又一遍地释放和重新分配相同的小缓冲区,因此当分配器搅动 lot 时,它永远不需要超过一百个字节一次(不包括在这两种情况下都存在的 100MB 缓冲区)。不确定 Kevin 的额外分配是从哪里来的,但我总是怀疑测量问题。
    • @trentcl 我将所有生成的字节放入Vec,而不是写入文件,以便回答它。
    【解决方案3】:

    我(MakotoE)对 Kevin Reid 的答案进行了基准测试,尽管内存分配似乎相同,但他们的方法似乎更快。

    时间基准:

    #[cfg(test)]
    mod tests {
        extern crate test;
        use test::Bencher;
        use super::*;
    
        #[bench]
        fn bench_write_random_lines0(b: &mut Bencher) {
            let mut data: Vec<u8> = Vec::new();
            data.reserve(100 * 1000000);
            b.iter(|| {
                write_random_lines0(&mut data, 100, 1000000).unwrap();
                data.clear();
            });
        }
    
        #[bench]
        fn bench_write_random_lines1(b: &mut Bencher) {
            let mut data: Vec<u8> = Vec::new();
            data.reserve(100 * 1000000);
            b.iter(|| {
                // This is Kevin's implementation
                write_random_lines1(&mut data, 100, 1000000).unwrap();
                data.clear();
            });
        }
    }
    
    test tests::bench_write_random_lines0 ... bench: 764,953,658 ns/iter (+/- 7,597,989)
    test tests::bench_write_random_lines1 ... bench: 360,662,595 ns/iter (+/- 886,456)
    

    使用 valgrind 的 Massif 对内存使用情况进行基准测试表明两者大致相同。我的总共使用了 3.072 Gi,峰值为 101.0 MB。 Kevin 总共使用了 4.166 Gi,峰值 128.0 MB。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-07
      • 1970-01-01
      • 2011-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-05
      相关资源
      最近更新 更多