我正在尝试获取使用 Rust 读取和写入“大型”CSV 文件的最大速度的指示性测量值。
我有一个包含 1 亿行相同行的测试 CSV 文件:
SomeLongStringForTesting1, SomeLongStringForTesting2
此文件在磁盘上的大小为 4.84GB。
我已经编写(主要是复制!)以下使用 csv: 1.1.3
crate 的代码:
use std::error::Error;
fn main() {
read_and_write("C:/Dev/100MillionRows.csv", "C:/Dev/100MillionRowsCopy.csv").unwrap();
}
fn read_and_write(in_file_path: &str, out_file_path: &str) -> Result<(), Box<Error>> {
let mut rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_path(in_file_path)?;
let mut wtr = csv::WriterBuilder::new()
.from_path(out_file_path)?;
for result in rdr.records() {
let record = result?;
wtr.write_record(record.iter())?;
}
wtr.flush()?;
Ok(())
}
在“ Release模式”下构建,然后使用以下命令运行:
powershell -Command "Measure-Command {.\target\release\csv-performance.exe}"
三个运行产生 72.79 秒、71.01 秒、70.77 秒
.
大约来说,我在 70 秒内看到 10GB(读写结合)的 IO,相当于 142MB/S。这大约是 Windows 在任务管理器中报告的磁盘使用情况。
这感觉可能会很慢,原因如下:
winsat disk -drive c
产生:
Windows System Assessment Tool
> Running: Feature Enumeration ''
> Run Time 00:00:00.00
> Running: Storage Assessment '-drive c -ran -read'
> Run Time 00:00:01.31
> Running: Storage Assessment '-drive c -seq -read'
> Run Time 00:00:05.36
> Running: Storage Assessment '-drive c -seq -write'
> Run Time 00:00:03.17
> Running: Storage Assessment '-drive c -flush -seq'
> Run Time 00:00:00.80
> Running: Storage Assessment '-drive c -flush -ran'
> Run Time 00:00:00.73
> Dshow Video Encode Time 0.00000 s
> Dshow Video Decode Time 0.00000 s
> Media Foundation Decode Time 0.00000 s
> Disk Random 16.0 Read 541.88 MB/s 8.3
> Disk Sequential 64.0 Read 1523.74 MB/s 8.8
> Disk Sequential 64.0 Write 805.49 MB/s 8.3
> Average Read Time with Sequential Writes 0.219 ms 8.6
> Latency: 95th Percentile 1.178 ms 8.2
> Latency: Maximum 7.760 ms 8.2
> Average Read Time with Random Writes 0.199 ms 8.9
这表明我的磁盘(相当不错的 SSD)的功能更多。
如果我只是复制文件:
powershell -Command "Measure-Command {Copy-Item "C:/Dev/100MillionRows.csv"-Destination "C:/Dev/100MillionRowsCopy.csv"}"
三次运行需要 9.97 秒、13.85 秒、10.90 秒
。取 11.57 秒
的平均值,我看到大约 860 MB/S 的 IO。这更符合我磁盘的局限性。
很明显,在我的代码中读取 CSV 时,我所做的工作比简单的副本多,但令我惊讶的是,它比副本慢约 6 倍。
想知道为什么会这样以及如何提高我的 Rust 代码的性能吗?我是 Rust 的新手,所以很可能那里有一些值得点头的东西!我知道文档的性能部分 https://docs.rs/csv/1.0.0/csv/tutorial/index.html#performance ,但这些似乎是 50% 左右的性能改进,而不是几百个百分点。
更新 1
在不修改代码的情况下,一些进一步的测试表明速率不一致,因为我改变了 1 亿行的行中字符串的大小:
A,B
:18 MB/S
SomeLongStringForTesting1, SomeLongStringForTesting2
:142 MB/S
AAAA...(A 重复 300 次),BBBB...(B 重复 300 次)
:279 MB/S
我将着手实现记录在案的改进,看看它有什么不同,并且可能还会尝试分析 - 任何有关工具的建议都会受到赞赏,否则我将只使用 Google。
最佳答案
按照您链接的教程中的性能提示,您可以获得相当大的改进。特别是,关键实际上是摊销分配并避免 UTF-8 检查,这两者都发生在您的代码中。也就是说,您的代码会在内存中为 CSV 文件中的每一行分配一条新记录。它还检查每个字段的有效 UTF-8。这两者都有成本,但它们确实提供了一个相当简单的 API,而且速度相当快。
此外,教程中未提及的一个技巧是尽可能使用 csv::Writer::write_byte_record
而不是 csv::Writer::write_record
.后者更灵活,但前者对输入的约束更大一些,可以在常见场景下更高效地实现写入。
总的来说,进行这些更改非常容易:
use std::error::Error;
fn main() {
read_and_write("rows.csv", "rows-copy.csv").unwrap();
}
fn read_and_write(
in_file_path: &str,
out_file_path: &str,
) -> Result<(), Box<dyn Error>> {
let mut rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_path(in_file_path)?;
let mut wtr = csv::WriterBuilder::new()
.from_path(out_file_path)?;
let mut record = csv::ByteRecord::new();
while rdr.read_byte_record(&mut record)? {
wtr.write_byte_record(&record)?;
}
wtr.flush()?;
Ok(())
}
这是你的代码在我的 Linux 系统上的时间:
$ time ./target/release/csvsoperf
real 21.518
user 19.315
sys 2.189
maxmem 6 MB
faults 0
这是我更新代码的时间:
$ time ./target/release/csvsoperf
real 12.057
user 9.924
sys 2.125
maxmem 6 MB
faults 0
分析更快的代码,大约 56% 的时间花在 csv::Reader::read_byte_record
上,而大约 29% 的时间花在 csv::作家::write_byte_record
。这对我来说似乎是正确的,并且表明您的程序实际上并没有做任何次优的事情。在 csv
本身之外没有真正的瓶颈需要优化。
Clearly when reading the CSV in my code I'm doing more work than a simple copy, but I was surprised that it would be ~6 times slower than the copy.
使用病态或非常受限的输入时很容易感到惊讶。您的示例 CSV 数据非常简单,事实上,如果数据遵循该格式,那么(显然)有更快的方法来解析和写入数据。但是 CSV 解析器不知道这一点,并且必须能够处理完整格式,其中包括处理转义和引用。 csv
解析器已经做了很多优化工作,通常应该是现有的更快的 CSV 解析器之一。所以这里比较合适的比较应该是另一个CSV解析器。数据的哑副本比对输入进行重要工作的解析器快得多也就不足为奇了。
关于csv - Rust:读写 CSV 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62116183/