Для одной задачки всхотелось мне сделать интерпретатор крайне простого язычка: есть память на дюжину целых чисел и AST из трех операций - сравнение двух ячеек, копирование из одной в другую и обмен значениями меж двух ячеек. Стал делать на хаскеле и сразу столкнулся с тем, что наивное решение в лоб тормозит: аналогичный код на окамле в 4 раза быстрее, а на Ди - в 10 раз. Вот так выглядит мой позор на хаскеле:
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 sOCaml - 1.85 sD - 0.69 s (использовался DMD - наименее оптимизирующий из компиляторов D, т.е. можно еще быстрее)
Upd: исправлен баг в окамловской версии, время перемеряно.
Upd2: не, не было там бага.