我的文件可能 30 + GB 或更多。此文件中的每一行称为记录,由 2 cols 组成,就像这样
id1 id2
所有这2个ID都是整数(32位)。我的工作是编写一个程序来删除所有重复记录,使记录唯一,最后将唯一的id2输出到文件中。
存在一些限制,最多允许 30G内存,并且通过非多线程/处理程序更有效地完成工作。
最初我提出了一个想法:由于内存限制,我决定读取文件 n 次,每次只保留那些带有id1 % n = i (i = 0,1,2,..,n-1)
的记录。我使用的数据结构是std::map<int, std::set<int> >
,它将id1作为键,并将id2放在id1的std::set
中。
这样,内存约束不会被违反,但速度很慢。我认为这是因为随着std::map
和std::set
变大,插入速度会下降。此外,我需要阅读该文件n次,每轮完成后,我必须清除下一轮的std::map
,这也需要花费一些时间。
我也尝试过哈希,但它也不满足我,我认为即使使用 300W 存储桶也可能会发生太多冲突。
所以,我在这里发布我的问题,帮助你们为我提供更好的数据结构或算法。
非常感谢。
PS
如果可以有效地执行脚本(shell,python),则需要它们。
答案 0 :(得分:8)
除非我忽略了一个要求,否则应该可以在Linux shell上执行此操作
sort -u inputfile > outputfile
许多实现使您能够以并行方式使用sort
:
sort --parallel=4 -u inputfile > outputfile
最多可执行四次并行执行。
请注意,sort
可能暂时在/tmp
中占用大量空间。如果那里的磁盘空间不足,您可以使用-T
选项将其指向磁盘上的备用位置以用作临时目录。
(编辑:)关于效率的一些评论:
sort
已经过高度优化。sort
)。同样,优化这意味着需要做很多工作,而sort
所有这些工作都已完成。sort
的一个缺点是它对输入行的字符串表示进行操作。如果你要编写自己的代码,你可以做的一件事(类似于你已经建议的那样)就是将输入行转换为64位整数并对它们进行散列。如果你有足够的RAM,这可能是一种在速度方面超过sort
的方法,如果你让IO和整数转换真的很快。我怀疑它可能不值得努力,因为sort
易于使用 - 我认为 - 足够快。答案 1 :(得分:1)
我认为如果不使用一堆磁盘,你就不能有效地做到这一点。任何形式的数据结构都会引入您的算法所遭受的大量内存和/或存储开销。所以我希望这里的排序解决方案最好。
我估计你可以一次排序大块的文件,然后合并( ie from merge-sort)之后的那些块。排序一个块后,显然它必须返回磁盘。您可以只替换输入文件中的数据(假设它是二进制文件),或写入临时文件。
就记录而言,您只有一堆64位值。使用30GB RAM,您一次可以容纳近40亿条记录。那太好了。您可以使用快速排序对那些许多进行排序,或者使用mergesort排序一半。你可能不会得到一个连续的内存块。所以你将不得不打破它。这会使quicksort变得有点棘手,所以你可能也想在RAM中使用mergesort。
在最终合并期间,丢弃重复项是微不足道的。合并可能完全基于文件,但最坏的情况是,您将使用相当于输入文件中两倍记录数的磁盘(一个用于临时文件,一个文件用于输出)。如果您可以将输入文件用作临时文件,那么您没有超出RAM限制或磁盘限制(如果有)。
我认为这里的关键是要求它不应该是多线程的。这非常适合基于磁盘的存储。您的大部分时间将用于磁盘访问。所以你想确保你尽可能高效地做到这一点。特别是,当您进行合并排序时,您希望最大限度地减少搜索量。你有大量的内存作为缓冲区,所以我相信你可以非常高效。
因此,假设您的文件为60GB(我认为它是二进制文件),因此大约有80亿条记录。如果您在RAM中进行合并排序,则可以一次处理15GB。这相当于读取和(一次)写入文件一次。现在有四个块。如果你想进行纯合并排序,那么你总是只处理两个数组。这意味着你可以再读取和写入文件两次:一次将每个15GB的块合并为30GB,最后一次合并(包括丢弃重复)。
我认为这太糟糕了。三次进出。如果你找到了一个很好的方法来快速排序,那么你可以通过少量传递文件来做到这一点。我想像deque
这样的数据结构可以很好地工作,因为它可以处理非连续的内存块......但是你可能想要构建自己的并精细调整你的排序算法来利用它。
答案 2 :(得分:1)
而不是std::map<int, std::set<int> >
使用std::unordered_multimap<int,int>
。如果你不能使用C ++ 11 - 自己编写。
std::map
是基于节点的,并且在每次插入时调用malloc,这可能是它很慢的原因。使用未经编码的映射(哈希表),如果您知道记录数,则可以预先分配。即使不这样做,malloc的数量也将是O(log N)
而不是O(N)
std::map
。
我敢打赌,这比使用外部sort -u
快几倍,内存效率更高。
答案 3 :(得分:1)
当文件中没有太多重复记录时,此方法可能会有所帮助。
第一遍。为Bloom filter分配大部分内存。从输入文件中哈希每一对并将结果放入Bloom过滤器。将Bloom过滤器找到的每个副本写入临时文件(此文件还将包含一些误报,这些误报不重复)。 第二遍。加载临时文件并根据其记录构建映射。键是std::pair<int,int>
,值是布尔标志。此映射可以实现为std :: unordered_map / boost :: unordered_map,也可以实现为std :: map。
第三遍。再次读取输入文件,搜索地图中的每条记录,如果找不到或者标志尚未设置,则输出id2
,然后设置此标记。