performance - funcion - editor de secuencias de comandos google
¿Hay un intérprete de Haskell de inicio rápido adecuado para las secuencias de comandos? (5)
¿Por qué no crear un front-end de script que compile el script si no ha estado antes o si la versión compilada está desactualizada?
Aquí está la idea básica, este código podría mejorarse mucho: busque la ruta en lugar de asumir que todo está en el mismo directorio, maneje mejor otras extensiones de archivo, etc. También estoy bastante verde en la codificación de 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
Ahora podemos crear scripts como este (hs-echo.hs):
#! ghc-compiled-script
import Data.List
import System
import System.Environment
main = do
args <- getArgs
putStrLn $ foldl (++) "" $ intersperse " " args
Y ahora ejecutándolo:
$ 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
¿Alguien sabe de un intérprete de Haskell de inicio rápido que sea adecuado para escribir scripts de shell? Ejecutar ''hola mundo'' usando Hugs tomó 400 ms en mi vieja computadora portátil y tomó 300 ms en mi Thinkpad X300 actual. Eso es demasiado lento para la respuesta instantánea. Los tiempos con GHCi son similares.
Los lenguajes funcionales no tienen que ser lentos: tanto Objective Caml como Moscow ML ejecutan hola mundo en 1 ms o menos.
Aclaración : soy un gran usuario de GHC y sé cómo usar GHCi. Sé todo sobre compilar para conseguir cosas rápido. Los costos del análisis deben ser completamente irrelevantes: si ML y OCaml pueden comenzar 300 veces más rápido que GHCi, entonces hay espacio para mejorar.
Busco
- La comodidad de las secuencias de comandos: un archivo de origen, sin código binario, el mismo código se ejecuta en todas las plataformas
Rendimiento comparable al de otros intérpretes, incluido el inicio y la ejecución rápidos para un programa simple como
module Main where main = print 33
No estoy buscando un rendimiento compilado para programas más serios. El punto principal es ver si Haskell puede ser útil para las secuencias de comandos.
¿Qué pasa con tener un demonio ghci y un script de feeder que toma la ruta y la ubicación del script, se comunica con el proceso ghci que ya se está ejecutando para cargar y ejecutar el script en el directorio adecuado y canaliza la salida al script de feeds para stdout?
Desafortunadamente, no tengo idea de cómo escribir algo como esto, pero parece que podría ser realmente rápido a juzgar por la velocidad de: l en ghci. Como parece, la mayor parte del costo en runhaskell es iniciar Ghci, no analizar y ejecutar el script.
Edición: Después de jugar un poco, encontré que el paquete Hint (una envoltura alrededor de la API de GHC) es de perfecto uso aquí. El siguiente código cargará el nombre del módulo pasado (aquí se supone que está en el mismo directorio) y ejecutará la función principal. Ahora ''todo'' lo que queda es convertirlo en un demonio, y hacer que acepte scripts en una tubería o socket.
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: En lo que respecta a stdout / err / in, usando este truco específico de GHC Parece que sería posible redireccionar los std del programa cliente al programa alimentador, luego a algunas canalizaciones con nombre (quizás) que el demonio está conectado en todo momento, y luego devolver la salida estándar del daemon a otra canalización con nombre que el programa de alimentación está escuchando. Pseudo-ejemplo:
grep ... | feeder my_script.hs | xargs ...
| ^---------------- <
V |
named pipe -> daemon -> named pipe
Aquí, el alimentador sería un pequeño programa de arnés compilado para simplemente redirigir los std hacia y luego salir del daemon y dar el nombre y la ubicación de la secuencia de comandos al daemon.
Si está realmente preocupado por la velocidad, se verá obstaculizado por volver a analizar el código de cada lanzamiento. Haskell no necesita ejecutarse desde un intérprete, compilarlo con GHC y debería obtener un rendimiento excelente.
Tienes dos partes para esta pregunta:
- te importa el rendimiento
- quieres scripting
Si te importa el rendimiento, la única opción seria es GHC, que es muy rápida: http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=all
Si quieres algo liviano para los scripts de Unix, usaría GHCi. Es aproximadamente 30 veces más rápido que Hugs, pero también es compatible con todas las bibliotecas en hackage.
Entonces instale GHC ahora (y obtenga GHCi gratis).
Usar ghc -e
es prácticamente equivalente a invocar ghci
. Creo que runhaskell
de GHC compila el código en un ejecutable temporal antes de ejecutarlo, en lugar de interpretarlo como ghc -e
/ ghci
, pero no estoy 100% seguro.
$ 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
¿Qué tan difícil es simplemente compilar todos sus "scripts" antes de ejecutarlos?
Editar
Ah, proporcionar binarios para múltiples arquitecturas es realmente un dolor. He ido por ese camino antes, y no es muy divertido ...
Lamentablemente, no creo que sea posible mejorar la sobrecarga de inicio de cualquier compilador de Haskell. La naturaleza declarativa del lenguaje significa que es necesario leer todo el programa primero, incluso antes de tratar de verificar cualquier cosa, sin importarle la ejecución, y luego, o bien sufres el costo del análisis de rigor o la pereza y el ruido innecesarios.
Los lenguajes populares de ''scripting'' (shell, Perl, Python, etc.) y los lenguajes basados en ML solo requieren un solo paso ... bueno, ML requiere un pase de comprobación de tipo estático y Perl tiene este divertido esquema de 5 pases (con dos de ellos corriendo en reversa); de cualquier manera, ser procedimental significa que el compilador / intérprete tiene mucho más fácil un trabajo de ensamblar los bits del programa juntos.
En resumen, no creo que sea posible mejorar mucho más que esto. No he probado para ver si Hugs o GHCi tiene un inicio más rápido, pero cualquier diferencia todavía está lejos de los idiomas que no son de Haskell.