【问题标题】:Rust: Reading and writing CSV performanceRust:读写 CSV 性能
【发布时间】:2020-05-31 12:25:00
【问题描述】:

我正在尝试获得使用 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(())
}

在“发布模式”下构建,然后使用以下命令运行:

powershell -Command "Measure-Command {.\target\release\csv-performance.exe}" 产生 72.79 seconds, 71.01 seconds, 70.77 seconds 三个运行。

大致说来,我看到 10GB(结合读取和写入)的 IO 在 70 秒内,相当于 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 seconds, 13.85 seconds, 10.90 seconds 运行三遍。以11.57 seconds 取平均值,我看到大约 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 repeated 300 times),BBBB...(B repeated 300 times): 279 MB/S

我将尝试实施记录在案的改进,看看它有什么不同,并且可能还会尝试分析 - 任何有关工具的建议都值得赞赏,否则我将只使用 Google。

【问题讨论】:

  • 只是指出执行时间减少>100% 意味着在开始之前完成。所以你可能不得不在这方面调整你的期望。
  • 哇!我的意思是我想知道是否有办法让我的代码快 6 倍。在开始之前完成将是一项壮举!
  • 使用分析器了解您的代码在哪里花费时间。其他一切都只是猜测。
  • 为什么不尝试性能部分中的提示,然后再忽略它们?我写它们是有原因的。如果您的程序仍然比您预期的要慢,请对其进行分析以确定瓶颈。

标签: csv rust


【解决方案1】:

按照您链接的教程中的性能提示,您可以获得相当大的改进。特别是,关键是真正摊销分配并避免 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::Writer::write_byte_record 上。这对我来说似乎是正确的,并且表明您的程序实际上并没有做任何其他次优的事情。除了csv 本身之外,没有真正需要优化的瓶颈。

显然,在我的代码中读取 CSV 时,我所做的工作比简单的副本要多,但令我惊讶的是它会比副本慢约 6 倍。

使用病态或非常受限的输入时很容易感到惊讶。您的示例 CSV 数据非常简单,实际上,如果遵循该格式,则有(显然)更快的方法来解析和写入数据。但是 CSV 解析器不知道这一点,它必须能够处理完整的格式,包括处理转义和引用。 csv 解析器对其进行了大量优化工作,通常应该是现有的更快的 CSV 解析器之一。所以这里比较合适的比较应该是另一个CSV解析器。数据的哑副本比对输入进行重要工作的解析器要快得多也就不足为奇了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-14
    • 1970-01-01
    • 2017-03-13
    • 1970-01-01
    • 1970-01-01
    • 2023-01-25
    • 2020-10-21
    相关资源
    最近更新 更多