【问题标题】:Efficient all-pairs set intersection on GPUGPU 上的高效全对集交集
【发布时间】:2015-06-27 21:16:56
【问题描述】:

我有n 集合,有限宇宙的子集。我想计算n*n 矩阵,其中(I, J) 条目包含集合I 和集合J 的交集的基数。 n 的顺序是50000

我的想法是将矩阵拆分为足够小的块,以便每个条目有一个线程。每个线程都应该使用bitwise and 计算交集。

有没有更有效的方法来解决这个问题?

【问题讨论】:

  • 集合的大小和宇宙的大小可能非常相关;您应该发布您感兴趣的各种参数的示例。
  • 据我了解,(I,J) 的基数应该与 (J,I) 相同,所以即使在您的幼稚实现中,您也只需要计算大约 n*n/2 的元素矩阵
  • @Hurkyl 宇宙的大小约为200000,集合的大小约为20000-30000。
  • @Slizzered 是的,矩阵是对称的,我正在考虑使用this 映射将线性结构上的三角矩阵分割成块。

标签: algorithm cuda set gpu intersection


【解决方案1】:

我假设你想按照你描述的那样计算它:实际计算每对集合的交集,使用按位和位集。

通过正确的数学设置,您实际上是在计算两个向量的外积,所以我会从高性能线性代数的角度来考虑。

性能的关键是减少内存流量,这意味着尽可能将内容保存在寄存器中。最重要的因素是你的元素很大。存储一个集合需要 6250 个 32 位字!例如,一个完整的 cuda 计算能力 3.0 多处理器只能在寄存器中保存 10 个集合。

您可能想要做的是将每个元素分散到整个线程块中。一个块中有 896 个线程,每个块有 7 个寄存器,您可以存储一组 200704 个元素。使用 cuda 计算能力 3.0,每个块将有 36 个寄存器可用。

最简单的实现是让每个块拥有输出矩阵的一行。它加载第二个向量的对应元素并将其存储在寄存器中,然后遍历第一个向量的所有元素,计算交集,计算并减少popcount,然后将结果存储到输出向量中。

此优化应将内存读取的总次数减少 2 倍,因此可能会使性能翻倍。

最好让每个块一次拥有 3-4 行输出矩阵,并将第二个向量的相应 3-4 个元素加载到寄存器中。然后该块遍历第一个寄存器的所有元素,并为每个元素计算 3-4 个交叉点,并将结果存储在输出矩阵中。

这种优化将内存流量减少了 3-4 倍。

【讨论】:

  • 感谢您对两个答案的意见,我会尽快实施您的建议并报告。
  • @Giovanni:不客气。想想这些事情很有趣!我希望他们真的有帮助。 :)
  • ...虽然我的答案中的两个(或全部三个?)可以组合成一个总体想法?我得考虑一下,我可能会在几天后对我的说明进行重大修改。
【解决方案2】:

一种完全不同的方法是单独处理 Universe 的每个元素:对于 Universe 的每个元素,您计算哪些集合实际包含该元素,然后(原子地)增加输出矩阵的相应条目。

渐近地说,这应该比计算集合的交集更有效。不幸的是,这听起来很难有效地实施。


一种改进是一次处理宇宙的 4 个元素。您将所有集合分成 16 个桶,具体取决于集合包含这 4 个元素中的哪一个。然后,对于 16*16 可能的桶对中的每一个,您遍历桶中的所有向量对并(原子地)适当地更新矩阵的相应条目。

这应该比上述版本更快,但仍可能难以实施。


为了降低完成所有同步的难度,您可以将所有输入集划分为k 组,每个组由n/k 集组成。然后,(i,j)-th 线程(或 warp 或 block)只对输出矩阵的相应块进行更新。

【讨论】:

    【解决方案3】:

    另一种解决问题的方法是将宇宙分成更小的分区,每个分区包含 1024 个元素,并仅计算宇宙这一部分中交点的大小。

    我不确定我是否描述得很好;基本上你在计算

    A[i,j] = sum((k in v[i]) * (k in w[j]) for k in the_universe)
    

    其中vw 是两个集合列表,如果为真,k in S1,否则0。关键是要置换索引,使 k 位于 outer 循环而不是 inner 循环中,尽管为了提高效率,您必须使用许多连续的 @987654328 @ 一次,而不是一次一个。

    也就是说,您将输出矩阵初始化为全零,对于每个包含 1024 个全域元素的块,您计算交集的大小并将结果累积到输出矩阵中。

    我选择 1024,因为我想您将拥有一个数据布局,这可能是您在从设备内存读取时仍然可以获得完整内存带宽的最小尺寸,并且 warp 中的所有线程一起工作。 (如果您比我更了解,或者您没有使用 nVidia 并且您使用的任何其他 GPU 可以与更好的东西配合使用,请适当调整)

    既然您的元素大小合理,您现在可以诉诸传统的线性代数优化来计算该乘积。我可能会做以下事情:

    每个扭曲都分配有大量的输出矩阵行。它从第二个向量中读取对应的元素,然后遍历第一个向量,计算乘积。

    您可以让所有经线独立运行,但最好执行以下操作:

    • 块中的所有扭曲一起工作以从第一个向量中加载一些元素
    • 每个扭曲计算它可以计算的交点并将结果写入输出矩阵

    您可以将加载的元素存储在共享内存中,但将它们保存在寄存器中可能会获得更好的结果。每个经线只能计算与其持有的集合元素的交集,但在这样做之后,经线都可以旋转哪些经线持有哪些元素。

    如果您按照这些思路进行了足够多的优化,您可能会达到不再受内存限制的程度,这意味着您可能不必进行最复杂的优化(例如,所描述的共享内存方法)以上可能已经足够了)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-20
      • 1970-01-01
      • 2013-05-22
      相关资源
      最近更新 更多