Ошибка производительности F #?

#performance #f#

#Производительность #f#

Вопрос:

 let test1fun x = [for i in 1..x-> i]

let test2fun x y= [for i in 1..x
                    do for i in 1..y-> i]

let  singlesearcher i =
    let rec searcher j agg =
        if j > i 
        then agg 
        else searcher (j 1) (i::agg)
    searcher 1 []


let  doublesearcher i j =
    let rec searcher k l agg =
        if k > i 
        then searcher 1 (l 1) agg
        else if l > j 
             then agg
             else searcher (k 1) l ((k,l)::agg)
    searcher 1 1 []
  

выполнение вышеуказанного с #time и 10000 для всех входных данных приводит

 list comprehension/singlesearcher-> negligable
cross product -> 320
list comprehension crossproduct -> 630
  

Почему понимание вложенного списка более чем в два раза превышает функциональную версию?

Ответ №1:

Да. Понимание списка обычно происходит медленнее, чем прямое использование F # list или array. (На моей машине я также нахожу похожее время с вами.)

Давайте посмотрим, как они реализованы. Версия для понимания списка на самом деле довольно сложная:

  1. последовательность / IEnumerable<int> создается с использованием синтаксиса понимания. Это просто ленивая последовательность, здесь тратится мало времени.

  2. затем эта последовательность преобразуется в F # List с помощью чего-то вроде Seq.toList . Здесь потрачено фактическое время. Здесь выполняется много HasNext MoveNext и switch (state) подобного кода. При таком количестве вызовов функций вы не можете ожидать быстрого выполнения.

В то время как функциональная версия doublesearcher должным образом оптимизирована для хвостовой рекурсии. Это более простая версия, чем понимание списка, и в ней используется несколько вызовов функций.

Обычно нас не волнует эта небольшая разница в производительности для последовательности, списков или массивов, если операция не очень критична. Я думаю, что в вашем примере генерация в любом случае одноразовая. Синхронизация в два раза не является большой проблемой. В других случаях, например при скалярном произведении двух векторов, использование массивов может сэкономить много времени, поскольку эта операция выполняется много раз.