【问题标题】:How to filter a very, very large file如何过滤非常非常大的文件
【发布时间】:2014-05-09 22:12:13
【问题描述】:

我有一个非常大的未排序文件,1000GB 的 ID 对

  1. ID:ABC123 ID:ABC124
  2. ID:ABC123 ID:ABC124
  3. ID:ABC123 ID:ABA122
  4. ID:ABC124 ID:ABC123
  5. ID:ABC124 ID:ABC126

我想为文件过滤

1) 重复

example
ABC123 ABC124
ABC123 ABC124

2) 反向对(丢弃第二次出现)

example
ABC123 ABC124
ABC124 ABC123

过滤后,上面的示例文件看起来像

  1. ID:ABC123 ID:ABC124
  2. ID:ABC123 ID:ABA122
  3. ID:ABC124 ID:ABC126

目前,我的解决方案是这样的

my %hash;

while(my $line = <FH>){
     chomp $line; #remove \n
     my ($id1,$id2) = split / /, $line;
     if(exists $hash{$id1$1d2} || exists $hash{$id2$id1}){
            next;
     }
     else{
         $hash{$id1$id2} = undef ; ## store it in a hash
         print "$line\n";
      }
}

这为较小的列表提供了所需的结果,但对于较大的列表占用了太多内存,因为我将哈希存储在内存中。

我正在寻找一种实现所需内存更少的解决方案。 我的一些想法是

1) 将哈希值保存到文件中,而不是内存中

2) 多次遍历文件

3) 使用 unix sort -u -k1,2 对文件进行排序和唯一化

在stack exchange cs上发帖后,他们提出了一种外部排序算法

【问题讨论】:

  • 如果你切换前两行的ID,其中一个将与第4行重复,对吧?
  • 这是一次性任务还是重复任务?如果是后者,是否有机会在创建文件时不添加重复项?
  • 有多少行或相当于平均行多长? ID 长什么样?像您的示例一样,3 个字母后跟 3 个数字?您希望删除多少行? 10、1000、1% 还是 10%?它们是如何分布的?几行有很多重复?许多行有几个重复?
  • @haifzhan 如果我切换前两行的id,第2行和第4行将被删除。
  • @AndrewMorton 这是一个经常性的任务。让数据同时包含重复和重复数据删除很有用。

标签: algorithm sorting out-of-memory uniqueidentifier


【解决方案1】:

您可以将map reduce 用于任务。

Map-Reduce 是一个批处理框架,可让您轻松地在多台机器之间分配工作,并使用并行处理而无需考虑同步和容错。

map(id1,id2):
    if id1<id2:
        yield(id1,id2)
   else:
        yield(id2,id1)

reduce(id1,list<ids>):
   ids = hashset(ids) //fairly small per id
   for each id2 in ids:
       yield(id1,id2)

map-reduce 实现将允许您在几台机器上分发您的工作,而几乎不需要额外的编程工作。
假设每个 ID 都与少量其他 ID 相关联,该算法还需要对数据进行线性(并且相当少)次数的遍历,并且需要相当少量的额外内存。

请注意,这将改变对的顺序(在某些情况下使第一个 id 成为第二个)
如果原始 ID 的顺序确实很重要,您可以很容易地用一个额外的字段来解决它。
还要注意,数据的顺序是改变的,使用map-reduce是没有办法克服的。

为了提高效率,您可能想要添加一个组合器,在这种情况下,它的作用与 reducer 相同,但它是否真的有帮助在很大程度上取决于数据。

Hadoop 是一个实现 Map-Reduce 的开源库,在社区中被广泛使用。

【讨论】:

  • 所以你说的是**1)使用更多的机器来获取我需要的内存** 2)如果数据在整个文件中随机分布,我该怎么做知道我正在寻找的内容是否在我正在搜索的块中?每个id都可以关联非常多的IDS
  • 投反对票的人可以发表评论吗?这个答案在处理大数据时给出了真正的工程解决方案,1TB 通常被认为是大数据。
  • 您能否提供更多细节,我知道 MapReduce 是一个框架,而 Hadoop 是用于存储的,如何在 Hadoop 上使用 MapReduce 处理那些 1000GB 的大数据?
  • @user3574820 这不是我要说的。工作被分成块,你处理每个块并根据顺序产生 (id1,id2),接下来 - 减速器确保你在同一台机器上处理所有具有相同第一个 id 的对。该框架会为您处理它。
  • 不是我的反对意见(从来都不是),但 map-reduce 会自动处理数据不适合所涉及机器的组合内存的情况吗?
【解决方案2】:

根据您的数据的详细信息(请参阅我对问题的评论)Bloom filter 可能是通过两次通行证逃脱的简单方法。在第一遍中,在对第一个和第二个值进行排序后,将每一对插入过滤器,并生成一组可能的重复项。在第二遍中,使用一组可能的重复项过滤文件。这显然要求(可能)重复的集合本身并不大。

