将算术运算映射到Scala集合并对结果求和

时间:2014-07-16 17:25:26

标签: scala linear-algebra scala-collections parallel-collections

以下是我昨天与我的研究小组分享的一些代码:https://gist.github.com/natemurthy/019e49e6f5f0d1be8719。编译之后,我使用以下堆参数运行map.scala:

$ scala -J"-Xmx4G" map

并获得以下4个独立测试的结果:

// (1L to 20000000L).map(_*2)
(Map) multiplying 20 million elements by 2
(Reduce) sum: 400000020000000
Total MapReduce time: 7.562381

// (1L to 20000000L).toArray.map(_*2)
(Map) multiplying 20 million elements by 2
(Reduce) sum: 400000020000000
Total MapReduce time: 1.233997

// (1L to 20000000L).toVector.map(_*2)
(Map) multiplying 20 million elements by 2
(Reduce) sum: 400000020000000
Total MapReduce time: 15.041896 

// (1L to 20000000L).par.map(_*2)
(Map) multiplying 20 million elements by 2
(Reduce) sum: 400000020000000
Total MapReduce time: 18.586220

我试图弄清楚为什么这些结果会因不同的集合类型而有所不同,更重要的是,为什么性能似乎更差,对于应该直观地评估得更快的集合。很想听听您对这些结果的见解。我还尝试在Breeze和Saddle上执行这些操作(在相同的测试中表现更好),但我想看看我可以在多大程度上推动内置的Scala Collections API。

这些测试是在华硕Zenbook UX31A,英特尔酷睿i7 3517U 1.9 GHz双核w /超线程,4 GB RAM,Ubuntu 12.04桌面上运行的。将Scala 2.11.1与JDK 1.7一起使用

1 个答案:

答案 0 :(得分:5)

这里显然有很多事情发生,但这里有几个:

首先,to方法创建了一个Range,这是一个非常有效的数据结构,因为它实际上并不创建一个包含2000万个元素的集合。它只知道如何在迭代时获取下一个元素。当您在map上调用Range时,输出为Vector,因此它会遍历范围(便宜),将每个数字乘以2(仍然便宜),但之后必须创建一个Vector(代价高昂;我估计大约需要7.5秒)。

其次,当您在.toVector上致电Range时,必须实际创建Vector并生成所有这2000万个值。这需要时间(再次,7.5秒)。当你调用map时,它会迭代Vector(便宜),将每个数字乘以2(仍然便宜),但是必须为结果创建一个 new Vector (昂贵)。所以你已经执行了相同的操作,但这次创建了两个新的2000万个元素的向量。 (7.5 * 2 = 15秒。)

第三,数组是非常简单的数据结构,开销极低。它们可以快速创建,索引和插入,因此构建一个大型数组然后映射它以将元素插入到一个新数组中就不足为奇了。

最后,对.par的{​​{1}}电话会产生RangeParRange的结果是map,因此创建该对象并将2000万个元素放入其中需要付出代价。当您调用ParVector时,它会创建线程来执行计算。但是,您映射的操作非常快,因此并行执行操作确实没有任何好处。与实际计算乘法相比,处理并行化的开销要花费更多的时间。

这样想。如果你想在现实生活中做这个操作,你就会聚集一群朋友成为你的主题。然后你必须将你的2000万个数字分开,并给每个朋友一些进行乘法运算。然后你的朋友会将每个数字乘以2,然后给出,加倍数字,然后等待你分发下一组数字。然后,您必须将每个相乘的数字输入到新表中。但是将数字乘以2的任务是如此之快,以至于你可以在更短的时间内完成它,而不是让你经历让你们在一起并来回传递信息的麻烦。此外,如果您只有两个内核,那么它​​无论如何都不是并行化的空间,所以它只有几个线程同时工作,而且您的开销与工作比率不太好