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



我想在给定参数的情况下生成一个伪随机 ASCII 字符的大文件:每行大小和行数。如果不为每行分配新的String,我就无法找到一种方法来做到这一点。这就是我所拥有的: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(&mut self, dest: &mut [u8]),但这适用于字节。

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

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

通过在循环外创建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的内容(必要时删除),但不会释放其后备缓冲区,因此无需重新分配即可重复使用。

参见

  • 在循环中使用read_line时的奇怪行为
  • 为什么 Iterator::take_while 拥有迭代器的所有权?解释了为什么需要by_ref

对代码的这种修改不会分配任何String,也不会每次都构造一个新的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中,这将批量写入目标(使用需要分配的缓冲区,但只能分配一次)。

我(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。凯文总共使用了 4.166 Gi,峰值为 128.0 MB。

最新更新