2017-03-04 35 views
14

使用下面的程序我尝试测试使用std::ofstream可以多快写入磁盘。为什么我的C++磁盘写入测试比使用bash的简单文件复制要慢得多?

写入1 GiB文件时,我可以达到大约300 MiB/s。

但是,使用cp命令的简单文件副本至少快两倍。

我的程序是否达到了硬件限制,还是可以加快速度?

#include <chrono> 
#include <iostream> 
#include <fstream> 

char payload[1000 * 1000]; // 1 MB 

void test(int MB) 
{ 
    // Configure buffer 
    char buffer[32 * 1000]; 
    std::ofstream of("test.file"); 
    of.rdbuf()->pubsetbuf(buffer, sizeof(buffer)); 

    auto start_time = std::chrono::steady_clock::now(); 

    // Write a total of 1 GB 
    for (auto i = 0; i != MB; ++i) 
    { 
     of.write(payload, sizeof(payload)); 
    } 

    double elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - start_time).count(); 
    double megabytes_per_ns = 1e3/elapsed_ns; 
    double megabytes_per_s = 1e9 * megabytes_per_ns; 
    std::cout << "Payload=" << MB << "MB Speed=" << megabytes_per_s << "MB/s" << std::endl; 
} 

int main() 
{ 
    for (auto i = 1; i <= 10; ++i) 
    { 
     test(i * 100); 
    } 
} 

输出:

Payload=100MB Speed=3792.06MB/s 
Payload=200MB Speed=1790.41MB/s 
Payload=300MB Speed=1204.66MB/s 
Payload=400MB Speed=910.37MB/s 
Payload=500MB Speed=722.704MB/s 
Payload=600MB Speed=579.914MB/s 
Payload=700MB Speed=499.281MB/s 
Payload=800MB Speed=462.131MB/s 
Payload=900MB Speed=411.414MB/s 
Payload=1000MB Speed=364.613MB/s 

更新

我从std::ofstream改变为fwrite

#include <chrono> 
#include <cstdio> 
#include <iostream> 

char payload[1024 * 1024]; // 1 MiB 

void test(int number_of_megabytes) 
{ 
    FILE* file = fopen("test.file", "w"); 

    auto start_time = std::chrono::steady_clock::now(); 

    // Write a total of 1 GB 
    for (auto i = 0; i != number_of_megabytes; ++i) 
    { 
     fwrite(payload, 1, sizeof(payload), file); 
    } 
    fclose(file); // TODO: RAII 

    double elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - start_time).count(); 
    double megabytes_per_ns = 1e3/elapsed_ns; 
    double megabytes_per_s = 1e9 * megabytes_per_ns; 
    std::cout << "Size=" << number_of_megabytes << "MiB Duration=" << long(0.5 + 100 * elapsed_ns/1e9)/100.0 << "s Speed=" << megabytes_per_s << "MiB/s" << std::endl; 
} 

int main() 
{ 
    test(256); 
    test(512); 
    test(1024); 
    test(1024); 
} 

这对于一个1个吉布文件提高了速度,以668MiB /秒:

Size=256MiB Duration=0.4s Speed=2524.66MiB/s 
Size=512MiB Duration=0.79s Speed=1262.41MiB/s 
Size=1024MiB Duration=1.5s Speed=664.521MiB/s 
Size=1024MiB Duration=1.5s Speed=668.85MiB/s 

这是一样快dd

time dd if=/dev/zero of=test.file bs=1024 count=0 seek=1048576 

real 0m1.539s 
user 0m0.001s 
sys 0m0.344s 
+0

你测试与优化你的程序的发布版本?你有没有尝试增加缓冲区大小? –

+0

不应该是'double megabytes_per_ns = MB/elapsed_ns;'? – zett42

+0

