【问题标题】:Reading in a file and breaking it into 100 smaller files is incredibly slow读入一个文件并将其分成 100 个较小的文件非常慢
【发布时间】:2012-12-14 09:40:20
【问题描述】:

所以我正在读取相对较大的文件 (>= 1GB),其中包含数百万条记录,每条记录都属于一个特定的组。有100组。为了更有效地处理数据,我创建了 100 个文件,每组 1 个。 (在追加模式下使用 fopen。)当我从大文件中读取记录时,我将每个记录写入相应的较小文件。我始终保持所有打开文件的文件指针,这样我就不会打开和关闭每条记录的文件。

这需要非常长的时间,并且读入(和写)的速度不是恒定的。它开始很快,然后会慢慢爬行,然后再次加速,然后缓慢。读取的文件越多,情况似乎越糟。

关于发生了什么的一种可能性是,随着它们变得越来越大,较小的文件需要重新定位到存储中。这将是令人惊讶的,因为我有 47GB 空闲空间(约 500 个)。但我想不出别的。我会看看重新碎片是否有帮助,但与此同时,有人知道发生了什么以及如何解决这个问题吗?有没有办法预先指定要创建的文件的大小,类似于std::vector::reserve

【问题讨论】:

  • 你能贴出演示这个问题的代码吗?
  • “难以置信的长时间”有多慢?秒?分钟?小时?磁盘 I/O 可能很慢,但它与您的情况有何关系?
  • 我想同时打开 100 个文件是个坏主意。磁盘不擅长随机访问,因此在一个区域写入,然后是另一个区域,然后是另一个区域,依此类推,可能会很慢。我建议您先读取大文件并将所有内容存储在RAM中,然后再写入小文件,一个接一个。
  • Fwiw,您可以通过将较小的文件放在单独的主轴上来显着改善大文件的运行。这并不意味着只是一个不同的 逻辑 驱动器;它必须是不同的物理驱动器。这样做,您将消除磁盘上的蝴蝶效应,即必须从大文件到小文件到大文件到小文件来回查找....
  • 这可能是您用来存储这些小文件的文件系统的问题。您能说出您使用的是哪个操作系统和文件系统吗?

标签: c++ c file file-io


【解决方案1】:

您只是看到文件系统缓存已满容量的副作用,然后必须等到空间被实际写入磁盘的数据释放。这是冰川缓慢的。虽然缓存中有空间,但 write() 调用会执行内存到内存的复制,以每秒 5 GB 或更高的速度运行。磁盘写入速度很少优于 30 兆字节/秒。巨大的差异,您正在测量缓存已满时的磁盘写入速度。

您需要更多 RAM 或更快的磁盘。

