在大型文本文件中搜索字符串 - 在python中分析各种方法

时间:2011-06-02 19:06:13

标签: python performance search profiling large-files

这个问题已被多次询问。花了一些时间阅读答案后,我做了一些快速分析,试用了前面提到的各种方法......

  
      
  • 我有一个 600 MB 文件,其中包含 600万个字符串行(来自DMOZ项目的类别路径)。
  •   
  • 每行的条目都是唯一的。
  •   
  • 我想加载文件一次& 继续搜索以查找数据中的匹配项
  •   

我在下面尝试的三种方法列出了加载文件所需的时间,否定匹配的搜索时间&任务管理器中的内存使用情况


1) set :
    (i)  data   = set(f.read().splitlines())
    (ii) result = search_str in data   
  

加载时间〜10s,搜索时间~0.0s,内存使用量~1.2GB


2) list :
    (i)  data   = f.read().splitlines()
    (ii) result = search_str in data
  

加载时间〜6s,搜索时间~0.36s,内存使用量~1.2GB


3) mmap :
    (i)  data   = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    (ii) result = data.find(search_str)
  

加载时间〜0秒,搜索时间~5.4秒,内存使用量〜NA


4) Hash lookup (using code from @alienhard below):   
  

加载时间〜65秒,搜索时间~0.0秒,内存使用量~250MB


5) File search (using code from @EOL below):   
   with open('input.txt') as f:
       print search_str in f #search_str ends with the ('\n' or '\r\n') as in the file
  

加载时间〜0秒,搜索时间~3.2秒,内存使用量〜NA


6) sqlite (with primary index on url): 
  

加载时间〜0秒,搜索时间~0.0秒,内存使用量〜NA


对于我的用例,只要我有足够的可用内存,似乎使用set是最好的选择。我希望能就这些问题得到一些评论:

  
      
  1. 更好的替代,例如sqlite?
  2.   
  3. 使用mmap 改善搜索时间的方法。我有一个64位的设置。   [编辑]例如绽放过滤器
  4.   
  5. 随着文件大小增加到几GB,有什么方法可以继续使用'set',例如分批分开..
  6.   

[编辑1] P.S.我需要经常搜索,添加/删除值,不能单独使用哈希表,因为我需要稍后检索修改后的值。

欢迎任何意见/建议!

[edit 2]使用答案中建议的方法的结果进行更新 [编辑3]使用sqlite结果更新

解决方案:基于所有分析&反馈,我想我会选择sqlite。第二种方法是方法4. sqlite的一个缺点是数据库大小是带有url的原始csv文件的两倍多。这是由于url上的主要索引

6 个答案:

答案 0 :(得分:12)

如果您需要启动许多连续搜索,变体1非常棒。由于set在内部是一个哈希表,因此它在搜索方面相当不错。但是,构建需要时间,并且只有在数据适合RAM时才能正常工作。

Variant 3适用于非常大的文件,因为您有足够的地址空间来映射它们并且OS缓存足够的数据。你做了全扫描;一旦你的数据停止适应RAM,它就会变得相当慢。

如果您需要在行中进行多次搜索并且无法将数据放入RAM中,那么SQLite绝对是一个不错的主意。将字符串加载到表中,构建索引,SQLite为您构建一个漂亮的b树。即使数据没有,树也可以装入RAM(这有点像@alienhard提出的那样),即使它没有,但是如果需要I / O的数量也会大大降低。当然,您需要创建基于磁盘的SQLite数据库。我怀疑基于内存的SQLite会显着击败变体1。

答案 1 :(得分:9)

使用外部化字符串进行自定义哈希表搜索

为了获得更快的访问时间,您可以执行以下操作:

  • 为每一行计算一个字符串哈希并将其添加到哈希表中,例如index[hash] = position存储该字符串)。如果发生冲突,请将该密钥的所有文件位置存储在列表中。
  • 查找字符串,计算其哈希值并在表格中查找。如果找到密钥,请从文件中读取position处的字符串以验证您确实匹配。如果有多个职位,请检查每个职位,直到找到匹配或没有。

编辑1:按位置替换line_number(由评论者指出,显然需要实际位置而不是行号)

编辑2:为具有自定义哈希表的实现提供代码,这表明此方法比上述其他方法更具内存效率:

from collections import namedtuple 
Node = namedtuple('Node', ['pos', 'next'])

def build_table(f, size):
    table = [ None ] * size
    while True:
        pos = f.tell()
        line = f.readline()
        if not line: break
        i = hash(line) % size
        if table[i] is None:
            table[i] = pos
        else:
            table[i] = Node(pos, table[i])
    return table

def search(string, table, f):
    i = hash(string) % len(table)
    entry = table[i]
    while entry is not None:
        pos = entry.pos if isinstance(entry, Node) else entry
        f.seek(pos)
        if f.readline() == string:
            return True
        entry = entry.next if isinstance(entry, Node) else None
    return False

SIZE = 2**24
with open('data.txt', 'r') as f:
    table = build_table(f, SIZE)
    print search('Some test string\n', table, f)

一行的哈希仅用于索引表(如果我们使用普通的dict,则哈希也将存储为键)。该行的文件位置存储在给定的索引处。使用链接解决冲突,即我们创建链接列表。但是,第一个条目永远不会包含在节点中(这种优化使代码更复杂,但它节省了相当多的空间)。

对于600万行的文件,我选择了哈希表大小为2 ^ 24。根据我的测试数据,我得到了933132次碰撞。 (一半大小的哈希表在内存消耗方面具有可比性,但导致更多冲突。由于更多冲突意味着对搜索的文件访问更多,我宁愿使用大型表。)

Hash table: 128MB (sys.getsizeof([None]*(2**24)))
Nodes:       64MB (sys.getsizeof(Node(None, None)) * 933132)
Pos ints:   138MB (6000000 * 24)
-----------------
TOTAL:      330MB (real memory usage of python process was ~350MB)

答案 2 :(得分:4)

您也可以尝试

with open('input.txt') as f:
    # search_str is matched against each line in turn; returns on the first match:
    print search_str in f

search_str以正确的换行符序列'\n''\r\n'结尾。这应该使用很少的内存,因为文件是逐步读取的。它也应该非常快,因为只读取了部分文件。

答案 3 :(得分:3)

我猜许多路径在DMOZ上都是一样的。 您应该使用trie data structure并将各个字符存储在节点上。

尝试有O(m)查找时间(其中m是密钥长度),在保存大字典或树状数据时也节省了大量空间。

您还可以在节点上存储路径部分以减少节点数 - 这称为Patricia Trie。但这使得查找比平均字符串长度比较时间慢。有关实施的详细信息,请参阅问题Trie (Prefix Tree) in Python

Python Package Index有几个trie实现,但它们不是很好。我用Ruby和Common Lisp写了一个,它特别适合这个任务 - 如果你问得好,我可以把它作为开源发布......: - )

答案 4 :(得分:1)

文本索引解决方案怎么样?

我会在Java世界中使用Lucene,但有一个名为Whoosh的python引擎

https://bitbucket.org/mchaput/whoosh/wiki/Home

答案 5 :(得分:1)

如果不构建索引文件,您的搜索速度会变慢,这不是那么简单的任务。所以最好使用已经开发的软件。最好的方法是使用Sphinx Search Engine