Scala Set相交性能

时间:2018-11-16 00:12:23

标签: scala performance

使用Scala的scala.collection.Set[T]。给定一个只有几个元素的小集s和另一个有很多元素的大集b,两者之间是否存在性能差异?

s & b // s intersect b

b & s // b intersect s.

如果是,哪个最快?

3 个答案:

答案 0 :(得分:2)

答案是:很复杂。

不可变集的默认实现为scala.collection.immutable.Set。它的大小为1到4的special cases。一旦您拥有4个以上的元素,它将使用scala.collection.immutable.HashSet

非常小的s(1..4)

因此,假设您有一个大集合b和一个小集合s,其中s包含<4个元素。

然后它将产生差异:

b & s将针对b中的成员资格过滤s的所有元素,因此进行b.count * s.count相等比较。由于b很大,所以会很慢。

s & b将针对s中的成员资格过滤b的几个元素,这是s.length乘以哈希值和相等性比较(如果哈希匹配)(记住b是a HashSet)。因为很小,所以应该很快。

小号(n> 4)

s大于4个元素时,将成为HashSet。 HashSet的交集以对称且非常有效的方式编写。它将结合sb的树结构,并在哈希匹配时执行相等比较。它将使用最大的结构共享。例如。如果b包含s的所有元素,则结果将与s 相同的实例,因此将不分配对象。

一般建议

如果您只是在不太关心高性能的情况下编写通用代码,则可以使用诸如scala.collection.Set之类的默认实现。但是,如果您关心性能特征,则最好使用 concrete 实现。例如。如果将sb声明为scala.collection.immutable.HashSet,只要您具有良好的哈希函数,您将获得与顺序无关的一致高性能。

答案 1 :(得分:1)

GenSetLike中使用intersect的通用实现被HashSet所覆盖,但该实现对我来说似乎很复杂(请参阅scala.collection.immutable.HashSet.HashTrieSet#intersect0)。根据我的粗略基准测试,a & bb & a的性能相似,并且与a filter b的性能相似,这比b filter a快一个数量级。我的测试代码是:

object Sets extends App {

  def time[R](block: => R): R = {
    val t0 = System.nanoTime()
    val result = block    // call-by-name
    val t1 = System.nanoTime()
    println("Elapsed time: " + (t1 - t0)/1e6 + "ms")
    result
  }

  val a = (0 until 10000 by 1).toSet      //smaller data
  val b = (0 until 1000000 by 2).toSet


  time {a & b}
  time {b & a}
  time {a & b}
  time {b & a}
  time {a & b}
  time {b & a}

  println("Filter")

  time {a filter b}
  time {b filter a}
  time {a filter b}
  time {b filter a}
  time {a filter b}
  time {b filter a}
}

结果是:

Elapsed time: 6.990442ms
Elapsed time: 4.25017ms
Elapsed time: 4.1089ms
Elapsed time: 4.480789ms
Elapsed time: 3.71588ms
Elapsed time: 3.160159ms
Filter
Elapsed time: 7.781613ms
Elapsed time: 68.33023ms
Elapsed time: 5.858472ms
Elapsed time: 42.491131ms
Elapsed time: 2.982364ms
Elapsed time: 52.762474ms

答案 2 :(得分:0)

让我们根据提到的条件创建两个集合

   val a = (0 until 10000 by 1).toSet      //smaller data
   val b = (0 until 1000000 by 2).toSet    //Relatively larger data

我们可以定义一个时间函数来检查执行时间,如下所示

def time[R](block: => R): R = {
    val t0 = System.nanoTime()
    val result = block    // call-by-name
    val t1 = System.nanoTime()
    println("Elapsed time: " + (t1 - t0) + "ns")
    result
}

现在我们可以检查相交条件

scala> time {a & b}
Elapsed time: 5895220ns
res2: scala.collection.immutable.Set[Int] = Set(892, 5810, 8062, ..)

scala> time {b & a}
Elapsed time: 6038271ns
res3: scala.collection.immutable.Set[Int] = Set(892, 5810, 8062, ...)

因此,我们可以得出这样的结论:较小的数据集和较大的数据集之间的交集具有性能差异,最好将较小的数据集放在左侧以便更快地执行Scala集