并行与分布式系统中的并行正则表达式匹配:基于自动机交(Automata Intersection)的并行构造与匹配算法
字数 3382 2025-12-20 23:24:55
并行与分布式系统中的并行正则表达式匹配:基于自动机交(Automata Intersection)的并行构造与匹配算法
题目描述
本题要求我们设计一个并行算法,用于在分布式内存环境中高效地匹配一个输入字符串是否被一组正则表达式(或一个正则表达式)所接受。核心思想是将正则表达式转换为有限状态自动机(通常是NFA或DFA),并利用自动机交运算(Automata Intersection)的概念,将多个模式或多个子模式的处理并行化。该算法需要处理大规模模式集和大规模输入文本,并充分利用多节点或众核的并行计算能力。
背景知识
- 正则表达式:描述字符串模式的工具,可用有限状态自动机表示。
- 有限状态自动机(FSA):
- NFA:非确定有限状态自动机。一个状态在接收到一个输入符号后可能转移到多个后续状态。匹配过程可能需要“猜测”,但转换过程具有内在的并行性。
- DFA:确定有限状态自动机。每个状态对每个输入符号有且仅有一个转移。匹配过程是确定性的,但状态数可能(相对于NFA)指数级增长。
- 自动机交:给定两个自动机A和B,可以构造一个新的自动机A∩B,其接受的语言是A和B接受语言的交集。这在多模式匹配中很有用(即文本需同时满足多个模式)。
- 并行性来源:
- 模式/子模式并行:不同的正则表达式模式(或单个复杂模式的子部分)可以独立地转换为自动机,或独立地进行匹配。
- 输入文本并行:将输入文本分割成多个子串,在不同处理单元上并行匹配,但需处理跨分区的边界状态同步。
- 自动机状态空间并行:在模拟NFA时,一个输入符号可能激活多个状态,这些状态的更新可以并行计算。
解题过程(并行算法设计)
我们设计一个基于模式并行和输入文本分块并行的混合方法。核心步骤是:1) 将一组正则表达式模式编译为NFA;2) 并行地处理输入文本块,模拟NFA的状态转移;3) 处理块边界的状态同步。
步骤1:正则表达式到NFA的并行编译
- 目标:将每个正则表达式模式编译为一个独立的NFA。
- 并行策略:这是任务并行的绝佳场景。如果有
P个处理器和M个正则表达式模式,我们可以将模式平均分配给处理器。每个处理器使用标准算法(如Thompson构造法)独立地将其分配到的模式编译为NFA。 - 细节:
- 每个处理器维护一个局部数据结构,存储其负责编译的NFA集合。
- 编译过程是独立的,无需通信。
- 最终,所有处理器拥有完整的NFA集合的一个划分。我们可能需要一个全局的“模式ID”到“负责处理器”的映射,用于后续匹配结果的收集。
步骤2:输入文本划分与分发
- 目标:将长输入文本
T划分为多个连续、不重叠的块,分配给不同处理器进行并行匹配。 - 并行策略:数据并行。将文本
T划分为P个近似等长的子串T_1, T_2, ..., T_P,每个处理器i获得子串T_i。 - 挑战:由于正则表达式模式可能跨越块边界(例如,模式
ab*c,a在块尾,b*c在下一块开头),简单分块会导致漏匹配。我们需要重叠划分或边界状态传播。
步骤3:带边界状态传播的并行NFA模拟
这是算法的核心。我们采用重叠划分和状态传播策略。
-
3.1 重叠划分:
- 除了将
T_i分配给处理器i,我们还额外分配给处理器i其前一个块T_{i-1}末尾的L个字符,其中L是所有模式的最大可能匹配长度(或一个安全上界,如模式最大长度)。 - 这样,处理器
i实际处理的字符串是Overlap_i = T_{i-1}[-L:] + T_i(对于第一个块,没有前驱重叠)。 - 这保证了任何完全落在
T_i内或跨越T_i和T_{i-1}边界的匹配都能在处理器i上被检测到。
- 除了将
-
3.2 并行NFA模拟:
- 每个处理器
i对其Overlap_i子串,针对所有NFA(步骤1中编译得到的完整集合)并行地进行NFA模拟。 - NFA模拟过程(对于单个NFA):
- 初始化:从NFA的起始状态(可能包括通过ε-闭包得到的所有状态)开始,得到一个当前活跃状态集合
S_current。 - 逐字符处理:对于
Overlap_i中的每个字符c:- 对于
S_current中的每个状态s,查找所有以c为条件的转移,将目标状态加入下一个状态集合S_next。 - 计算
S_next的ε-闭包(通过ε边可达的所有状态),得到新的S_current。 - 此步骤中,对
S_current中状态的转移计算是可并行的(例如,将S_current中的状态分配给多个线程处理)。
- 对于
- 匹配检测:在处理每个字符后,检查
S_current中是否包含NFA的任何接受状态。如果是,则记录一个匹配(匹配位置、模式ID等)。注意,由于重叠,匹配位置可能需要根据块偏移量进行全局调整。
- 初始化:从NFA的起始状态(可能包括通过ε-闭包得到的所有状态)开始,得到一个当前活跃状态集合
- 每个处理器
-
3.3 状态传播(边界同步)的替代方案:
- 重叠划分虽然简单,但可能导致冗余计算(每个字符可能被多个处理器处理)和内存开销(存储重叠部分)。
- 更高效的方法:不发送重叠文本,而只发送边界状态。
- 处理器
i处理完其块T_i后,得到处理完T_i末尾时的所有NFA的最终活跃状态集合(一个映射:PatternID -> Set of States)。 - 处理器
i将这个边界状态映射发送给处理器i+1。 - 处理器
i+1在开始处理其块T_{i+1}时,不是从NFA的初始状态开始,而是从接收到的边界状态(作为其初始活跃状态集合)开始模拟。 - 这需要对每个模式进行状态集合的通信,但通信量远小于重叠文本(尤其是当状态集合可以压缩表示时)。
- 处理器
- 挑战:对于NFA,一个模式的状态集合可能很大。对于DFA,状态是单个的,通信量小,但构造DFA可能有状态爆炸问题。一种折中是使用部分确定化或Aho-Corasick自动机(用于多字符串模式,而非全正则表达式)。
步骤4:匹配结果的收集与归并
- 每个处理器
i在本地记录其在处理Overlap_i(或T_i,如果使用状态传播)时检测到的所有匹配。 - 由于重叠划分,同一个匹配可能被两个相邻的处理器检测到(如果匹配正好在重叠区域内)。需要进行去重。
- 去重策略:每个匹配用
(pattern_id, start_position, end_position)唯一标识。在收集所有匹配后,进行全局排序和去重。或者,在设计时确保每个匹配只被一个处理器“负责”报告(例如,规定匹配的起始位置属于哪个块,就由该块所在的处理器报告)。
- 去重策略:每个匹配用
- 最后,一个根处理器收集所有处理器的匹配结果,进行去重和排序,输出最终匹配列表。
算法复杂度与优化
- 时间复杂度:
- 编译阶段:每个模式的编译是线性的(相对于模式长度),完美并行。
- 匹配阶段:对于文本长度
N,处理器数P,理想情况下并行时间为O(N/P * M * S),其中M是模式数,S是NFA平均大小相关因子。但实际上由于状态传播和通信,会有额外开销。
- 空间复杂度:每个处理器需要存储所有NFA(或部分)和文本块。重叠划分会增加文本存储开销。
- 优化方向:
- 自动机优化:将一组模式编译为单个Aho-Corasick自动机(适用于字符串集合)或单个DFA,减少模拟开销,但可能增加编译时间和内存。对于正则表达式,可考虑使用正则表达式交集的并行构造。
- 状态集合表示:使用位图(Bitmask)表示NFA的状态集合,可以利用位操作(AND, OR, SHIFT)和SIMD指令加速状态转移计算。
- 通信优化:在状态传播中,只传播“可能产生匹配”的模式的状态集合,或使用增量编码压缩状态集合。
- 负载均衡:如果文本块大小不均,或某些块内模式匹配活动频繁(导致NFA模拟更耗时),可能需要动态任务窃取。
总结
本算法通过任务并行(模式编译)和数据并行(文本分块匹配)相结合,利用NFA模拟的内在并行性,并设计重叠划分或边界状态传播机制处理跨块匹配问题,实现了正则表达式集的并行匹配。关键挑战在于高效处理边界同步和状态集合的并行模拟。该算法适用于大规模日志分析、入侵检测、生物序列分析等需要高速多模式正则匹配的场景。