【问题标题】:C++ / Fast random access skipping in a large fileC ++ /快速随机访问在大文件中跳过
【发布时间】:2017-05-07 11:35:07
【问题描述】:

我有大文件,包含少量大数据集。每个数据集都包含一个名称和数据集大小(以字节为单位),允许跳过它并转到下一个数据集。

我想快速建立数据集名称的索引。一个文件示例大约 21MB 大,包含 88 个数据集。通过使用std::ifstreamseekg() 在数据集之间跳过来快速读取 88 个名称大约需要 1300 毫秒,我想减少。

所以事实上,我正在读取 88 个大约 30 字节的块,位于 21MB 文件中的给定位置,并且需要 1300 毫秒。

有没有办法改善这一点,还是操作系统和文件系统的限制?我在 Windows 7 64bit 下运行测试。

我知道在文件的开头有一个完整的索引会更好,但是文件格式没有这个,我们不能改变它。

【问题讨论】:

  • 这些是文本文件吗? Windows 是否仍然在行尾存储回车和换行符?如果是这样,是否使用 ifstream 搜索逻辑文本文件,过滤掉回车字符?如果是这样,也许您可​​以以原始二进制模式打开文件,从而防止查找功能必须读取所有字符以避免在查找偏移中包含 CR?但是当然,您的代码可能不得不忽略未过滤的回车。
  • 您可以选择分割成 88 个小文件且数据格式不变吗?
  • 是否有可能将索引信息完全写入一个单独的文件,或者(如果您不能这样做)将其全部写入文件末尾的一个位置?如果你能做到这一点,你就可以避免(几乎)所有的寻求;您只需从一个地方读取少量信息。

标签: c++ file random-access


【解决方案1】:

你可以使用内存映射文件接口(我推荐boost's implementation.

这会将文件打开到应用程序的虚拟页面中,以加快查找时间,而无需返回磁盘。

【讨论】:

  • 我不明白这将如何避免来回寻找磁盘磁头;主要区别是现在操作系统发送的是搜索命令而不是应用程序。
  • @JeremyFriesner 这两种情况都避免了初始磁盘磁头寻道。主要区别在于页面位于内核内存中,您可以直接访问它,而不是通过 iostreams 复制到用户空间中。
  • 这不是 OP 试图最小化的(相对无关紧要的)从内核到用户空间的复制,而是驱动器磁头的寻找。使用 mmap() 并不能避免这些搜索,实际上它可能会增加更多(取决于操作系统的前瞻算法实现如何与 OP 程序访问 mmap() 的内存区域的模式交互)
  • @JeremyFriesner 任何一种情况都会寻找驱动器磁头。如果你认为索引会避免你错了。 OP 要求提供随机访问解决方案,这是您使用内存映射文件的主要原因之一。如果他需要查找名称,然后再次查找同一区域中的某些内容,则 mmap 在 avg 上的性能。是否会降低第二次读取的性能,因为第一次迭代的读取缓存可能已经被其他东西吹走(即导致另一个磁盘读取恢复),而 mmap 在页面内保持持久性。
  • 一个单独的索引将允许从驱动器上的单个位置读取所有必要的数据(只需要 1 次搜索即可到达那里)。如果没有索引,程序要么必须从磁盘顺序读取整个 21MB 文件,然后丢弃其中的大部分,要么查找大约 88 次以仅读取他需要的数据——这两个选项都不会无论您是否通过 mmap() 实现它们,都要快。
【解决方案2】:

您可以扫描文件并使用单独的文件中的键和索引制作自己的标题。根据您的用例,您可以在程序启动和每次文件更改时执行一次。 在访问大数据之前,在较小的文件中查找会为您提供所需的索引。

【讨论】:

    【解决方案3】:

    您可以使用多线程来执行缓冲区排队过程。您可以创建一个自定义结构来存储各种数据量。

    你说:

    每个数据集都包含一个名称和数据集大小(以字节为单位),允许跳过它并转到下一个数据集。

    因此,由于一遍又一遍地打开和关闭文件很慢,您可以一次性读取文件并将其存储到完整的缓冲区对象中,然后对其进行解析或批量存储。这还取决于您是在文本模式还是二进制模式下阅读文件解析的难易程度。我将通过填充多个批次来演示后者,同时从文件中读取缓冲大小的数据量。

    伪代码

    struct Batch {
        std::string name; // Name of Dataset
        unsigned size;    // Size of Dataset
        unsigned indexOffset;  // Index to next read location
        bool empty = true;     // Flag to tell if this batch is full or empty
        std::vector<DataType> dataset; // Container of Data
    }; 
    
    std::vector<Batch> finishedBatches;
    
    // This doesn't matter on the size of the data set; this is just a buffer size on how much memory to digest in reading the file
    const unsigned bufferSize = "Set to Your Preference" 1MB - 4MB etc.
    
    void loadDataFromFile( const std::string& filename, unsigned bufferSize, std::vector<Batch>& batches ) {
    
        // Set ifstream's buffer size 
    
        // OpenFile For Reading and read in and upto buffer size
    
        // Spawn different thread to populate the Batches and while that batch is loading 
        // in data read in that much buffer data again. You will need to have a couple local 
        // stack batches to work with. So if a batch is complete and you reached the next index 
        // location from the file you can fill another batch.
    
        // When a single batch is complete push it into the vector to store that batch.
        // Change its flag and clear its vector and you can then use that empty batch again.
    
        // Continue this until you reached end of file.           
    
    }
    

    这将是一个 2 线程系统。用于打开和读取文件并从文件中查找的主线程,工作线程填充批次并将批次推入容器并交换以使用下一批。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-06
      • 1970-01-01
      • 2014-02-05
      • 2015-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多