Я заново реализую некоторый код (простой алгоритм байесовского вывода, но это не очень важно) с 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);
}
}
Довольно просто, верно?Поэтому я решил использовать 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
})
К сожалению, этот код работает примерно в 5 раз медленнее, чем Java (с использованием простой и неточной метрики; просто называется кодом 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
})
Это лучше!Он работает примерно в 3 раза хуже, чем код Java.Моя следующая догадка заключалась в том, что, вероятно, существуют некоторые затраты, связанные с созданием всех новых кортежей на каждом этапе сгиба.Поэтому я решил попробовать версию, которая проходит через список дважды, но без создания кортежей.
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
})
Это работает примерно так же, как и в предыдущей версии (в 3 раза медленнее, чем в версии Java).Не удивительно, но я был полон надежд.
Итак, мой вопрос: есть ли более быстрый способ реализовать этот фрагмент Java в Scala, сохраняя код Scala в чистоте, избегая ненужной изменчивости и следуя идиомам Scala?Я ожидаю использовать этот код в конечном итоге в параллельной среде, поэтому ценность сохранения неизменности может перевесить более медленную производительность в одном потоке.