object PrefixScan {
sealed abstract class Tree[A]
case class Leaf[A](a: A) extends Tree[A]
case class Node[A](l: Tree[A], r: Tree[A]) extends Tree[A]
sealed abstract class TreeRes[A] { val res : A }
case class LeafRes[A](override val res: A) extends TreeRes[A]
case class NodeRes[A](l : TreeRes[A], override val res: A, r: TreeRes[A]) extends TreeRes[A]
def reduceRes[A](t: Tree[A], f:(A,A)=>A): TreeRes[A] = t match {
case Leaf(v) => LeafRes(v)
case Node(l, r) => {
val (tL, tR) = (reduceRes(l, f), reduceRes(r, f))
NodeRes(tL, f(tL.res, tR.res), tR)
}
}
}
我关注reduceRes
功能。
它有效......计算结果很棒!
但是我去实现了另一个版本reduceResPar
,它在前几个分支中使用fork-join来并行化计算。但它没有加速。
然后我回去意识到......上面的版本reduceRes
已经在我的机器上使用了所有12个内核!它怎么能这样做?我以为它只是一个核心!
此代码来自Coursera的并行编程课程在第2周的最后一堂课中,我们正在学习并行前缀扫描操作。
答案 0 :(得分:4)
怎么做呢?我以为它只是一个核心!
您看到所有核心的使用并不意味着您的代码执行是并行的。我们可以从实现中看到它的顺序,但是我们不知道每个周期操作系统将在哪个CPU上安排我们的单线程。
当你在线程中执行一个方法时,操作系统会根据它管理的优先级队列决定它将获得多少CPU时间片以及何时获取。
要了解您的算法可能在不同的内核上运行,我们可以询问操作系统当前正在执行我们的线程的逻辑内核。我已经为Windows准备了一个小实现,它有一个名为GetCurrentProcessorNumber()
的本机WinAPI方法,它返回我们正在执行的处理器号。我们将JNA用作示例:
build.sbt:
"net.java.dev.jna" % "jna" % "4.4.0"
Java实现:
import com.sun.jna.Library;
import com.sun.jna.Native;
public class ProcessorNumberNative {
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary("Kernel32.dll",
CLibrary.class);
Integer GetCurrentProcessorNumber();
}
}
现在让我们在递归的每个步骤中添加println
:
def reduceRes[A](t: Tree[A], f: (A, A) => A): TreeRes[A] = t match {
case Leaf(v) =>
println(s"Logical Processor Number: ${ProcessorNumberNative.CLibrary.INSTANCE.GetCurrentProcessorNumber()}")
LeafRes(v)
case Node(l, r) =>
println(s"Logical Processor Number: ${ProcessorNumberNative.CLibrary.INSTANCE.GetCurrentProcessorNumber()}")
val (tL, tR) = (reduceRes(l, f), reduceRes(r, f))
NodeRes(tL, f(tL.res, tR.res), tR)
}
现在让我们创建一个树并执行:
def main(args: Array[String]): Unit = {
val tree = Node(Leaf(1),
Node(Leaf(2),
Node(Node(Leaf(24), Leaf(30)),
Node(Leaf(3), Node(Leaf(10), Leaf(52))))))
reduceRes(tree, (a: Int, b: Int) => a + b)
}
并给出这两个不同的运行(我正在运行具有4个逻辑核心的计算机):
首先:
Logical Processor Number: 1
Logical Processor Number: 3
Logical Processor Number: 3
Logical Processor Number: 3
Logical Processor Number: 0
Logical Processor Number: 0
Logical Processor Number: 0
Logical Processor Number: 3
Logical Processor Number: 0
Logical Processor Number: 0
Logical Processor Number: 0
Logical Processor Number: 0
Logical Processor Number: 0
第二
Logical Processor Number: 1
Logical Processor Number: 3
Logical Processor Number: 1
Logical Processor Number: 1
Logical Processor Number: 1
Logical Processor Number: 1
Logical Processor Number: 1
Logical Processor Number: 1
Logical Processor Number: 3
Logical Processor Number: 3
Logical Processor Number: 3
Logical Processor Number: 3
Logical Processor Number: 3
在每次执行期间,您会看到正在执行的线程在3个不同的内核0,1和3上执行了一段执行,而我们仍然在单线程环境中运行。这表明虽然算法的计算肯定是连续的,但这并不意味着你不会看到你所有的核心都在发挥作用。