Подходит ли для скриптов быстрый стартующий интерпретатор Haskell? - PullRequest
18 голосов
/ 03 апреля 2009

Кто-нибудь знает о быстром запуске интерпретатора Haskell, который подойдет для написания сценариев оболочки? Выполнение «Hello World» с использованием Hugs заняло 400 мс на моем старом ноутбуке и 300 мс на моем текущем Thinkpad X300. Это слишком медленно для мгновенного ответа. Времена с GHCi похожи.

Функциональные языки не должны быть медленными: и Objective Caml, и Moscow ML запускают hello world за 1 мс или меньше.

Уточнение : Я интенсивный пользователь GHC и знаю, как использовать GHCi. Я знаю все о компиляции, чтобы получить вещи быстро. Расходы на разбор должны быть совершенно не важны: если ML и OCaml могут начать в 300 раз быстрее, чем GHCi, тогда есть возможности для улучшения.

Я ищу

  • Удобство написания скриптов: один исходный файл, без двоичного кода, один и тот же код работает на всех платформах
  • Производительность сравнима с другими интерпретаторами, включая быстрый запуск и выполнение для простой программы, такой как

    module Main where
    main = print 33
    

Я не ищу скомпилированную производительность для более серьезных программ. Весь смысл в том, чтобы выяснить, может ли Haskell быть полезен для написания сценариев.

Ответы [ 5 ]

31 голосов
/ 24 июля 2010

Почему бы не создать интерфейсную часть сценария, которая компилирует сценарий, если его не было раньше, или если скомпилированная версия устарела.

Вот основная идея, этот код может быть значительно улучшен - ищите путь, а не предполагая, что все находится в одном и том же каталоге, лучше обрабатывайте другие расширения файлов и т. Д. Также я довольно хорошо разбираюсь в кодировании на haskell (ghc-compiled -script.hs):

import Control.Monad
import System
import System.Directory
import System.IO
import System.Posix.Files
import System.Posix.Process
import System.Process

getMTime f = getFileStatus f >>= return . modificationTime

main = do
  scr : args <- getArgs
  let cscr = takeWhile (/= '.') scr

  scrExists <- doesFileExist scr
  cscrExists <- doesFileExist cscr
  compile <- if scrExists && cscrExists
               then do
                 scrMTime <- getMTime scr
                 cscrMTime <- getMTime cscr
                 return $ cscrMTime <= scrMTime
               else
                   return True

  when compile $ do
         r <- system $ "ghc --make " ++ scr
         case r of
           ExitFailure i -> do
                   hPutStrLn stderr $
                            "'ghc --make " ++ scr ++ "' failed: " ++ show i
                   exitFailure
           ExitSuccess -> return ()

  executeFile cscr False args Nothing

Теперь мы можем создавать такие сценарии, как этот (hs-echo.hs):

#! ghc-compiled-script

import Data.List
import System
import System.Environment

main = do
  args <- getArgs
  putStrLn $ foldl (++) "" $ intersperse " " args

А теперь запустим его:

$ time hs-echo.hs "Hello, world\!"     
[1 of 1] Compiling Main             ( hs-echo.hs, hs-echo.o )
Linking hs-echo ...
Hello, world!
hs-echo.hs "Hello, world!"  0.83s user 0.21s system 97% cpu 1.062 total

$ time hs-echo.hs "Hello, world, again\!"
Hello, world, again!
hs-echo.hs "Hello, world, again!"  0.01s user 0.00s system 60% cpu 0.022 total
8 голосов
/ 03 апреля 2009

Использование ghc -e в значительной степени эквивалентно вызову ghci. Я считаю, что GHC runhaskell компилирует код во временный исполняемый файл перед его запуском, в отличие от интерпретации его как ghc -e / ghci, но я не уверен на 100%.

$ time echo 'Hello, world!'
Hello, world!

real    0m0.021s
user    0m0.000s
sys     0m0.000s
$ time ghc -e 'putStrLn "Hello, world!"'
Hello, world!

real    0m0.401s
user    0m0.031s
sys     0m0.015s
$ echo 'main = putStrLn "Hello, world!"' > hw.hs
$ time runhaskell hw.hs
Hello, world!

