Raj*_*j B 5 optimization functional-programming scala fold
我正在重新实现一些从Java到Scala的代码(一种简单的贝叶斯推理算法,但这并不重要).我希望以尽可能最高效的方式实现它,同时通过尽可能避免可变性来保持代码的清洁和功能.
以下是Java代码的片段:
// initialize
double lP = Math.log(prior);
double lPC = Math.log(1-prior);
// accumulate probabilities from each annotation object into lP and lPC
for (Annotation annotation : annotations) {
float prob = annotation.getProbability();
if (isValidProbability(prob)) {
lP += logProb(prob);
lPC += logProb(1 - prob);
}
}
Run Code Online (Sandbox Code Playgroud)
很简单吧?所以我决定在第一次尝试时使用Scala foldLeft和map方法.由于我有两个值,我正在积累,累加器是一个元组:
val initial = (math.log(prior), math.log(1-prior))
val probs = annotations map (_.getProbability)
val (lP,lPC) = probs.foldLeft(initial) ((r,p) => {
if(isValidProbability(p)) (r._1 + logProb(p), r._2 + logProb(1-p)) else r
})
Run Code Online (Sandbox Code Playgroud)
不幸的是,这段代码的执行速度比Java快5倍(使用简单且不精确的度量标准;只需在循环中调用代码10000次).一个缺点很明显; 我们遍历列表两次,一次是在map中调用,另一次是在foldLeft中.所以这是一个遍历列表的版本.
val (lP,lPC) = annotations.foldLeft(initial) ((r,annotation) => {
val p = annotation.getProbability
if(isValidProbability(p)) (r._1 + logProb(p), r._2 + logProb(1-p)) else r
})
Run Code Online (Sandbox Code Playgroud)
这个更好!它的执行速度比Java代码差3倍.我的下一个预感是,在折叠的每个步骤中创建所有新元组可能需要花费一些成本.所以我决定尝试两次遍历列表的版本,但不创建元组.
val lP = annotations.foldLeft(math.log(prior)) ((r,annotation) => {
val p = annotation.getProbability
if(isValidProbability(p)) r + logProb(p) else r
})
val lPC = annotations.foldLeft(math.log(1-prior)) ((r,annotation) => {
val p = annotation.getProbability
if(isValidProbability(p)) r + logProb(1-p) else r
})
Run Code Online (Sandbox Code Playgroud)
这与先前版本大致相同(比Java版本慢3倍).并不奇怪,但我很有希望.
所以我的问题是,是否有更快的方法在Scala中实现这个Java代码段,同时保持Scala代码干净,避免不必要的可变性并遵循Scala惯用法?我确实希望最终在并发环境中使用此代码,因此保持不变性的价值可能会超过单个线程中较慢的性能.
目前无法在不装箱的情况下与 scala 集合库进行交互。因此,doubleJava 中的原语将在操作中不断地装箱和拆箱fold,即使您没有将它们包装在 a 中Tuple2(这是专门的 - 但当然您已经付出了每次创建新对象的性能开销) 。