Я перепрограммирую некоторый код (простой байесовский алгоритм вывода, но это не очень важно) от Java до Scala. Я хотел бы реализовать его наиболее эффективным образом, сохраняя при этом код чистым и функциональным, избегая, насколько возможно, изменчивости.Предложения по оптимизации простой Scala foldLeft по нескольким значениям?
Вот фрагмент кода 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 и методы карты для моей первой попытки. Поскольку у меня есть два значения я накапливался в течение, аккумулятор является кортежем:
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 раз петля). Один недостаток довольно ясен; мы перебираем списки дважды, один раз в вызове карты, а другой - в 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? Я ожидаю использовать этот код в конечном итоге в параллельной среде, поэтому значение сохранения неизменяемости может перевесить более медленную производительность в одном потоке.
Есть ли у вас ленивые datastructures в scala? Если это так, вы должны быть в состоянии избежать нескольких проходов. – Marcin
@Marcin: да, коллекции Scala предоставляют метод ['view'] (http://www.scala-lang.org/docu/files/collections-api/collections_42.html), что делает это очень просто. –