考虑这个程序:
use std::io::BufRead;
use std::io;
fn main() {
let mut n = 0;
let stdin = io::stdin();
for _ in stdin.lock().lines() {
n += 1;
}
println!("{}", n);
}
为什么它比wc的GNU版本慢10倍?看看我如何测量它:
$ yes | dd count=1000000 | wc -l
256000000
1000000+0 records in
1000000+0 records out
512000000 bytes (512 MB, 488 MiB) copied, 1.16586 s, 439 MB/s
$ yes | dd count=1000000 | ./target/release/wc
1000000+0 records in
1000000+0 records out
512000000 bytes (512 MB, 488 MiB) copied, 41.685 s, 12.3 MB/s
256000000
答案 0 :(得分:12)
您的代码比原始wc
慢得多的原因有很多。你需要付出一些实际根本不需要的东西。通过删除它们,您已经可以获得相当大的速度提升。
BufRead::lines()
会返回an iterator,这会产生String
个元素。由于这种设计,它(它必须!)为每一行分配内存。 lines()
方法是一种方便编写代码的方法,但它不应该用于高性能情况。
为避免为每一行分配堆内存,您可以使用BufRead::read_line()
代替。代码有点冗长,但正如您所看到的,我们正在重用s
的堆内存:
let mut n = 0;
let mut s = String::new();
let stdin = io::stdin();
let mut lock = stdin.lock();
loop {
s.clear();
let res = lock.read_line(&mut s);
if res.is_err() || res.unwrap() == 0 {
break;
}
n += 1;
}
println!("{}", n);
在我的笔记本上,这会导致:
$ yes | dd count=1000000 | wc -l
256000000
1000000+0 records in
1000000+0 records out
512000000 bytes (512 MB, 488 MiB) copied, 0,981827 s, 521 MB/s
$ yes | dd count=1000000 | ./wc
1000000+0 records in
1000000+0 records out
512000000 bytes (512 MB, 488 MiB) copied, 6,87622 s, 74,5 MB/s
256000000
正如你所看到的,它改进了很多的东西,但仍然不等同。
由于我们正在读入String
,因此我们将stdin的原始输入验证为正确的UTF-8。这需要时间!但我们只对原始字节感兴趣,因为我们只需要计算换行符(0xA
)。我们可以使用Vec<u8>
和BufRead::read_until()
let mut n = 0;
let mut v = Vec::new();
let stdin = io::stdin();
let mut lock = stdin.lock();
loop {
v.clear();
let res = lock.read_until(0xA, &mut v);
if res.is_err() || res.unwrap() == 0 {
break;
}
n += 1;
}
println!("{}", n);
这导致:
1000000+0 records in
1000000+0 records out
512000000 bytes (512 MB, 488 MiB) copied, 4,24162 s, 121 MB/s
256000000
这是60%的改善。但原来的wc
仍然快了3.5倍!
现在我们用掉所有低悬的水果来提升表现。为了匹配wc
的速度,我想必须做一些严肃的剖析。在我们当前的解决方案中,perf
报告以下内容:
memchr
;我不认为这可以改进<StdinLock as std::io::BufRead>::fill_buf()
和<StdinLock as std::io::BufRead>::consume()
剩余时间的很大一部分直接用于main
(由于内联)。从它的外观来看,我们也为跨平台抽象付出了一些代价。 Mutex
方法和内容花费了一些时间。
但是在这一点上,我只是在猜测,因为我没有时间进一步研究这个问题。对不起:&lt;
但请注意,wc
是一个旧工具,并且针对其运行的平台以及正在执行的任务进行了高度优化。我想有关Linux内部事物的知识会对性能有很大帮助。这是非常专业的,所以我不希望轻易匹配性能。
答案 1 :(得分:11)
这是因为你的版本绝不等同于没有为字符串分配任何内存的GNU,而只是移动文件指针并递增不同的计数器。此外,它处理原始字节,而Rust的String
必须是有效的UTF-8。
答案 2 :(得分:4)
这是我从Arnavion那里获得的#rust-beginners IRC版本:
use std::io::Read;
fn main() {
let mut buffer = [0u8; 1024];
let stdin = ::std::io::stdin();
let mut stdin = stdin.lock();
let mut wc = 0usize;
loop {
match stdin.read(&mut buffer) {
Ok(0) => {
break;
},
Ok(len) => {
wc += buffer[0..len].into_iter().filter(|&&b| b == b'\n').count();
},
Err(err) => {
panic!("{}", err);
},
}
};
println!("{}", wc);
}
这使得性能非常接近原始wc
的效果。