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

nmu*_*thy 0 scala linear-algebra scala-collections parallel-collections

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

$ scala -J"-Xmx4G" map
Run Code Online (Sandbox Code Playgroud)

并获得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
Run Code Online (Sandbox Code Playgroud)

我试图找出为什么这些结果在不同的集合类型中有所不同,更重要的是,为什么性能似乎更糟糕的集合应该直观地评估得更快.很想听听您对这些结果的见解.我还尝试在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一起使用

dhg*_*dhg 5

这里显然有很多事情发生,但这里有一对:

首先,该to方法创建了Range一个非常有效的数据结构,因为它实际上并不创建具有2000万个元素的集合.它只知道如何在迭代时获取下一个元素.当你打电话mapRange,输出是一个Vector,所以它遍历范围(便宜),将每个数字乘以2(仍然便宜),但后来必须创建一个Vector(昂贵的;我猜大约7.5秒).

其次,当你打电话.toVector给a时Range,它必须实际创建一个Vector并生成所有这2000万个值.这需要时间(再次,7.5秒).当你打电话时map,它会迭代Vector(便宜),将每个数字乘以2(仍然便宜),但随后必须为结果创建一个 Vector的(代价高昂).所以你已经执行了相同的操作,但这次创建了两个包含2000万个元素的新向量.(7.5*2 = 15秒.)

第三,数组是非常简单的数据结构,并且开销极低.它们可以快速创建,索引和插入,因此构建一个大型数组然后映射它以将元素插入到一个新数组中并不令人惊讶.

最后,.par呼吁Range产生了一个ParRange.结果map是a ParVector,因此创建该对象并将2000万个元素放入其中会产生成本.当你调用.map它时会创建线程来执行计算.但是,您映射的操作非常快,因此并行执行操作确实没有任何好处.与实际计算乘法相比,处理并行化的开销要花费更多的时间.

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