haskell n00b question
Jan. 6th, 2015 12:30 amДля одной задачки всхотелось мне сделать интерпретатор крайне простого язычка: есть память на дюжину целых чисел и AST из трех операций - сравнение двух ячеек, копирование из одной в другую и обмен значениями меж двух ячеек. Стал делать на хаскеле и сразу столкнулся с тем, что наивное решение в лоб тормозит: аналогичный код на окамле в 4 раза быстрее, а на Ди - в 10 раз. Вот так выглядит мой позор на хаскеле:
А вот прямой перевод на Окамл, почти вчетверо быстрее:
А теперь вопрос: как хаскельный вариант следует переписать, чтобы он стал побыстрее и выглядел не слишком страшно?
Полные тексты тестов:
Haskell - 7.19 s
OCaml - 1.85 s
D - 0.69 s (использовался DMD - наименее оптимизирующий из компиляторов D, т.е. можно еще быстрее)
Upd: исправлен баг в окамловской версии, время перемеряно.
Upd2: не, не было там бага.
data Exp = IfGt Int Int Block Block | Swap Int Int | Copy Int Int deriving Show type Block = [Exp] eval :: STUArray s Int Int -> Exp -> ST s (STUArray s Int Int, Int) eval a (IfGt i j b1 b2) = do ai <- readArray a i aj <- readArray a j let b = if ai > aj then b1 else b2 (r, n) <- evalBlock a b return (r, n+1) eval a (Swap i j) = do ai <- readArray a i aj <- readArray a j writeArray a i aj writeArray a j ai return (a, 1) eval a (Copy i j) = do aj <- readArray a j writeArray a i aj return (a, 1) evalBlock :: STUArray s Int Int -> Block -> ST s (STUArray s Int Int, Int) evalBlock a blk = foldM f (a,0) blk where f (a,cnt) exp = fmap (\(r, n) -> (r, cnt + n)) $ eval a exp
А вот прямой перевод на Окамл, почти вчетверо быстрее:
type exp = IfGt of int * int * block * block | Swap of int * int | Copy of int * int and block = exp list;; let rec eval a = function | IfGt(i, j, b1, b2) -> let (r,n) = evalBlock a (if a.(i) > a.(j) then b1 else b2) in (r, n+1) | Swap(i, j) -> let ai = a.(i) and aj = a.(j) in a.(i) <- aj; a.(j) <- ai; (a, 1) | Copy(i, j) -> a.(i) <- a.(j); (a, 1) and evalBlock a blk = let f (m, cnt) exp = let (r,n) = eval m exp in (r, cnt + n) in List.fold_left f (a,0) blk
А теперь вопрос: как хаскельный вариант следует переписать, чтобы он стал побыстрее и выглядел не слишком страшно?
Полные тексты тестов:
Haskell - 7.19 s
OCaml - 1.85 s
D - 0.69 s (использовался DMD - наименее оптимизирующий из компиляторов D, т.е. можно еще быстрее)
Upd: исправлен баг в окамловской версии, время перемеряно.
Upd2: не, не было там бага.
no subject
Date: 2015-01-05 05:50 pm (UTC)Наверно, там ленивость сыграла злую шутку, и хаскелл вместо вычислений рожает мега-санки, которые отрабатывают лишь в самом конце. А это удар по памяти, вот и тормоза.
Самле простое - натыкать форсирования вычислений гле только можно (на возврате из eval?) - оператор $! емнип.
no subject
Date: 2015-01-05 06:04 pm (UTC)Если что, STUArray - мутабельный строгий анбоксед массив, живущий в ST.
Но где-то санки могут действительно копиться, ибо аллоцируют приведенные функции как черти.
no subject
Date: 2015-01-05 06:14 pm (UTC)no subject
Date: 2015-01-05 06:15 pm (UTC)GHC 7.6.3
no subject
Date: 2015-01-05 06:19 pm (UTC)upd. Конкретно, посмотрите вот эту прагму: https://downloads.haskell.org/~ghc/7.0.2/docs/html/users_guide/pragmas.html#unpack-pragma . Потому что у вас в тесте, насколько я понимаю, в АСТ инты завернуты в отдельные танки. Нужно их как миниум распаковать, иначе сравнение нечестное.
no subject
Date: 2015-01-05 06:43 pm (UTC)data Exp = IfGt {-# UNPACK #-} !Int {-# UNPACK #-} !Int Block Block | Swap {-# UNPACK #-} !Int {-# UNPACK #-} !Int | Copy {-# UNPACK #-} !Int {-# UNPACK #-} !Int7.08 s. Крайне незначительное улучшение.
Еще пробовал unsafeRead/unsafeWrite для массива, еще около секунды дало выигрыш, тоже маловато.
no subject
Date: 2015-01-05 07:28 pm (UTC)А вообще надо смотреть core.
no subject
Date: 2015-01-05 08:15 pm (UTC)no subject
Date: 2015-01-05 08:22 pm (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2015-01-05 08:20 pm (UTC)Если использовать Data.Foldable то начинает выполняться нормально у меня за 0.05с, при том, что изначальная версия за 5с.
Ссылку на полный код можно найти на форке гиста, т.к. боюсь в жж её не запостить.
UPD. т.е. import Data.Foldable (foldlM) и использовать его вместо foldM.
так же по желанию добавить ! к аккумулятору f, т.е. f (!a, !cnt) ...
UPD2. с Data.Foldable можно оставлять вариант с fmap и убирать там явное вычисление, т.к. ghc уже справляется.
no subject
Date: 2015-01-05 08:51 pm (UTC)Я попробую еще сделать код поаккуратнее, но думаю, что вопрос в треде он снимать должен.
no subject
Date: 2015-01-06 06:41 am (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2015-01-05 10:22 pm (UTC)В очередной версии гиста положено.
(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-01-05 09:56 pm (UTC)а в продакшне, матерясь, переписываешь в кашу из бангов и unsafePerformIO на MVar
no subject
Date: 2015-01-05 10:13 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-01-05 11:33 pm (UTC)...И вдвое короче.
no subject
Date: 2015-01-06 11:02 am (UTC)no subject
Date: 2015-01-06 11:10 am (UTC)Но ради эксперимента можно попробовать и с массивами на окамле.
no subject
Date: 2015-01-06 01:14 pm (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2015-01-06 11:10 am (UTC)no subject
Date: 2015-01-06 11:25 am (UTC)На моем ноуте и GHC 7.6.3:
Haskell - 0.85 s
https://gist.github.com/jkff/7d85da1e409c8725f282
OCaml - 1.06 s
https://gist.github.com/thedeemon/5afe7fe65a30165aae23
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-01-07 04:11 am (UTC)https://gist.github.com/thedeemon/c2c3e436d78592dabf63
Upd. ... пока я не поменял компилятор D на LDC, получив там 0.40 с.
no subject
Date: 2015-01-07 03:14 am (UTC)1. не создаём closure на каждый вызов eval+go -- это мне дало 1.02 -> 0.94
2. используем указание типа массива, чтобы избежать полиморфного/структурного сравнения, это дало 0.94 -> 0.57
Вот теперь збс.
no subject
Date: 2015-01-07 04:06 am (UTC)0.57 с у меня тоже.
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2015-03-15 10:40 am (UTC)Original (https://gist.github.com/thedeemon/d9dfe4982ab6c5e68854) - 3.855s
Fast (https://gist.github.com/jkff/7d85da1e409c8725f282) - 0.595s
This one - 0.375s