#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. (На моей машине я также нахожу похожее время с вами.)
Давайте посмотрим, как они реализованы. Версия для понимания списка на самом деле довольно сложная:
-
последовательность /
IEnumerable<int>
создается с использованием синтаксиса понимания. Это просто ленивая последовательность, здесь тратится мало времени. -
затем эта последовательность преобразуется в F # List с помощью чего-то вроде
Seq.toList
. Здесь потрачено фактическое время. Здесь выполняется многоHasNext
MoveNext
иswitch (state)
подобного кода. При таком количестве вызовов функций вы не можете ожидать быстрого выполнения.
В то время как функциональная версия doublesearcher
должным образом оптимизирована для хвостовой рекурсии. Это более простая версия, чем понимание списка, и в ней используется несколько вызовов функций.
Обычно нас не волнует эта небольшая разница в производительности для последовательности, списков или массивов, если операция не очень критична. Я думаю, что в вашем примере генерация в любом случае одноразовая. Синхронизация в два раза не является большой проблемой. В других случаях, например при скалярном произведении двух векторов, использование массивов может сэкономить много времени, поскольку эта операция выполняется много раз.