Подтвердить что ты не робот

Трудно понять поведение распределения памяти Haskell

Я наткнулся на Haskell и FP и был ошеломлен возможностями. И старая математика, задуманная внутри меня, без труда писала наивный код для реальных полезных целей. Однако, несмотря на все показания, мне все еще очень трудно понять, как не поражать некоторые неожиданные узкие места.

Итак, я пишу очень короткие фрагменты кода с наивными реализациями, а затем попробую немного изменить, чтобы увидеть, как реагирует производительность. И вот один пример, который я действительно не могу понять, я написал эту функцию, которая находит решение проблемы Josephus, реализация наивного списка.

m = 3
n = 3000
main = putStr $ "Soldier #" ++ (show $ whosLeft [1..n]) ++ " survived...\n"

whosLeft [lucky] = lucky
whosLeft soldiers = whosLeft $ take (length soldiers -1) $ drop m $ cycle soldiers

Последний работает в 190 мс с производительностью 63% в соответствии с RTS.

Тогда первое, что я хотел попробовать, это удалить (length soldier -1) и заменить его на декрементирующее целое.

Время работы выросло до 900 мс и производительность до 16%, и в 47 раз больше памяти, чем более простой код выше! Поэтому я добавил строгую оценку, заставил тип Int, попробовал такие вещи, как удаление глобальных переменных и других, но не очень полезный. И я просто не могу понять это замедление.

m = 3::Int
n = 3000::Int
main = putStr $ "Soldier #" ++ (show $ whosLeft n [1..n]) ++ " survived...\n"

whosLeft 1 [lucky] = lucky
whosLeft n' soldiers = n' `seq` left `seq` whosLeft (n'-1) left
                where left = take (n'-1) $ drop m $ cycle soldiers

Я просидел через статьи и публикации, связанные с работой, но я просто не понимаю, что это такое. Все еще будучи Haskell noob, мне, должно быть, не хватает чего-то большого... Как этот добавленный параметр (предварительно пережевывание вычислений...) значительно снижает скорость?

PS: Я знаю, если бы Джозефус действительно был с 3000 солдатами, им не понадобилось бы самоубийства...

4b9b3361

Ответ 1

Первое решение заставляет весь спинной хребет солдат списывать, занимая его длину. Второе решение только заставляет (используя seq) возглавить список солдат. Замените left между seq на length left, и вы вернете свою производительность.