随机化超大文件内容排序的有效方法是什么?
What's an efficient way to randomize the ordering of the contents of a very large file?
对于我的神经网络训练项目,我有一个非常大的输入数据文件。文件格式是二进制的,它由大量的固定尺寸记录组成。该文件当前〜13GB,但将来可能会变得更大。出于这个问题的目的,让我们假设它太大了,无法立即将所有内容放在我的计算机的RAM中。
今天的问题涉及我编写的一个小实用程序(在C 中,尽管我认为语言的选择在这里并不重要,因为在任何语言中可能会遇到相同的问题(,旨在阅读大文件和输出一个类似的大文件 - 输出文件将包含与输入文件相同的数据,除了记录被随机订购。
为此,i mmap()
输入文件到内存中,然后生成一个从1到n的整数列表(其中n是输入文件中的记录数(,随机洗牌该列表的排序,然后迭代列表,从mmap'd内存区域写入输出文件。
这一切都可以正常工作。问题在于它的扩展不佳。也就是说,随着输入文件的大小变大,进行此转换所需的时间比O(n(更快。它已经成为我工作流程的瓶颈的地步。我怀疑问题是I/O系统(对于MacOS/X 10.13.4,使用Mac Pro垃圾桶的内部SSD,以防万一很重要的情况下(进行了优化,以用于顺序读取,并跳到完全随机的位置就缓存/读取/其他I/O优化而言,输入文件几乎是最坏的情况。(我想在旋转磁盘上,由于远见的延迟,它的性能甚至会更糟,但是幸运的是,我至少在这里使用SSD(
所以我的问题是,我可以使用任何聪明的替代策略或优化,以使此文件随机化过程更有效 - 随着输入文件的大小增加,它会更好地扩展?
<</p>如果问题与读取随机文件位置时的交换和随机磁盘访问有关,您至少可以顺序读取输入文件吗?
当您访问MMAP-ED文件中的一些块时,Prefetcher会认为您很快就需要相邻页面,因此它也会加载它们。但是您不会,因此这些页面将被丢弃,并且将浪费加载时间。
- 创建n个topositons的数组,因此sposition [i] = i;
- 随机范围(您使用Knuth的洗牌吗?(;
- 然后置换[i] =输入[i]的目的地。因此,从start依次读取输入数据,然后将它们放入目标文件的相应地点。
也许,这将更加友好。当然,随机编写数据也很慢,但是至少您不会从输入文件中浪费预取的页面。
其他好处是,当您处理了数百万个输入数据页面时,这些GB将从RAM卸载,而您将不再需要它们,因此您不会污染实际的磁盘缓存。请记住,实际的内存页面大小至少为4K,因此,即使您随机访问MMAP-ED文件的1个字节,也应将至少4K数据从磁盘读取到缓存中。
我建议不要使用 mmap()
-根本没有任何记忆压力,除非您重新阅读相同的内容数据多次,mmap()
通常是读取数据的最糟糕的方法。
首先,生成n个随机偏移,然后给定这些偏移,使用pread()
读取数据 - 并使用低级C风格IO。
这使用fcntl()
函数禁用文件的页面缓存。由于您没有重新阅读相同的数据,因此页面缓存可能不错,但是它确实耗尽了RAM,从而减慢了其他内容。在有或没有页面缓存禁用的情况下,尝试一下,然后查看哪个更快。还要注意,我遗漏了所有错误检查:
(我还假设C风格的IO函数在Mac上在namespace std
中,并且我使用C风格的字符串和数组来匹配C-Style IO函数,同时保持代码更简单。(
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
void sendRecords( const char *dataFile, off_t offsets, size_t numOffsets )
{
int fd = std::open( dataFile, O_RDONLY );
// try with and without this
std::fcntl( fd, F_NOCACHE, 1 );
// can also try using page-aligned memory here
char data[ RECORD_LENGTH ];
for ( size_t ii = 0; ii < numOffsets; ii++ )
{
ssize_t bytesRead = std::pread( fd, data, sizeof( data ), offsets[ ii ] );
// process this record
processRecord( data );
}
close( datafd );
}
假设您有一个包含预先计算的随机偏移的文件:
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
void sendRecords( const char *dataFile, const char *offsetFile )
{
int datafd = std::open( dataFile, O_RDONLY );
// try with and without this
std::fcntl( fd, F_NOCACHE, 1 );
int offsetfd = std::open( offsetFile, O_RDONLY );
// can also try using page-aligned memory here
char data[ RECORD_LENGTH ];
for ( ;; )
{
off_t offset;
ssize_t bytesRead = std::read( offsetfd, &offset, sizeof( offset ) );
if ( bytesRead != sizeof( offset ) )
{
break;
}
bytesRead = std::pread( fd, data, sizeof( data ), offset );
// process this record
processRecord( data );
}
std::close( datafd );
std::close( offsetfd );
}
您也可以更快地走,因为该代码会交替进行阅读和处理,并且使用多个线程同时读取和处理可能会更快。使用一个或多个线程将数据读取到预关注的缓冲区中并不难,然后将其排队并发送到处理线程。
多亏了此线程中各个人的建议(尤其是Marc Glisse和Andrew Henle(,我能够减少在13GB输入文件上的程序的执行时间,从〜16分钟开始到〜2分钟。我会在这个答案中记录我如何做到这一点,因为该解决方案不像上面的任何一个答案(这更基于Marc的评论,所以我会给Marc复选框,如果/当他重申评论时作为答案(。
我尝试用PREAD((替换MMAP((策略,但这似乎没有太大的不同。我尝试将f_nocache和其他各种标志传递给fcntl((,但它们似乎没有效果或使事情变慢,所以我决定尝试其他方法。
新方法是以2层方式进行操作:我的程序不是一次从单个记录中读取,而是从输入文件中加载顺序记录的"块"(每个包含大约4MB数据的块(。
块以随机顺序加载,我将块加载到块中,直到我在RAM中持有一定数量的块数据(目前〜4GB,因为这是我的Mac的RAM可以舒适地保持的(。然后,我开始从随机的RAM块中获取随机记录,然后将其写入输出文件。当给定的块不再留下任何记录要抓住时,我将释放该块并从输入文件中加载另一个块。我重复此操作,直到已加载输入文件中的所有块并将其所有记录分发到输出文件。
这是更快的,因为我所有的输出都是严格的顺序,并且我的输入主要是顺序的(即,在每个搜索之后而不是仅〜2kb之后读取4MB数据(。输出的顺序比以前的序列稍小,但我认为这对我来说不会是问题。
- 在C++中初始化向量映射的最有效方法
- 将此布尔值传递给此函数的最有效方法是什么?
- 比较C++变量的最有效方法
- 在 c++ 中解决段树以外的范围查询的有效方法是什么?
- 存储变量的更有效方法是什么?
- 确保套装新鲜度的有效方法
- 当映射包含字符串向量作为值时,从值中获取键的有效方法
- 映射唯一值和重复值的有效方法.可以访问键或值的位置
- 在C++事务之间存储大量字符数据的有效方法
- 在unordered_multimap中精确迭代一次每个键的有效方法
- 一种将 Dart 中的字节数据转换为 C++ 中的无符号字符*的有效方法?
- 检查两个向量是否并行的最有效方法
- 从浮点数中删除小数部分但保留类型的有效方法
- 传递非泛型函数的最有效方法是什么?
- 按升序打印矢量的所有元素直到它为空而没有重复项的最有效方法是什么?
- 创建字符串数组的有效方法
- 返回一个引用C++中另一个类对象的对象的有效方法
- C++去除前x个元素的有效方法,在不改变向量大小的情况下将第x+1个元素推到第一个
- 将一种数据类型的向量复制到同一数据类型的结构向量中的有效方法是什么
- 从std::map值中获取密钥的有效方法