【问题标题】:Why is fwrite so much slower in binary mode?为什么 fwrite 在二进制模式下这么慢?
【发布时间】:2016-12-14 20:30:59
【问题描述】:

我有一个大约 2GB 的原始二进制数据缓冲区,存储在 QByteArray 中,我正在尝试以最快的方式编写它。因为QFile 的速度明显较慢,所以我又开始涉足 C/C++ 风格的写作,我已经有几年没做过了,所以我很生疏。

示例代码:

QFile in("D:/input.las");
in.open(QIODevice::ReadOnly);
QByteArray junk = in.readAll();
in.close();

QFile test1("D:/samplelas/bigset2/out/_test1.las");
test1.open(QIODevice::WriteOnly | QIODevice::Truncate);
if(test1.isOpen())
{
    QElapsedTimer t;
    t.start();
    test1.write(junk);
    test1.close();
    qDebug("Round\t%'i\tTest QFile\ttook\t%'i", i+1, t.elapsed());
}

FILE* test2 = fopen("D:/_test2.las", "wb");
if(test2)
{
    QElapsedTimer t;
    t.start();
    fwrite(junk.constData(), sizeof(char), junk.size(), test2);
    fclose(test2);
    qDebug("Round\t%'i\tTest wb\ttook\t%'i", i+1, t.elapsed());
}

FILE* test3 = fopen("D:/_test3.las", "w");
if(test3)
{
    QElapsedTimer t;
    t.start();
    fwrite(junk.constData(), sizeof(char), junk.size(), test3);
    fclose(test3);
    qDebug("Round\t%'i\tTest w\ttook\t%'i", i+1, t.elapsed());
}

我注意到使用"w" 而不是"wb" 会损坏输出,因为在我的Windows 机器上解释了“换行符”字符(新手错误)。尽管如此,结果还是很有希望的,所以我再次尝试使用“wb”。

我非常吃惊地发现二进制模式慢了 5-10 倍,我不知道为什么。如果有的话,非二进制模式写入应该更快,因为它没有被解释,只是原始数据。

我错过了什么?

Edit1:在 MSVC2010、Windows 7x64 Pro 上使用 Qt 4.8.6 在发布模式下进行测试。

Edit2:添加 QFile 测试用例并澄清问题的解释。

【问题讨论】:

  • 如果你翻转代码并先进行非二进制写入会发生什么?还是更快吗?
  • @Phlucious 听起来像磁盘缓存。在第一个文件上它错过了,所以它写得很长,但第二个文件命中了,所以它要短得多。
  • @NathanOliver 我通过独立运行每个测试 10 次(10 次使用“wb”和 10 次使用“w”,在两者之间完全重新启动)重新进行了基准测试,并且二进制文件仍然明显慢 - 27 秒对 5 秒。
  • 如果您没有发布一个完整的、独立的测试用例,就无法回答任何问题——最好是一开始就没有损坏的测试用例。您的主要问题很可能是您写入磁盘,这会扭曲结果。改为写入 NUL。此外,QFile 不应比原始写入慢。
  • @KubaOber 使用 QFile::write 写入相同的数据块介于二进制和非二进制 fwrite 方法之间——大约 15 秒。我不知道如何才能比我拥有的更多地简化提供的测试用例......只需注释掉当时没有执行的两个测试。你写给 NUL 的建议没有意义......写到磁盘正是我正在测试的。

标签: c++ qt optimization io


【解决方案1】:

也许我完全错了,但我看到(基于您的代码)性能差异的原因是与这些行中的 fwrite 函数参数混淆:

fwrite(junk.constData(), sizeof(char), junk.size(), <file>);

让我们回顾一下该函数的定义:

size_t fwrite (const void* buff, size_t size, size_t count, FILE* stream);

地点:

  • size 是要写入的块大小。
  • count是要写入的块数
  • stream 在这种情况下是要写入的文件。
  • 该函数返回一个size_t,其中包含成功写入的块数。

现在,这些参数的含义完全取决于fwrite 的实现代码。在某些情况下(如 FreeBSD 的 libc),实现只是简单地执行类似total = size*count 的操作并进行一次系统调用来写入整个缓冲区。但在其他实现中(如微软的),情况就不同了:

文本模式下的含义

fwrite 将:

  • size 字节的块复制到 BUFSIZ 字节大小的内部内存缓冲区(通常为 512,如果用户未更改)
  • 转换所有换行符
  • 当内部BUFSIZ 缓冲区已满时,进行一次系统调用以写入该缓冲区。
  • 如果无法完成该系统调用,则返回到目前为止已写入的块数。
  • 重新开始,直到 count 块被写入或发生错误。

在您的代码中:fwrite 将使 aprox. (junk.size()/512) 个系统调用。

二进制模式的含义

fwrite 将:

  • 从 1 到 count 迭代循环:
  • 在每次迭代中,它将进行一次系统调用以将一个 size 字节块写入该文件。
  • 如果无法完成该系统调用,则返回到目前为止已写入的块数。
  • 重新开始,直到 count 块被写入或发生错误。

在您的代码中:fwrite 将比在文本模式下多进行约 512 倍的系统调用(为什么它只慢 10 倍?这是另一个问题)。

【讨论】:

  • 有趣...如果这是真的,那么通过交换sizecount,我不会看到明显的性能提升吗?情况似乎并非如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-14
  • 1970-01-01
  • 1970-01-01
  • 2014-03-01
  • 1970-01-01
  • 2013-05-03
相关资源
最近更新 更多