考虑到数据集的特征 - 最多约 250 亿个唯一对,每对大约 64 位 - 结果将约为 200 GB。因此,您要么需要大量内存、多遍或多台机器。即使是布隆过滤器也必须很大才能产生可​​接受的错误率。

sortbenchmark.org 可以提供一些关于所需内容的提示,因为该任务与排序没有什么不同。 2011 年的获胜者使用了 66 个节点,每个节点配备 2 个四核处理器、24 GiB 内存和 16500 GB 磁盘,并在 59.2 秒内排序了 1353 GB。

【讨论】:

  • 在单台机器上处理 1TB 的数据将是危险的,尤其是因为 cmets 建议这将被多次执行(在多个数据集上)。
  • 使用 HDD,您可以在 2 小时内读取 1 TB,使用 RAID 系统更快,使用 SSD 更快。您仍然可以通过在不同机器上处理不同文件来扩展到多台机器。
【解决方案3】:

除了推出自己的巧妙解决方案之外,您还可以将数据添加到数据库中,然后使用 SQL 获取所需的子集。很多大神已经解决了查询“大数据”的问题,而 1000GB 并不是真的那么,所有的东西都考虑...

【讨论】:

  • 请提供解决问题的 SQL 查询。简单地将数据放入数据库是行不通的。
  • 我认为这应该是一个新问题,适当标记。
  • 这个问题是关于从数据集中删除重复的,我不明白你的回答是如何回答这个问题的。
  • 这个问题是关于“如何过滤一个非常大的文件”——看看标题就知道了。如果 OP 需要帮助构建将删除重复项等的 SQL 查询,那么他应该提出一个标有“SQL”的新问题,因为这样他会得到更多帮助。
  • OP 想要过滤他的文件/数据集。您建议使用 SQL,但没有指定哪种 SQL/什么查询。这就像说“过滤你的文件只是使用脚本”。但是什么脚本? in 这个脚本应该是什么?只是“脚本”不是答案,“SQL”也不是。您可以尝试对数据进行排序,或调用“distinct”,或使用“group by”或其他方式。所有这些解决方案都有不同的成本,并不是所有的(如果有的话)都可以真正适用。 “使用 SQL”这个答案太笼统了,无法真正解决问题。
【解决方案4】:

您的方法几乎没有问题,您只需将哈希移动到磁盘而不是将它们保存在内存中。但是,让我们一步一步来。

重新排序 ID

处理具有不同 ID 顺序的记录很不方便。因此,如果可能,请重新排序 ID,或者,如果没有,请为每个具有正确顺序的记录创建附加键。我假设您可以重新排序 ID(我在 Bash 中不是很好,所以我的代码将在 Python 中):

with open('input.txt') as file_in, open('reordered.txt', 'w') as file_out:
    for line in file_in:
        reordered = ' '.join(sorted(line.split(' ')))  # reorder IDs
        file_out.write(reordered + '\n')

按哈希分组记录

您不能一次过滤所有记录,但可以将它们分成合理数量的部分。每个部分都可以通过其中的记录哈希来唯一标识,例如:

N_PARTS = 1000
with open('reordered.txt') as file_in:
    for line in file_in: 
        part_id = hash(line) % N_PARTS # part_id will be between 0 and (N_PARTS-1)
        with open('part-%8d.txt' % part_id, 'a') as part_file:
            part_file.write(line + '\n')

选择 has 函数在这里很重要。我使用了标准 Python 的 hash()(模块 N_PARTS),但您可能需要使用另一个函数,它给出了每个散列接近 uniform 的记录数分布。如果散列函数或多或少可以正常工作,那么您将获得 1000 个约 100Mb 的小文件,而不是 1 个 1Tb 的大文件。最重要的是,您可以保证不同部分中没有 2 条相同的记录。

注意,为每一行打开和关闭部分文件并不是一个好主意,因为它会产生无数的系统调用。事实上,更好的方法是保持文件打开(您可能需要增加您的ulimit -f),使用批处理甚至写入数据库 - 这取决于实现,而出于演示的目的,我将保持代码简单。

过滤每个组

100Mb 文件更容易处理,不是吗?您可以将它们加载到内存中并使用哈希集轻松删除重复项:

unique = set([])
for i in range(N_PARTS):                          # for each part
    with open('part-%8d.txt') as part_file: 
        file line in part_file:                   # for each line
            unique.add(line)
with open('output.txt', 'w') as file_out:
    for record in unique:
        file_out.write(record + '\n')

这种方法使用一些繁重的 I/O 操作和 3 遍,但它在时间上是线性的并且使用可配置的内存量(如果您的部件对于单台机器来说仍然太大,只需增加 N_PARTS)。

