String 构造trie的并行算法?

String 构造trie的并行算法?,string,algorithm,data-structures,parallel-processing,trie,String,Algorithm,Data Structures,Parallel Processing,Trie,由于trie数据结构有如此巨大的分支因子,并且每个子树都完全独立于其他子树,因此似乎应该有一种方法通过并行添加所有单词来极大地加快给定词典的trie构建 我最初的想法是:将互斥体与trie中的每个指针(包括指向根的指针)相关联,然后让每个线程遵循向trie中插入单词的正常算法。但是,在遵循任何指针之前,线程必须首先获取该指针上的锁,这样,如果它需要向trie添加新的子节点,它就可以在不引入任何数据竞争的情况下执行此操作 这种方法的缺点是它使用了大量的锁(trie中的每个指针一个锁),并执行大量的

由于trie数据结构有如此巨大的分支因子,并且每个子树都完全独立于其他子树,因此似乎应该有一种方法通过并行添加所有单词来极大地加快给定词典的trie构建

我最初的想法是:将互斥体与trie中的每个指针(包括指向根的指针)相关联,然后让每个线程遵循向trie中插入单词的正常算法。但是,在遵循任何指针之前,线程必须首先获取该指针上的锁,这样,如果它需要向trie添加新的子节点,它就可以在不引入任何数据竞争的情况下执行此操作

这种方法的缺点是它使用了大量的锁(trie中的每个指针一个锁),并执行大量的获取和释放(每个输入字符串中的每个字符一个锁)


有没有一种方法可以在不使用几乎同样多的锁的情况下并行构建一个trie?

好吧,为一组节点设置锁(而不是一个节点)的细粒度和粗粒度之间存在明显的权衡

一种简单的方法是通过散列-拥有
m
不同的锁,并为每个要访问的节点获取编号为
hash(node)%m
的锁。
请注意,该方法基本上是建议方法(使用完美哈希和
n==m
)和串行方法(使用
m==1
)的推广

另一个可能使用的方法是——如果该方法实际上会提高性能,那么性能取决于字典的分布和trie的大小,当然,如果冲突往往非常罕见(对于非常长的单词字典来说可能是这样),则可能会有很大帮助。

其想法是在没有任何同步的情况下将单词添加到trie中,如果遇到冲突,则回滚到最后一个已知的稳定状态(当然,这需要对数据进行快照,如果我们谈论的是无法存储的数据流,则可能不可行).

我刚刚想到,可以通过使用原子测试和指针上的set操作而不是锁来解除锁定。具体来说,如果线程希望跟随指针,它将执行以下操作:

  • 以原子方式读取指针值
  • 如果指针不为空,则跟随它。你完了
  • 否则,请分配一个新节点
  • 原子测试指针是否为null,如果为null,则将其设置为新节点
  • (备注:这里的指针绝对是非空的。要么我们只是设置了它,要么它是由另一个线程设置的)
  • 跟着指针走
  • 根据硬件的不同,这可能会快得多,因为它避免了一直锁定和解锁的工作,并确保没有线程会无限期地等待

    一个缺点是,所涉及的分配数量会增加,因为多个线程可能都会尝试分配一个节点,将其放置在某个位置的trie中,但只有一个线程可以将其放置在那里。幸运的是,这可以通过以下优化得到缓解:如果线程曾经不必要地分配节点,而不是立即释放它,它只是将节点存储在临时空间中。如果以后需要分配新节点,可以使用缓存的节点。如果没有,它可以在最后释放它


    希望这有帮助

    一个明显的无锁算法是:

  • Bucket按length-k前缀对输入字符串进行排序(通常,k=1,但对于小字母,增加k)
  • 对于每个字母,构造一个包含以该字母开头的所有字符串的k后缀的trie
  • 合并上一步的尝试(当k=1时,只需添加一个根节点)

  • 假设前缀分布均匀,这可以使您的线性加速达到字母表大小的k次方。

    根据字典的外观,如果可以让每个线程构建独立的子树,您可能根本不需要锁。如果这不是一个在线算法,请按前缀对单词进行预排序(如果少于26个线程,则为第一个字母;如果有更多线程,则为第一个字母;如果知道数据不平衡,则为第二个字母,例如,90%的单词以A开头)。基本上,这将是一个O(n)操作,您只需进行一次运算,计算以给定字母开头的单词数量,然后进行一次排序(按照所选前缀的基数排序)。然后,在线程之间划分前缀,并让每个线程构建这些独立的子树。最后,让一个线程将这些子树中的每一个子树添加到根。我将浏览下面的一个示例

    你的字典:
    树皮
    苹果
    饼干

    婴儿
    玉米
    蓝色
    蛋糕
    培根

    排序后:
    苹果

    树皮
    婴儿
    蓝色
    培根
    玉米
    饼干
    蛋糕

    然后我们在线程之间划分前缀。对于本例,我们有3个线程,它们获取前缀[A][B][C],并构建以下树:

    A --| B -------| C -------| P N |-- A ---| L O ---| A P D R B C U O R K L K Y O E K N E E N I E A--B--C-- P N--A--L O--A P D R B C O R K 好的,好的 恩尼 E 然后有一个线程在根处组合这些线程,如:

    |----------- Root------------------| A --| B -------| C -------| P N |-- A ---| L O ---| A P D R B C U O R K L K Y O E K N E E N I E |-----------根------------------| A--B--C-- P N--A--L O--A P D R B C O R K 好的,好的 恩尼 E 我希望这是有道理的

    此方法的优点: 线程基本上是独立工作的,您不需要处理获取和释放锁的开销

    这种方法的缺点: 如果你