【讨论】:

    【解决方案2】:

    如果您不能或不愿意重组程序以一次写入一组,请为每个“小”文件(setbufsetvbuf)设置更大的缓冲区。这样做的效果是缓冲区刷新到磁盘将表现出更多的“局部性”,即不是将 X 量的数据刷新 100 次到 100 个不同的文件,而是将 10 倍的数据量刷新到 100 个不同的文件 10 次。

    测试用例程序(故意没有错误检查):

    -- hugefile.h --
    
    struct record
    {
      unsigned int group;
      char data [1020];
    };
    
    
    --- gen-hugefile.c ---
    
    #include <stdio.h>
    #include <stdlib.h>
    
    #include "hugefile.h"
    
    int
    main (int argc, char **argv)
    {
      unsigned int i, nrecords = strtol (argv [1], 0, 10);
      FILE *f;
    
      f = fopen ("hugefile.db", "w");
    
      for (i = 0; i < nrecords; ++i)
        {
          struct record r;
          r.group = rand () % 100;
    
          fwrite (&r, sizeof r, 1, f);
        }
    
      fclose (f);
      return 0;
    }
    
    --- read-hugefile.c ---
    
    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    
    #include "hugefile.h"
    
    FILE *in;
    FILE *out[100];
    
    int
    main ()
    {
      int i;
      char name [128];
      in = fopen ("hugefile.db", "r");
    
    #ifdef BUFFER
      setvbuf (in, malloc (2*BUFFER), _IOFBF, 2*BUFFER);
    #endif
    
      for (i = 0; i < 100; ++i)
        {
          sprintf (name, "out/file%03d.db", i);
          out [i] = fopen (name, "w");
    #ifdef BUFFER
          setvbuf (out [i], malloc (BUFFER), _IOFBF, BUFFER);
    #endif
        }
    
      struct record r;
      while ((i = fread (&r, sizeof r, 1, in)) == 1)
        fwrite (&r, sizeof r, 1, out [r.group]);
    
      fflush (0);
      return 0;
    }
    
    velco@sue:~/tmp/hugefile$ ls
    gen-hugefile.c  hugefile.h  read-hugefile.c
    velco@sue:~/tmp/hugefile$ gcc -O2 gen-hugefile.c -o gen-hugefile
    velco@sue:~/tmp/hugefile$ ./gen-hugefile 1000000
    velco@sue:~/tmp/hugefile$ ls -lh
    total 978M
    -rwxrwxr-x 1 velco velco 8.5K Dec 14 13:33 gen-hugefile
    -rw-rw-r-- 1 velco velco  364 Dec 14 13:31 gen-hugefile.c
    -rw-rw-r-- 1 velco velco 977M Dec 14 13:34 hugefile.db
    -rw-rw-r-- 1 velco velco   61 Dec 14 12:56 hugefile.h
    -rw-rw-r-- 1 velco velco  603 Dec 14 13:32 read-hugefile.c
    velco@sue:~/tmp/hugefile$ gcc -O2 read-hugefile.c -o read-hugefile
    velco@sue:~/tmp/hugefile$ gcc -O2 -DBUFFER=1048576 read-hugefile.c -o read-hugefile-buf
    velco@sue:~/tmp/hugefile$ mkdir out
    velco@sue:~/tmp/hugefile$ time ./read-hugefile
    
    real    0m34.031s
    user    0m0.716s
    sys 0m6.204s
    velco@sue:~/tmp/hugefile$ time ./read-hugefile
    
    real    0m25.960s
    user    0m0.600s
    sys 0m6.320s
    velco@sue:~/tmp/hugefile$ time ./read-hugefile-buf
    
    real    0m20.756s
    user    0m1.528s
    sys 0m5.420s
    velco@sue:~/tmp/hugefile$ time ./read-hugefile-buf
    
    real    0m16.450s
    user    0m1.324s
    sys 0m5.012s
    velco@sue:~/tmp/hugefile$ 
    

    【讨论】:

    • +1 for setvbuf,对此一无所知。但是即使有 1MB 的缓冲区,情况也有了很大的改善,但仍然存在一些缓慢的补丁。
    【解决方案3】:

    在一个进程中只有 100 个打开的文件,或者在一个目录中只有 100 个文件,这不应该成为现代系统的瓶颈。但可以同时随机访问 101 个文件和总共 2 GB 的数据。

    我会这样做:

    从大文件中读取一些记录,将每种类型的记录存储到内存中自己的列表中。读取大约 10 兆字节的记录可能足够大,您将获得不错的性能,但这取决于可用的 RAM(您确实想要使用太多以至于操作系统开始为此使用交换文件...)。

    然后遍历内存中的 100 个记录列表一次一个并追加到一次一个文件。您可以将所有文件保持打开状态,这可能不是问题,但您也可以根据需要尝试关闭和打开它们,这样一次处理一个文件不会造成太大开销。

    【讨论】:

      【解决方案4】:

      听起来你可以在内存中对它们进行排序,然后一次写出一组。

      【讨论】:

        猜你喜欢
        • 2013-05-11
        • 1970-01-01
        • 1970-01-01
        • 2020-03-22
        • 2019-09-09
        • 1970-01-01
        • 2015-12-29
        • 2021-10-12
        • 2015-08-19
        相关资源
        最近更新 更多