Предложения по оптимизации простого Scala foldLeft по нескольким значениям?

Я повторно реализую некоторый код (простой алгоритм байесовского вывода, но это не очень важно) с 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? Я действительно ожидаю использовать этот код в конечном итоге в параллельной среде, поэтому ценность сохранения неизменяемости может перевесить более низкую производительность в одном потоке.

5
задан Raj B 2 February 2012 в 17:00
поделиться