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-05 08:51 pm (UTC)
From: [identity profile] qnikst (from livejournal.com)
Ну и как обычно, Array далеко не лучшая штука, что есть в Haskell, поэтому если существует возможность, то лучше использовать пакет vectors и подходящие вектора оттуда. В последней версии gist (https://gist.github.com/qnikst/3517def97f21555dea83) я отвоевал ещё 10% (итого 0.4с), при этом не модифицируя изначальные вектора (что в прочем на таких размерах не важно).

Я попробую еще сделать код поаккуратнее, но думаю, что вопрос в треде он снимать должен.

Date: 2015-01-06 06:41 am (UTC)
From: [identity profile] antilamer.livejournal.com
Еще чуть побыстрее становится, если Exp сделать всячески строгим (на моей машине 0.8 => 0.7с). И еще (0.53с) вот так: https://gist.github.com/jkff/7d85da1e409c8725f282

Date: 2015-01-06 07:32 am (UTC)
From: [identity profile] qnikst (from livejournal.com)
Да, верно. Про строгие поля, вообще rule of thumb это иметь строгие поля, если обратное не требуется. И распаковывать строгие поля размером не больше указателя (начиная с 7.8 поведение по умолчанию). В данном случаеслучае делать строгим list немного лишнее т.к. форсирует только первый элемент, а вот сделать вместо unboxed vector можно.

Про код я верно понял, что fold развернут явно и eval заинлайнен или что-нибудь ещё упустил? В принципе компилятор должен сам уметь такое ну или с подсказки {-# INLINE eval #-}.

Date: 2015-01-06 08:02 am (UTC)
From: [identity profile] antilamer.livejournal.com
Я тут сделал несколько мелких полезных вещей и пару бесполезных, уже трудно разделить :) Прирост с 0.7 до 0.53 получился после следующего:
1) инлайн foldl' в виде функции go
2) вынесение неизменного аргумента "a" в evalBlock за пределы определения рекурсивных функций go, eval.

2 - это трансформация вида
map f [] = []; map f (x:xs) = f x : map f xs
---->
map f = m where m [] = []; m (x:xs) = f x : m xs
(кстати, в ghc, вероятно, именно поэтому функция map реализована вторым способом - во всяком случае, так было последний раз, когда я смотрел)

Profile

thedeemon: (Default)
Dmitry Popov

December 2025

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

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 29th, 2026 03:21 pm
Powered by Dreamwidth Studios