此外,你应该打开二进制模式的流,以公平地比较它与其他写作方法。使用(“test.file”,std :: ios :: binary)的std :: ofstream。 ('ofstream'和'fwrite'之间的性能非常接近(差异在测量误差范围内)。编译器VC++ 2017。 – zett42

回答

14

首先,你没有真正测量磁盘写入速度,但(部分)的数据写入OS磁盘缓存的速度。要真正衡量磁盘写入速度,在计算时间之前应将数据刷新到磁盘。在不刷新的情况下,根据文件大小和可用内存的不同,可能会有所不同。

计算中似乎也有错误。您没有使用MB的值。

还要确保的缓冲区大小是二的幂,或至少在盘页大小的倍数(4096个字节):char buffer[32 * 1024];。你也可以做​​。 (看起来你在添加计算的编辑中将其从1024更改为1000)。

不要使用流将数据写入到磁盘的(二进制)缓冲,而是直接写入文件,使用FILE*, fopen(), fwrite(), fclose()。有关示例和一些时间,请参见this answer


要复制文件:在只读方式打开源文件,可能的话,只进模式,并使用fread(), fwrite()

while fread() from source to buffer 
    fwrite() buffer to destination file 

这应该给您媲美的速度操作系统文件副本的速度(您可能想测试一些不同的缓冲区大小)。

威力略快使用内存映射:

open src, create memory mapping over the file 
open/create dest, set file size to size of src, create memory mapping over the file 
memcpy() src to dest 

对于大文件应该使用较小的映射视图。

7
  1. Streams are slow
  2. cp直接使用系统调用read(2)mmap(2)
+0

链接问题为5年,同时实现可能有所改进。代码示例链接已死亡。 – zett42

4

我敢打赌,这是CP或文件系统内部的聪明之处。如果它在CP内部,那么它可能是你正在复制的文件有很多的0,并且cp正在检测这个文件并且编写了一个文件的sparse版本。 cp的手册页上说:“默认情况下,稀疏的SOURCE文件被粗略的启发式检测到,相应的DEST文件也变得稀疏。”这可能意味着一些事情,但其中之一是,cp可以使文件的稀疏版本,这将需要更少的磁盘写入时间。

如果它在您的文件系统中,那么它可能是Deduplication

作为一个长期的第三,它也可能是您的操作系统或您的磁盘固件中的某些东西,它将读写转换为一些专用指令,不需要像程序所需的那么多同步(较低的总线使用意味着更少的延迟)。

3

您正在使用相对较小的缓冲区大小。小缓冲区意味着每秒更多操作,这会增加开销。磁盘系统在收到读取/写入请求并开始处理之前会有少量延迟;一个更大的缓冲区分摊成本会更好一些。较小的缓冲区也可能意味着磁盘花费更多时间寻找。

您不会发出多个同时发出的请求 - 您需要在下一次启动前完成一次读取。这意味着磁盘在无所事事的地方可能会有空闲时间。由于所有写操作都依赖于所有读操作,并且您的读操作是串行的,所以您正在挨饿读取请求的磁盘系统(因为写操作会从读取中消失)。

总在所有请求读取的字节读取请求应比磁盘系统的带宽时延积较大。如果磁盘的延迟时间为0.5毫秒,性能为4 GB /秒,那么您希望始终有4 GB * 0.5 ms = 2 MB的读取值。

你没有使用任何操作系统的提示,你正在做顺序阅读。

要解决这个问题:

  • 更改您的代码在任何时候都有一个以上的未完成的读请求。
  • 有足够的未完成读取请求,以至于您至少等待2 MB的数据。
  • 使用posix_fadvise()标志来帮助优化OS磁盘调度和页面缓存。
  • 考虑使用mmap来减少开销。
  • 每个读取请求使用较大的缓冲区大小以减少开销。

此答案的更多信息: https://stackoverflow.com/a/3756466/344638

0

的问题是,您所指定太小缓冲区为您的fstream

char buffer[32 * 1000]; 
std::ofstream of("test.file"); 
of.rdbuf()->pubsetbuf(buffer, sizeof(buffer)); 

您的应用程序在用户模式下运行。要写入磁盘,在内核模式下执行的流向调用系统write函数。然后write将数据传输到系统缓存,然后传输到HDD缓存,然后将其写入磁盘。

此缓冲区大小影响系统调用次数(每32 * 1000字节调用一次)。在系统调用期间,操作系统必须将执行上下文从用户模式切换到内核模式,然后返回。切换上下文是开销。在Linux中,它相当于2500-3500个简单的CPU命令。因此,您的应用程序在上下文切换中花费的CPU时间最多。

在你的第二个应用程序使用默认情况下使用更大的缓冲

FILE* file = fopen("test.file", "w"); 

文件中,这就是为什么它产生更高效的代码。您可以尝试使用setvbuf指定小缓冲区。在这种情况下,您应该看到相同的性能下降。

请注意在你的情况下,瓶颈不是硬盘性能。它是上下文切换

image

相关问题