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 10:22 pm (UTC)В очередной версии гиста положено.
no subject
Date: 2015-01-06 04:28 am (UTC)У меня работает за 0.075 с, т.е. в 95 раз быстрее, чем было, и почти в 10 раз быстрее D. Подозрительно быстро (4 такта на 1 узел AST?), но если это не глюк, то просто чудо.
Upd. нашел обман:
main = print $ minimum $ replicate 40 (calcScore sornet)
тут мы не 40 раз считаем, а лишь один раз. Отсюда и невероятное ускорение.
Если считать на 40 формально отличающихся программах одинакового размера
main = print . minimum . map calcScore $ [Copy 10 (x `mod` 10) : sornet | x <- [1..40]]
то получается 1.40 с, уже реалистичное время. Быстрее окамла, но вдвое медленнее D.
no subject
Date: 2015-01-06 06:40 am (UTC)В принципе мне кажется там можно ещё попробовать поускорять, но это будет чёрная магия.
no subject
Date: 2015-01-06 07:26 am (UTC)