thedeemon: (office)
[personal profile] thedeemon
Для одной задачки всхотелось мне сделать интерпретатор крайне простого язычка: есть память на дюжину целых чисел и 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 s
OCaml - 1.85 s
D - 0.69 s (использовался DMD - наименее оптимизирующий из компиляторов D, т.е. можно еще быстрее)

Upd: исправлен баг в окамловской версии, время перемеряно.
Upd2: не, не было там бага.

Date: 2015-01-06 03:56 pm (UTC)
From: [identity profile] antilamer.livejournal.com
Спасибо! Bang pattern'ами в основном утыкал, кажется, я - по неаккуратности.
Кстати, эта версия быстрее?

Date: 2015-01-06 04:18 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Медленне немного - 0.91 вместо 0.85.

Date: 2015-01-06 05:02 pm (UTC)
From: [identity profile] Игорь Петров (from livejournal.com)
Не заметил разницы, но я компилировал 7.8.3

Date: 2015-01-08 05:32 pm (UTC)
From: [identity profile] Игорь Петров (from livejournal.com)
Да, как-то мне не давала покоя мысль, почему foldlM не оптимизируется, сейчас дошло, что
foldlM не при чем - заменять его на рекурсивную лапшу не надо. Виноват вот этот вот код
 (+cnt) <$> eval exp 

так что можно нормально комбинировать комбинаторы:
evalBlock :: STVector s Int -> Block -> ST s Int
evalBlock a = foldlM f 0 where
    f cnt exp = do
        n2 <- eval exp
        return $! n2 + cnt -- в 7.10 ЕМНИП будет <$!>
    eval (IfGt i j b1 b2) = do
      ai <- a ! i
      aj <- a ! j
      let b = if ai > aj then b1 else b2
      n <- evalBlock a b
      return (n+1)
    eval (Swap i j) = do
      UM.unsafeSwap a i j
      return 1
    eval (Copy i j) = do
      UM.unsafeWrite a i =<< a ! j
      return 1

    (!) = UM.unsafeRead

Работает с той же скоростью.

Profile

thedeemon: (Default)
Dmitry Popov

December 2025

S M T W T F S
 12 3456
789101112 13
14151617181920
21222324252627
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 28th, 2026 10:45 pm
Powered by Dreamwidth Studios