Глядя на очень простую программу, как выяснить, на что расходуется ЦП:
use mmap::*;
use crc::{crc32, Hasher32};
use std::cmp::min;
use std::env::args;
use std::fs::File;
use std::fs::metadata;
use std::os::unix::io::AsRawFd;
use std::slice::from_raw_parts;
use std::time::Instant;
fn main() -> std::io::Result<()> {
let filename = args().nth(1).unwrap();
let t0 = Instant::now();
let file = File::open(String::from(&filename[..]))?;
let fd = file.as_raw_fd();
let fs = metadata(filename)?;
let sz = fs.len() as usize;
let mut offset: usize = 0;
let mut c32 = crc32::Digest::new(crc32::IEEE);
while offset < sz {
let rem = sz - offset;
let rem = min(rem, 1024 * 1024);
let map = MemoryMap::new(rem, &[MapOption::MapFd(fd),
MapOption::MapReadable,
MapOption::MapOffset(offset)]).
map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let buf = map.data();
c32.write(unsafe { from_raw_parts(buf, rem) });
offset += rem;
}
println!("{:08x} in {:.3}", c32.sum32(), t0.elapsed().as_secs());
Ok(())
}
Это предназначено для отображения в памяти файла, предоставленного в командной строке, и вычисления его CRC32. Я не ищу других реализаций, которые могли бы достичь этого, потому что я намерен практиковать взаимодействия с libc
функциями.
Программа, кажется, работает правильно, но потребляет гораздо больше времени и ресурсов процессора, чем эквивалентные Go или Java. Программа, которую я написал, хотя я думаю, что она скомпилирована с оптимизациями:
Cargo.toml:
[profile.dev]
opt-level = 3
[package]
name = "mmap"
version = "0.1.0"
authors = ["Gee"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
mmap = "~0.1.1"
crc = "^1.0.0"
Mac OSX, SSD, выполнение программ Go и Rust в различном порядке, чтобы устранить влияниеиз «холодных» файловых буферов и «теплых» буферов - дает около 4 секунд холода для Go и менее секунды для теплого прогона для Go, но всегда 30+ секунд для Rust. Пробовал cargo build --release
тоже. Загрузка ЦП программой Rust также значительно выше (например, 25% для Go, 80 +% для Rust). Я ожидаю, что загрузка ЦП будет происходить в основном из вычислений CRC-32 и копирования содержимого файла между некоторыми буферами. Разница в том, что Rust выполняет здесь дополнительную работу.
Программа Go сканирует XML-файл объемом 1 ГБ, используя тот же метод, syscall.Mmap
, отображая 1 МБ за раз:
$ ../a ~/demo/dest.xml
a772d8c4 in 3.978
Грузовой рейс:
$ cargo run ~/demo/dest.xml
Finished dev [optimized + debuginfo] target(s) in 0.13s
Running `target/debug/mmap /Users/ololo/demo/dest.xml`
a772d8c4 in 33
В комментариях был запрос на показ Java-программы. Вот программа Go, которая может прочитать этот файл за 3,9 секунды:
package main
import(
"fmt"
"hash/crc32"
"os"
"syscall"
"time"
)
func main() {
t0 := time.Now()
fn := os.Args[1]
fd, err := os.Open(fn)
if err != nil {
fmt.Printf("Couldn't open %s", err)
return
}
defer fd.Close()
fi, err := fd.Stat()
if err != nil {
fmt.Printf("Couldn't stat: %s", err)
return
}
sz := fi.Size()
cksum := crc32.NewIEEE()
var off int64
off = 0
for sz > off {
rem := 1024 * 1024
if sz - off < int64(rem) {
rem = int(sz - off)
}
data, err := syscall.Mmap(int(fd.Fd()), off, rem, syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
fmt.Printf("Couldn't mmap at %d", off)
return
}
off += int64(rem)
n, err := cksum.Write(data)
if err != nil || n != len(data) {
fmt.Printf("Somehow could not cksum %d", len(data))
return
}
syscall.Munmap(data)
}
fmt.Printf("%x in %.3f\n", cksum.Sum32(), time.Now().Sub(t0).Seconds())
}