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 04:54 am (UTC)
From: [identity profile] thedeemon.livejournal.com
В новом варианте вместо 40 раз считалось 1, отсюда и бешенное ускорение.
Реальное ускорение вышло в 5 раз. Результат несколько быстрее окамла и вдвое медленнее Ди. См. коммент рядом.
Edited Date: 2015-01-06 04:55 am (UTC)

Date: 2015-01-06 07:41 am (UTC)
From: [identity profile] gds.livejournal.com
интересно, откуда получилось быстрее. Есть идея, что дело в маленьком minor heap. Можно попробовать OCAMLRUNPARAM=s=4M , например. Лично мне было бы интересно узнать, поменялось ли что-то.

Date: 2015-01-06 08:21 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Бинго!

OCAMLRUNPARAM=s=4M уменьшает время до 1.35, а маленькая деступидитизация с возвращением одного числа вместо пары уменьшает еще до 1.24.

https://gist.github.com/thedeemon/48ab320c3675916f3980

Date: 2015-01-06 08:24 am (UTC)
From: [identity profile] gds.livejournal.com
а у х-я сейчас какой рекорд?

Date: 2015-01-06 08:26 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Вариант antilamer'a с ручным разворачиванием - 0.85 с.

Его аналог на окамле - 1.06 с.
https://gist.github.com/thedeemon/5afe7fe65a30165aae23
Edited Date: 2015-01-06 08:38 am (UTC)

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. 29th, 2026 07:00 am
Powered by Dreamwidth Studios