【问题标题】:Comparing Arrays of Arrays比较数组的数组
【发布时间】:2016-01-24 15:50:00
【问题描述】:

所以我有两个数组,a 和 b 大小不一,包含相同长度的子数组,并且它们的类型与子数组相同(例如浮点数)。

我想在数组 a 的子数组中找到 b 中子数组的所有匹配项。

现在我正在寻找一种更快或更好的方法来执行此操作(可能是 CUDA 或 SIMD 编码)。

目前我有类似 (F#) 的东西:

let mutable result = 0.0
for a in arrayA do:
 for b in arrayB do:
  if a = b then 
   result <- result + (a |> Array.sum)

我的数组 a 包含大约 500 万个元素,数组 b 包含大约 3000 个元素。因此是与性能相关的问题。

【问题讨论】:

  • 什么是“子”数组?这些阵列有多大?请发布一个最小的演示数据集和您想要的输出。
  • 除此之外,您如何定义浮点值的“相等”?你有考虑到一些epsilon吗?
  • 可能值得先对一个或两个数组进行排序,因此您可以在 O(NlogN + MlogM) 时间内完成此操作,而不是 O(N*M)。如果您在排序时消除和计算重复项,则不必在循环时执行此操作。实际上,只需对较短的数组进行排序,然后对于每个a,在arrayB 中进行二进制搜索。排序是特别的。如果您要使用容差进行较慢的比较,则很有价值。
  • 我在最后一段中指定了估计尺寸。我认为为避免精度问题,我将使用小数等,因为它是我要处理的与货币相关的信息。
  • 感谢您的 cmets,他们帮助我稍微调整了循环。

标签: arrays parallel-processing f# simd


【解决方案1】:

您可以通过将大型数组拆分为较小的数组并并行执行相等性检查来节省一些时间。

这个chunk函数直接取自F# Snippets

let chunk chunkSize (arr : _ array) = 
query {
  for idx in 0..(arr.Length - 1) do
  groupBy (idx / chunkSize) into g
  select (g |> Seq.map (fun idx -> arr.[idx]))
}

然后像这样比较数组。我选择将每个数组分成 4 个较小的块:

let fastArrayCompare a1 a2 = async {
let! a =
  Seq.zip (chunk 4 a1) (chunk 4 a2)
  |> Seq.map (fun (a1',a2') -> async {return a1' = a2'}) 
  |> Async.Parallel
return Array.TrueForAll (a,(fun t -> t))}

显然,您现在在数组拆分方面增加了一些额外的时间,但是有很多非常大的数组比较,您应该弥补这个时间,然后再补一些。

【讨论】:

  • 我已经使用 F# ChunkBySize 使子数组的长度相等。它存在于 F# 4.0.0.1 核心中,Cheers。
【解决方案2】:

您使用蛮力算法来解决问题。假设 AB 的大小分别为 NM,你检查相等性的每个小数组是 K 个元素长。您的算法在最坏情况下花费 O(NMK) 时间,在最佳情况下花费 O(NM + ZK) 时间,因为匹配数是 Z (可能达到NM)。

请注意,您的每个小数组本质上都是一个字符串。你有两组字符串,你想检测它们之间所有相等的对。

这个问题可以用hash table解决。用 O(M) 个单元创建一个哈希表。在这个表中,存储数组B的字符串,不重复。从 B 中添加所有字符串后,遍历 A 中的字符串并检查它们是否存在于哈希表中。该解决方案可以实现为随机解决方案,平均时间复杂度为 O((M + N) K),与输入数据大小成线性关系。

此外,您可以以非随机方式解决问题。将所有字符串放入单个数组 X 并对其进行排序。在排序过程中,将 A 中的字符串放在 B 中所有相等的字符串之后。请注意,您应该记住 X 的哪些字符串来自哪个数组。你可以使用一些快速的comparison sort,或者使用radix sort。在后一种情况下,排序是在线性时间内完成的,即在 O((M + N) K).

现在所有常见的字符串都连续存储在X中。您可以迭代 X,保持来自 B 的字符串集等于当前处理的字符串。如果您看到与前一个不同的字符串,请清除该集合。如果字符串来自B,则将其添加到集合中。如果它来自A,记录它等于来自B的元素集合。这是对 X 的一次遍历,每个字符串需要 O(K) 时间,因此需要 O((M + N) K) 时间总共。

如果您的字符串长度K 不是很小,您可以将向量化添加到字符串操作中。在哈希表方法的情况下,大部分时间将花在计算字符串哈希上。如果你选择polynomial hash模2^32,那么很容易用SSE2对其进行向量化。此外,您需要快速的字符串比较,这可以通过memcmp 函数完成,也可以很容易地向量化。对于排序解决方案,您只需要字符串比较。此外,您可能想要实现基数排序,恐怕无法进行矢量化。

两种方法的高效并行化并不是很简单。对于第一个算法,您需要一个并发哈希表。实际上,甚至还有一些lock-free hash tables。对于第二种方法,您可以并行化第一步(快速排序很容易并行化,基数排序则不然)。如果没有太多相等的字符串,第二步也可以并行化:您可以将数组 X 拆分为几乎相等的部分,只在两个不同的字符串之间进行拆分。

【讨论】:

  • 感谢您的解释,非常有见地,我曾考虑过我将实施并查看的哈希。
猜你喜欢
  • 1970-01-01
  • 2018-09-18
  • 2021-11-28
  • 2018-12-03
  • 2011-02-07
  • 2019-02-01
  • 2023-03-19
  • 2013-04-29
  • 1970-01-01
相关资源
最近更新 更多