我在 32 位四核 Core2 系统上运行这个 scala 代码:
def job(i:Int,s:Int):Long = {
val r=(i to 500000000 by s).map(_.toLong).foldLeft(0L)(_+_)
println("Job "+i+" done")
r
}
import scala.actors.Future
import scala.actors.Futures._
val JOBS=4
val jobs=(0 until JOBS).toList.map(i=>future {job(i,JOBS)})
println("Running...")
val results=jobs.map(f=>f())
println(results.foldLeft(0L)(_+_))
(是的,我确实知道有很多更有效的方法可以对一系列整数求和;这只是让 CPU 有事可做)。
根据我将 JOBS 设置为的内容,代码将在以下时间运行:
JOBS=1 : 31.99user 0.84system 0:28.87elapsed 113%CPU
JOBS=2 : 27.71user 1.12system 0:14.74elapsed 195%CPU
JOBS=3 : 33.19user 0.39system 0:13.02elapsed 257%CPU
JOBS=4 : 49.08user 8.46system 0:22.71elapsed 253%CPU
令我惊讶的是,这并没有真正超出“正在发挥作用”的 2 个期货。我做了很多多线程 C++ 代码,毫无疑问,如果我用英特尔的 TBB 编写这种东西,或者boost::threads
(当然会更冗长),我会很好地扩展到 4 个内核,并看到 > 390% 的 CPU 利用率)。
那么:发生了什么事,我怎样才能将规模扩大到我希望看到的 4 个核心?这是否受到 scala 或 JVM 中的某些东西的限制?我突然想到,我实际上并不知道 scala 的期货在“哪里”运行……是每个未来产生的线程,还是“期货”提供专用于运行它们的线程池?
[我在带有 sun-java6 (6-20-0lennny1) 的 Lenny 系统上使用来自 Debian/Squeeze 的 scala 2.7.7 软件包。]
更新:
正如雷克斯的回答中所建议的,我重新编码以避免创建对象。
def job(i:Long,s:Long):Long = {
var t=0L
var v=i
while (v<=10000000000L) {
t+=v
v+=s
}
println("Job "+i+" done")
t
}
// Rest as above...
这快得多了,我不得不显着增加迭代次数才能运行任何时间!结果是:
JOBS=1: 28.39user 0.06system 0:29.25elapsed 97%CPU
JOBS=2: 28.46user 0.04system 0:14.95elapsed 190%CPU
JOBS=3: 24.66user 0.06system 0:10.26elapsed 240%CPU
JOBS=4: 28.32user 0.12system 0:07.85elapsed 362%CPU
这更像是我希望看到的(尽管 3 个工作案例有点奇怪,其中一个任务始终比其他两个任务早几秒钟完成)。
更进一步,在四核超线程 i7 上,后一个版本JOBS=8
实现了 x4.4 的加速比 JOBS=1,CPU 使用率为 571%。