使用按位运算符在非常长的字符串中查找子字符串的最快(并行?)方法是什么?
e.g。在人类基因组中查找“GCAGCTGAAAACA”序列的所有位置http://hgdownload.cse.ucsc.edu/goldenPath/hg18/bigZips/hg18.2bit(770MB)
*字母表由4个符号组成('G','C',T,'A'),用2位表示: 'G':00,'A':01,'T':10,'C':11
*您可以假设查询字符串(较短的字符串)的长度是固定的,例如127个字符
*最快我的意思是不包括任何预处理/索引时间
*文件将在预处理后加载到内存中,基本上会在更大的字符串中搜索数十亿个短字符串,全部在内存中。
*按位,因为我正在寻找最简单,最快速的方法来搜索大型位阵列中的位模式,并尽可能保持与硅的接近。
*由于字母表很小,KMP无法正常工作
* C代码,x86机器代码都很有趣。
输入格式说明(.2bit):http://jcomeau.freeshell.org/www/genome/2bitformat.html
相关:
Fastest way to scan for bit pattern in a stream of bits
Algorithm help! Fast algorithm in searching for a string with its partner
答案 0 :(得分:5)
如果您只是查看文件,那么您几乎可以保证自己受到约束。应该使用大缓冲区(~16K)和strstr()
。如果文件以ascii编码,则只搜索"gcagctgaaaaca"
。如果它实际上是以位编码的;只是置换可能接受的字符串(应该有~8;丢掉第一个字节),并使用memmem()
加上一个微小的重叠位检查。
我将在此注意到glibc strstr
和memmem
已经使用Knuth-Morris-Pratt在线性时间内进行搜索,因此请测试该性能。这可能会让你大吃一惊。
答案 1 :(得分:3)
如果您首先用无损编码方法(例如Huffman,指数Golumb等)编码/压缩DNA串,那么您将获得各种核苷酸组合的DNA标记的排序概率表(“编码树”)(例如,A
,AA
,CA
等)。
这意味着,一旦你压缩你的DNA:
GCAGCTGAAAACA
和其他子序列,而不是每个基数总是使用两位的“未编码”方法。对于并行方法,将编码的目标字符串拆分为N个块,并使用缩短的编码搜索字符串在每个块上运行搜索算法。通过跟踪每个块的位偏移,您应该能够生成匹配位置。
总的来说,如果您计划对不会改变的序列数据进行数百万次搜索,这种压缩方法将非常有用。你会搜索更少的比特 - 总体而言可能会少得多。
答案 2 :(得分:2)
Boyer-More是一种用于搜索普通字符串中的子串的技术。基本的想法是,如果你的子字符串是10个字符长,你可以查看字符串中第9位的字符进行搜索。如果该字符不是搜索字符串的一部分,则可以在该字符后面开始搜索。 (如果该字符确实存在于您的字符串中,则Boyer-More算法使用查找表来跳过最佳字符数。)
有可能将这个想法重用于基因组串的压缩表示。毕竟,只有256个不同的字节,因此您可以安全地预先计算跳过表。
答案 3 :(得分:1)
将字母表编码为位字段的好处是紧凑性:一个字节保存相当于四个字符。这类似于Google执行搜索单词的一些优化。
这表明有四个并行执行,每个执行的(变换的)搜索字符串偏移一个字符(两位)。一种快速而肮脏的方法可能只是查找搜索字符串的第一个或第二个字节,然后在匹配字符串的其余部分之前和之后检查额外的字节,必要时屏蔽两端。第一次搜索由x86指令scasb
轻松完成。后续的字节匹配可以建立在cmpb
的寄存器值上。
答案 4 :(得分:1)
您可以创建状态机。在本主题中, Fast algorithm to extract thousands of simple patterns out of large amounts of text ,我使用[f] lex为我创建状态机。使用4个字母(:= 2位)字母表需要一些hackery,但可以使用[f] lex生成的相同表格来完成。 (你甚至可以创建你自己的fgetc()函数,它从输入流中一次提取两个比特,并保持连续调用的其他六个比特。推回将有点困难,但不可撤销)。
BTW:我非常怀疑每个核苷酸压缩数据到两位是否有任何好处,但这是另一回事。答案 5 :(得分:1)
好的,鉴于您的参数,问题并不那么难,只是没有像传统的字符串搜索问题那样接近。它更类似于数据库表连接问题,其中表比RAM大得多。
选择一个好的滚动哈希函数aka buzhash。如果您有数十亿个字符串,那么您正在寻找具有64位值的哈希值。
根据每个127元素的搜索字符串创建一个哈希表。内存中的表只需要存储(hash,string-id),而不是整个字符串。
扫描非常大的目标字符串,计算滚动哈希值并查找表中哈希值的每个值。只要匹配,就将(string-id,target-offset)对写入流,可能是文件。
重新读取目标字符串和配对流,根据需要加载搜索字符串,将它们与每个偏移处的目标进行比较。
我假设一次将所有模式字符串加载到内存中是禁止的。有一些方法可以将哈希表分割成大于RAM但不是传统的随机访问哈希文件的东西;如果您有兴趣,请搜索“混合哈希”和“恩典哈希”,这在数据库世界中更为常见。
我不知道它是否值得您这么做,但您的配对流为您提供了完美的预测输入来管理模式字符串的缓存 - Belady的经典VM页面替换算法。