real    0m0.335s
user    0m0.015s
sys     0m0.015s
$ time ghc --make hw
[1 of 1] Compiling Main             ( hw.hs, hw.o )
Linking hw ...

real    0m0.855s
user    0m0.015s
sys     0m0.015s
$ time ./hw
Hello, world!

real    0m0.037s
user    0m0.015s
sys     0m0.000s

Насколько сложно просто скомпилировать все ваши "скрипты" перед их выполнением?

Редактировать

Ах, обеспечение двоичных файлов для нескольких архитектур действительно является проблемой. Я шел по этой дороге раньше, и это не очень весело ...

К сожалению, я не думаю, что можно улучшить загрузку любого компилятора Haskell. Декларативный характер языка означает, что необходимо сначала прочитать всю программу, даже прежде чем пытаться что-либо проверить, не обращать внимания на выполнение, а затем вы либо столкнетесь с затратами на анализ строгости, либо с ненужной ленью и громом.

Популярные языки сценариев (shell, Perl, Python и т. Д.) И языки на основе ML требуют только одного прохода ... хорошо, ML требует статического прохода проверки типов, а Perl имеет этот забавный 5 проходов схема (две из которых работают в обратном направлении); В любом случае, процедурный подход означает, что компилятору / интерпретатору гораздо проще собрать вместе кусочки программы.

Короче говоря, я не думаю, что возможно стать намного лучше, чем это. Я не проверял, есть ли у Hugs или GHCi более быстрый запуск, но есть разница, отличная от не-Haskell языков.

5 голосов
/ 03 апреля 2009

Если вы действительно обеспокоены скоростью, вам будет мешать повторный анализ кода при каждом запуске. Haskell не нужно запускать из интерпретатора, скомпилируйте его с GHC , и вы получите превосходную производительность.

4 голосов
/ 03 апреля 2009

У вас есть две части к этому вопросу:

  • Вы заботитесь о производительности
  • вы хотите сценарий

Если вы заботитесь о производительности, единственным серьезным вариантом является GHC, который очень и очень быстр: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=all

Если вы хотите что-то легкое для сценариев Unix, я бы использовал GHCi. Это примерно в 30 раз быстрее, чем Hugs, но также поддерживает все библиотеки на взломе.

Так что установите GHC сейчас (и получите GHCi бесплатно).

3 голосов
/ 20 ноября 2010

А как насчет наличия демона ghci и скрипта фидера, который получает путь и местоположение скрипта, связывается с уже запущенным процессом ghci для загрузки и выполнения скрипта в соответствующем каталоге и передает результат обратно в скрипт фидера для stdout?

К сожалению, я понятия не имею, как написать что-то подобное, но кажется, что это может быть очень быстро, судя по скорости: l в ghci. Как кажется, большая часть затрат в runhaskell заключается в запуске ghci, а не в разборе и запуске скрипта.

Редактировать: После некоторой игры я обнаружил, что пакет Hint (обертка вокруг GHC API) идеально подходит здесь. Следующий код загрузит переданное имя модуля (здесь предполагается, что он находится в том же каталоге) и выполнит основную функцию. Теперь «все», что осталось, - это сделать его демоном и заставить его принимать сценарии в канале или сокете.

import Language.Haskell.Interpreter
import Control.Monad

run = runInterpreter . test

test :: String -> Interpreter ()
test mname = 
  do
    loadModules [mname ++ ".hs"]
    setTopLevelModules [mname]
    res <- interpret "main" (as :: IO())
    liftIO res

Edit2: Что касается stdout / err / in go, используя этот специфический трюк GHC Похоже, можно было бы перенаправить стандартные клиентские программы в фидер затем запрограммируйте его в несколько именованных каналов (возможно), к которым демон всегда подключен, и затем верните стандартный вывод демона в другой именованный канал, который слушает программа фидера. Псевдо-пример:

grep ... | feeder my_script.hs | xargs ...
            |   ^---------------- <
            V                      |
         named pipe -> daemon -> named pipe

Здесь фидер - это небольшая скомпилированная программа, которая просто перенаправляет std в, а затем обратно из демона и сообщает демону имя и местоположение сценария.

...