Random 具有最小分配的随机字符串生成器

Random 具有最小分配的随机字符串生成器,random,rust,Random,Rust,我想在给定参数的情况下生成一个伪随机ASCII字符的大文件:每行大小和行数。如果不为每行分配新的Strings,我无法找到一种方法来实现这一点。这就是我所拥有的: 使用rand:{Rng,SeedableRng}; 使用std::error::error; fn main()->结果{ 让mut data:Vec=Vec::new(); 写入随机行(&mut数据,10,10)?; println!(“{}”,std::str::from_utf8(&data); 好(()) } fn写入随机行(

我想在给定参数的情况下生成一个伪随机ASCII字符的大文件:每行大小和行数。如果不为每行分配新的
String
s,我无法找到一种方法来实现这一点。这就是我所拥有的:

使用rand:{Rng,SeedableRng};
使用std::error::error;
fn main()->结果{
让mut data:Vec=Vec::new();
写入随机行(&mut数据,10,10)?;
println!(“{}”,std::str::from_utf8(&data);
好(())
}
fn写入随机行(
文件:&mut W,
行大小:usize,
行计数:使用,
)->结果
哪里
W:std::io::Write,
{
对于0中的u..行u计数{
让mut s:String=rand::rngs::SmallRng::from_entropy()
.sample_iter(兰德::分布::字母数字)
.take(行大小)
.收集();
s、 推送('\n');
文件。写入(s.为_bytes())?;
}
好(())
}
我正在为每一行创建一个新的
字符串
,因此我认为这不是内存效率。有,但这是字节

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


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

此代码修改不会分配任何
字符串,也不会每次构造新的
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(())
}
fn写入随机行(
文件:&mut W,
行大小:usize,
行计数:使用,
)->结果
哪里
W:std::io::Write,
{
//一个随机数据迭代器。
让mut rng_iter=rand::rngs::SmallRng::from_entropy()
.sample_iter(兰德::分布::字母数字);
//用于字符编码的临时存储器。如果使用
//如果不是全部ASCII码,则其大小应增加到4。
让mut char_buffer=[0;1];
对于0中的u..行u计数{
对于0.行大小中的u{
file.write(
rng_iter.next()
.unwrap()//迭代器是无限的,因此它永远不会失败
.encode_utf8(&mut char_缓冲区)
.as_bytes())?;
}
file.write(“\n”.as_bytes())?;
}
好(())
}

我是新生锈的,所以它可能缺少一些整理它的方法。另外,请注意,这样一次只写一个字符;如果您的
W
每项操作的成本比内存中的缓冲区更高,则您可能希望将其打包,这将批量写入目标(使用需要分配的缓冲区,但只需一次).

通过在循环外部创建字符串,并在使用内容后将其清除,您可以轻松地在循环中重用字符串:

    // 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
删除
字符串的内容(必要时删除),但不会释放其备份缓冲区,因此无需重新分配即可重复使用

另见
  • 解释为什么需要按_ref
执行

I(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();
        });
    }
}

使用valgrind的Massif对内存使用情况进行基准测试表明,两者大致相同。矿山使用的Gi总量为3.072,峰值水平为101.0 MB。凯文总共使用了4.166GI,峰值128.0MB。

谢谢你的建议。我测试了这个,它似乎没有任何好转。请查看我的编辑结果。我的错,我犯了一个愚蠢的错误。您的速度更快,但分配量相同。这可能是因为迭代器在您的方法中被重用,但我想知道为什么这两种方法的内存分配是相同的。我预计我的情况会更糟。@Makoto您的过程几乎肯定会一次又一次地释放和重新分配同一个小缓冲区,因此,当分配器大量运行时,它永远不会一次需要超过100字节(不包括两种情况下的100MB缓冲区)。不确定凯文的情况下额外的分配来自何处,但我总是怀疑测量问题。@trentcl我将所有生成的字节放入
Vec
中,而不是写入文件,这样就可以解决问题。谢谢;我决定将其用于
BufWriter
,因为这是最简单、最直接的方法。就速度而言,这比凯文快15%
by_ref()
将成为循环中的超级少数。
#[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)