快速的字符串搜索

Fast in string search

本文关键字:搜索 字符串      更新时间:2023-10-16

我有一个问题,我正在寻找一些指导来解决最有效的方法。我有2亿个字符串的数据,大小从3个字符到70个字符。字符串由字母、数字和一些特殊字符(如:破折号和下划线)组成。我需要能够快速搜索整个字符串或字符串内的任何子字符串(最小子字符串大小为3)。快速在这里定义为小于1秒。

作为我的第一个切口,我做了以下操作:

  1. 创建了38个索引文件。索引包含所有以特定字母开头的子字符串。前4mb包含100万个哈希桶(哈希链的开始)。索引的其余部分包含来自散列桶的链表链。哈希是均匀分布的。100万个哈希桶保存在RAM中,并镜像到磁盘。

  2. 当一个字符串被添加到索引时,它被分解成它的不重复的(在它自己内部)3-n个字符的子字符串(当n是字符串的长度-1时)。因此,例如,"苹果"在"A"索引中存储为苹果,苹果,ppl,pp(子字符串也存储在"L"answers"P"索引中)。

搜索/添加服务器作为守护进程运行(在c++中),并且像冠军一样工作。典型的搜索时间小于1/2秒。

问题在流程的前端。我通常一次添加30,000个键。这个过程会持续很长时间。通过基准测试,将时间加载到包含180,000个可变长度键的空索引中大约是3.5小时。

这个方案除了加载时间很长之外是有效的。

在我疯狂优化(或尝试)之前,我想知道是否有更好的方法来解决这个问题。对于这么大的数据集,前后通配符搜索(例如:在DBMS中像'%ppl%'这样的字符串)非常慢(例如在MySQL中以小时为数量级)。因此,DBMS解决方案似乎是不可能的。我不能使用全文搜索,因为我们处理的不是普通的单词,而是可能由也可能不是由真正的单词组成的字符串。

根据您的描述,加载数据需要花费所有时间,因为您正在处理I/O,将膨胀的字符串镜像到硬盘。这肯定会成为瓶颈,主要取决于您向磁盘读取和写入数据的方式。

使用mmap和一些LRU策略可能会改善执行时间。我很确定复制数据的想法是为了使搜索更快,但是由于您使用的是—看起来是—只有一台机器,因此您的瓶颈将从内存搜索跳到I/O请求。

另一个您可能不感兴趣的解决方案(:——)是将数据拆分到多台机器上。考虑到您构建数据的方式,实现本身可能需要一些时间,但它将非常简单。你会有:

  • 每台机器由一组桶负责,使用接近hash_id(bucket) % num_machines的东西选择;
  • 插入在本地执行,从每台机器;
  • 搜索可以由一些类型的查询应用程序接口,或者简单地聚集到查询集中——如果应用程序不是交互式的;
  • 搜索甚至可能具有分布式接口,考虑到您可能从一个节点发送启动请求,并将请求转发到另一个节点(也是集群请求,以避免过多的I/O开销)。

另一个优点是,正如你所说,数据是均匀分布的——已经o/;这通常是分布式实现中最挑剔的部分之一。此外,这将是高度可伸缩的,因为当数据大小增长时,您可以添加另一台机器。

与其一次解决所有问题,不如38次解决问题。

读取180,000个字符串中的每个字符串。在每个字符串中找到"A",并只将内容写入"A"哈希表。完成后,将"A"哈希表的整个完成结果写到磁盘上。(要有足够的内存将整个"A"哈希表存储在内存中——如果没有,就创建更小的哈希表。也就是说,有38^2个哈希表在字母对上,有1444个不同的表。你甚至可以动态地改变哈希表的键值,根据它们的前缀有多常见,所以它们的大小都是适度的。跟踪这些前缀的长度并不昂贵。)

然后读取180,000个字符串中的每一个,寻找"B"。等。

我的理论是,你的速度比你可以的慢,因为你的大数据表的缓存抖动。

下一件可能有帮助的事情是限制你做哈希的字符串的长度,以缩小你的表的大小。

如果将哈希值的长度限制为10个字符,则只有508个子字符串的长度为3到10,而不是处理长度为70的字符串的所有2278个子字符串的长度为3到70。在长度大于10的弦上可能不会有那么多碰撞。同样,您可以让哈希的长度是动态的——长度X哈希可能有一个标志,表示"如果字符串比X长,请尝试长度X+Y哈希,这太常见了",否则就直接终止哈希。这样可以减少表中的数据量,但在某些情况下会降低查找速度。