Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Room to optimize fast path for Haskell shell scripts run via stack? #1330

Open
rrnewton opened this issue Nov 10, 2015 · 12 comments
Open

Room to optimize fast path for Haskell shell scripts run via stack? #1330

rrnewton opened this issue Nov 10, 2015 · 12 comments

Comments

@rrnewton
Copy link
Contributor

rrnewton commented Nov 10, 2015

I absolutely love being able to make self contained shell scripts like this:

#!/usr/bin/env stack
-- stack --verbosity silent --resolver lts-3.8 --install-ghc runghc  --package turtle --package filemanip --package optparse-applicative
module Main where
import Turtle
main :: IO ()
main = putStrLn "hello world"

But the above hello world takes up to 2 seconds to run on my laptop, even after the first run where the installs occur. Could we get a description of what the fundamental work to do in this scenario is? Which parts of it must be slow, and which parts may have room for improvement?

For example, I would hope the fast path would basically say:

  • is snapshot lts-3.8 there already?
  • are the requested packages installed already? (3 checks)
  • go!

And it sounds like those should be O(1) operations, checking only on a few packages.

I'm guessing the current algorithm is more conservative, running some kind of O(N) sanity check over, e.g. all the transitive dependencies? If it's something like that, should we work on a fast or trusted mode where only the constant time checks are done?

@rubik
Copy link
Contributor

rubik commented Nov 11, 2015

Probably somewhat related to #1235.

@borsboom borsboom added this to the P2: Should milestone Nov 17, 2015
@soenkehahn
Copy link

Note that stack runs scripts like this in interpreted mode, which is inherently slower than running a compiled executable. My guess would be that the most time is spent in loading up ghc and type-checking the script.

There is a replacement for runghc that solves this problem by compiling scripts to machine-code and caching the results: http://hackage.haskell.org/package/runghc. I wonder if we want something like this in stack.

This caching is not strictly better than the interpreted mode however, since compiling the program to machine code can be slower than interpreting, in case the cache is out of date. This can be quite annoying when developing a script. (Since you will modify the file constantly and always invalidate your cache through that.)

@rrnewton
Copy link
Contributor Author

rrnewton commented May 5, 2016

That hypothesis is easy to test. runghc on the script above takes 400ms on my (wimpy) laptop right now, whereas it still takes 2s under stack.

At the same time, sure, for it would be great to transparently compiler scripts sometimes. This would certainly help with throughput, and since 400ms is ok but not great, maybe it could help there too. But you'd figure that for stack the latency would be similar to stack build.

@mgsloan
Copy link
Contributor

mgsloan commented May 5, 2016

@rrnewton Are you on the latest stack? 1.1 comes with some performance enhancements, namely, avoiding loading the hackage index when it's unneeded. More performance enhancements in the pipeline :) (replacing binary with something faster)

@rrnewton
Copy link
Contributor Author

rrnewton commented May 6, 2016

Ah, no I hadn't updated to 1.1. I did just now and do see some perf improvement. That hello-world script drops from 2.0s to 1.5s on this machine.

@da-x
Copy link
Contributor

da-x commented Aug 12, 2016

I'd also be happy for support in caching compiled exes for Stack scripts! It would be the only way to compete with the sub-millisecond bash script execution.

@sboosali
Copy link

sboosali commented May 1, 2017

any update? this seems like a good idea.

@rrnewton
Copy link
Contributor Author

rrnewton commented May 1, 2017

It looks like things may have already improved substantially. These days, on stack 1.4.0, that hello world script is taking only 700ms. But then again, with time stack --resolver=lts-3.8 runghc hello.hs, it takes only 300ms. So it looks like 400ms are still spent in the "checking" part, before "doing" begins.

@rrnewton
Copy link
Contributor Author

rrnewton commented May 1, 2017

@da-x for bettor or worse, even though stack scripts are not full blown projects (stack.yaml, .cabal, etc), they can import other Haskell files. So that means that if we want to cache exes for scripts, we have to reliably find all the source code imported by the script and include it / hash it when we go to do the cache lookup.

@sboosali
Copy link

sboosali commented May 1, 2017

huh, still takes 1.1s for me, then 1.7s when adding a few language extensions and (unused) import statements, to print a "hello". either way, it does seem like only half of the delay is from runhaskell (or runghc? not sure about how they differ), but both halves (say 500ms) are pretty slow (for a script).

https://pastebin.com/JjDBg2et

https://pastebin.com/tXmMv0rr

btw, any other alternatives besides tuning and caching? maybe a daemon, but that adds further complexity.

@sboosali
Copy link

sboosali commented May 1, 2017

anyways, i might also try nix for reproducible (haskell) scripts, but idk what it (nix-shell?) caches or its load speed.

@crocket
Copy link

crocket commented Aug 10, 2017

-- stack --resolver lts-9.0 script --package turtle --optimize

stack script --optimize seems to add 400ms delay to compiled executables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants