18

シェルスクリプトの作成に適した、すぐに起動できる Haskell インタープリターを知っている人はいますか? Hugs を使用して「hello world」を実行すると、古いラップトップでは 400 ミリ秒かかり、現在の Thinkpad X300 では 300 ミリ秒かかりました。瞬時に応答するには遅すぎます。GHCi の使用時間も同様です。

関数型言語は遅い必要はありません。Objective Caml と Moscow ML はどちらも、hello world を 1 ミリ秒以下で実行します。

明確化: 私はGHCのヘビーユーザーであり、GHCiの使い方を知っています。物事を高速化するためのコンパイルについてはすべて知っています。解析コストはまったく関係ないはずです: ML と OCaml が GHCi よりも 300 倍速く起動できるなら、改善の余地があります。

を探しています

  • スクリプト作成の利便性: 1 つのソース ファイル、バイナリ コードなし、すべてのプラットフォームで同じコードが実行される
  • 次のような単純なプログラムの高速起動と実行など、他のインタープリターに匹敵するパフォーマンス

    module Main where
    main = print 33
    

より深刻なプログラムのコンパイルされたパフォーマンスを探しているわけではありません。要点は、Haskell がスクリプト作成に役立つかどうかを確認することです。

4

5 に答える 5

31

スクリプトが以前に作成されていない場合、またはコンパイルされたバージョンが古くなっている場合は、スクリプトをコンパイルするスクリプトフロントエンドを作成してみませんか。

これが基本的な考え方です。このコードは大幅に改善される可能性があります。すべてが同じディレクトリにあると想定するのではなく、パスを検索したり、他のファイル拡張子をより適切に処理したりします。 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
于 2010-07-24T18:00:35.030 に答える
8

を使用ghc -eすることは、 を呼び出すこととほとんど同じghciです。GHCは/のrunhaskellように解釈するのではなく、コードを実行する前に一時的な実行可能ファイルにコンパイルすると思いますが、100% 確実ではありません。ghc -eghci

$ 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 コンパイラの起動時のオーバーヘッドをこれ以上改善することはできないと思います。言語の宣言型の性質は、何かを型チェックしようとする前であっても、実行を気にせずに最初にプログラム全体を読む必要があることを意味します。その後、厳密性分析の代償や不必要な怠惰やサンクに苦しむことになります。

一般的な「スクリプト」言語 (シェル、Perl、Python など) と ML ベースの言語では、1 つのパスのみが必要です。そのうちの 2 つは逆に実行されます)。いずれにせよ、手続き型であるということは、コンパイラ/インタプリタがプログラムのビットをまとめて組み立てる作業がはるかに簡単になることを意味します。

要するに、これ以上良くなることは不可能だと思います。Hugs や GHCi の起動が速いかどうかはテストしていませんが、Haskell 以外の言語との違いはまだあります。

于 2009-04-03T17:32:59.327 に答える
5

速度に本当に関心がある場合は、起動するたびにコードを再解析することで妨げられます。Haskellはインタプリタから実行する必要はなく、GHCでコンパイルすれば、優れたパフォーマンスが得られるはずです。

于 2009-04-03T06:49:14.680 に答える
4

この質問には 2 つの部分があります。

  • パフォーマンスを気にする
  • スクリプトが必要です

パフォーマンスを重視する場合、唯一の深刻なオプションは GHC です。これは非常に高速です: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=all

Unix スクリプト用に軽いものが必要な場合は、GHCi を使用します。Hugs よりも約 30 倍高速ですが、ハックのすべてのライブラリもサポートしています。

GHC を今すぐインストールしてください (そして GHCi を無料で入手してください)。

于 2009-04-03T16:24:11.410 に答える
3

スクリプトのパスと場所を取得し、既に実行中の ghci プロセスと通信して適切なディレクトリにスクリプトをロードして実行し、出力を stdout のフィーダー スクリプトに戻す ghci デーモンとフィーダー スクリプトを用意するのはどうでしょうか。

残念ながら、このようなものを書く方法はわかりませんが、ghci の :l の速度から判断すると、非常に高速であると思われます。runhaskell のコストのほとんどは、スクリプトの解析と実行ではなく、ghci の起動にかかるようです。

編集:いくつか遊んだ後、ヒントパッケージ(GHC APIのラッパー)がここで完全に使用できることがわかりました。次のコードは、渡されたモジュール名 (ここでは同じディレクトリにあると想定) をロードし、main 関数を実行します。あとは、それをデーモンにして、パイプまたはソケットでスクリプトを受け入れるようにするだけです。

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 に関する限り、この特定の GHC トリックを使用して、クライアント プログラムの std をフィーダー プログラムにリダイレクトし、次にデーモンが接続されているいくつかの名前付きパイプ (おそらく) にリダイレクトすることが可能であるように見えます常にに接続し、デーモンの stdout を、フィーダー プログラムがリッスンしている別の名前付きパイプに戻します。疑似例:

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

ここで、フィーダーは、std をデーモンにリダイレクトしてデーモンから戻し、スクリプトの名前と場所をデーモンに与えるための小さなコンパイル済みハーネス プログラムです。

于 2010-11-20T07:02:38.453 に答える