【讨论】:

    【解决方案5】:

    因此,如果这是我,我将采用 @Tom 在另一个答案中描述的数据库路线。我在这里使用 Transact SQL,但似乎大多数主要 SQL 数据库都有类似的窗口/排名row_number() 实现(MySQL 除外)。

    我可能会运行两次扫描方法,首先将id1id2 列重写到新表中,以便“最低”值在id1 中,最高值在id2 中。

    这意味着接下来的任务就是在这个重写的表中找到骗子。

    最初,您需要将源数据批量复制到数据库中,或者生成一大堆insert 语句。我已经在这里进行了插入,但更喜欢大数据的批量插入。不同的数据库有不同的方法来做同样的事情。

    CREATE TABLE #TestTable
    (
        id int,
        id1 char(6) NOT NULL,
        id2 char(6) NOT NULL
    )
    
    insert into 
    #TestTable (id, id1, id2) 
    values 
        (1, 'ABC123', 'ABC124'),
        (2, 'ABC123', 'ABC124'),
        (3, 'ABC123', 'ABA122'),
        (4, 'ABC124', 'ABC123'),
        (5, 'ABC124', 'ABC126');
    
    select 
        id, 
        (case when id1 <= id2 
            then id1 
            else id2 
        end) id1,
        (case when id1 <= id2 
            then id2 
            else id1 
        end) id2
        into #correctedTable 
    from #TestTable
    
    create index idx_id1_id2 on #correctedTable (id1, id2, id)
    
    ;with ranked as
    (select 
        ROW_NUMBER() over (partition by id1, id2 order by id) dupeRank, 
        id,
        id1,
        id2
     from #correctedTable)
    
    select id, id1, id2 
      from ranked where dupeRank = 1
    
    drop table #correctedTable
    drop table #TestTable
    

    这给了我们结果:

    3 ABA122 ABC123 1 ABC123 ABC124 5 ABC124 ABC126

    【讨论】:

    • 感谢 SQL 中的解决方案,我忠实地支持您的回答。我不知道row_number...partition by 是如何工作的,但我认为它使用桶排序,这是非常合理的。此外,您可能会注意到您的解决方案基本上与我的解决方案相同 - 两者都简化为在线性时间内对记录进行分组。一些区别:你使用 (id1, id2) 作为分组函数,而我处理更抽象的哈希函数;你索引你的数据进行分区,而我在写入文件时在线分区记录。其余的几乎相同,据我所知,这是该任务唯一有效的算法。
    【解决方案6】:

    我不想回答这个问题,只是将我的 0.02 欧元添加到其他答案中。

    对我来说,必须做的是将任务拆分为多个较小的任务,正如已经建议的那样。控制流和数据结构。

    Merge Sort was used with Tape Drives 对大数据量(大于内存,大于随机访问磁盘)进行排序的方式。在今天,这意味着存储分布在多个(网络)磁盘或网络磁盘扇区中。

    已经有语言甚至操作系统支持这种不同粒度的分布。大约 10 年前,我有过这类任务的热门人选,但我不记得他们的名字了,从那以后事情发生了变化。

    第一个是分布式Linda Operating System,并行处理器根据需要连接/断开。基本协调结构是巨大的分布式Tuple Space 数据结构,处理器在其中读取/写入任务并写入结果。

    具有类似工作分配的最新方法是Multi agent systemsCzech Wikipedia article 可能包含更多链接)

    相关的维基百科文章是Parallel ComputingSupercomputer Operating SystemsList of concurrent and parallel programming languages

    我并不是说您应该在超级计算机上购买一些处理器时间并在那里运行计算。我将它们列为要研究的算法概念。

    因为很多时候会有一些免费或开源软件解决方案可供您使用,这些解决方案可以让您在小型环境中做同样的事情。从便宜的软件和可用的硬件开始。例如回到 1990 年的大学,我们利用计算机实验室的夜间时间计算 ray-traced 3D images。这是一个计算量非常大的过程,因为对于每个像素,您必须投射“射线”并计算其与场景模型的碰撞。在一台带有一些眼镜和镜子的场景的机器上,它每秒运行 1 个像素(C++ 和优化的汇编语言代码)。在实验室,我们有大约 15 台 PC 可用。所以最终时间可能会减少约 15 倍(I386、I486 和 320x200 256 色的图像)。图像被分成独立的任务,并行计算,然后合并为一个。该方法在当时具有很好的扩展性,类似的方法在今天也会对您有所帮助。

    一直存在并且永远都会有类似“大数据”的东西,大到无法放入 RAM 中,无法放入磁盘,并且无法在有限时间内在一台计算机上进行计算。

    从计算机诞生的第一天起,此类任务就已成功解决。 B-Tree、磁带机、Seek time、Fortran、Cobol、IBM AS/400 等术语都来自那个时代。如果您像那个时代的工程师,那么您肯定会做出一些聪明的事情:)

    编辑:实际上,您可能正在寻找External Sorting

    【讨论】:

      猜你喜欢
      • 2013-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多