4 ms·
The main problem is laziness and to some extent purity. And partly GHC is a bit of a pig. Pretty much everything starts out life as a thunk, and the compiler d
by chrisdone 13y ago
The main problem is laziness and to some extent purity. And partly GHC is a bit of a pig.
Pretty much everything starts out life as a thunk, and the compiler does its best to translate these into strict values which affects when things are evaluated and therefore the performance, whether things can be stored on the stack fully evaluated, as registers or boxed things on the heap that need forcing, etc.
The good news is that purity makes it significantly easier to do inlining and reducings/transformations bordering on whole-program compilation. Code that looks like a load of function calls usually gets compiled down (if you use the -fext-core flag) to a couple case expressions.
You have to do this to get reasonable, baseline, performance in Haskell. In OCaml—or ML, or any strict, mutating language—you get that for free, as a starting point. They mirror the architecture underneath so they're already fast from the get-go.
It's not to do with type checking or anything like that. Type checking with GHC is very fast. If you use -fno-code, it can compile, e.g. a 9Kloc codebase of 65 modules in 1.361s on my machine. However, if you enable code generation, it takes 9.532s and that's with -O0: with -O2 it takes 32.910s. Fay, my unoptimized, non-optimizing Haskell→JS compiler will codegen that codebase in 1.940s. It would do it in less if I optimized the compiler's parser and codegen. But it's fast because I don't do anything. I don't do any flow analysis or strictness analysis or simplification or generation of core or compiling to C--. Fay's generated JS is not fast as a result. Compare that to if I were writing a Lisp compiler to JS, which would be easy because the source and the target are basically the same execution model.
- papsosouid 13y agoI understand optimizing haskell is a harder problem, but I don't mean producing optimized binaries, I only need to do that once in a while. I have to compile it thousands of times while developing, and turn off optimizations there since it is just a waste of time. But non-optimizing ghc is still incredibly slow. Wasn't yhc much faster than ghc back when it existed? Surely it must be possible to make ghc fast enough that development isn't seriously hindered?
- chrisdone 13y agoI dunno about Yhc. Jhc, at least, is slower than GHC. It depends on the settings and the project in my experience. If you're on a project like a Yesod app, then you have to conservatively rebuild a lot more than you would normally because TH doesn't lend itself well to incremental compilation and the compiling itself is slower too. Depending on the size of the project you're facing 5-15-30 seconds of waiting. That is actually monstrous and I hate it as much as you do, I cannot stand waiting for my app to rebuild, I turn to reddit, or YouTube. The more time waiting for feedback the less interactive and less enjoyable a programming experience is for me. So I think we're on the same page on that. On the other hand, if you're just using normal Haskell code with reasonably modularized file structure, GHC will be able to rebuild incrementally and link just that module quickly. E.g. if it's a web app, you can have MyProject.View.Person and just change something in a blaze-html template and hit F12 in your editor (as I do) and it'll rebuild and restart in a second. I mean, to rebuild hpaste entirely from scratch takes 5 seconds: $ time cabal build --ghc-options='-fforce-recomp -O0' Compiling [snip] Linking dist/build/hpaste/hpaste ... real 0m5.116s user 0m3.936s sys 0m0.680s Which is great. hpaste is only 4kloc, but your average codebase is between 3 and 15. In a normal development cycle you're changing just one or two modules at once, so with incremental compilation the refresh cycle is very fast. If you just want to type-check there is the -fno-code flag which brings it down to 1.2s to typecheck the whole project. Another approach I've taken with an IRC server (hulk) is running it inside GHCi. That works surprisingly well. Especially if you have your run function return the state as a mutable reference, then you can take a look at it while it's running and update it. Here's hpaste running in GHCi: λ> :set args "hpaste.conf" λ> tid <- forkIO main Listening on http://0.0.0.0:1234/ PASS ****** USER hpaste * * * NICK hpaste λ> :t tid tid :: ThreadId λ> killThread tid Shutting down... λ> tid <- forkIO main Listening on http://0.0.0.0:1234/ PASS ****** USER hpaste * * * NICK hpaste It's currently running on http://chrisdone.com:1234/ http://chrisdone.com:1234/ (I'll take it down later). Doesn't seem slow! Updating the code takes some milliseconds with :r and restarting is a case of killThread tid and fork again, some other milliseconds. So yeah, I feel you on dev time. I'm more inclined to the approaches that lead to more immediate development cycles, can't stand waiting. I'm Emacser/ex Common Lisper. GHC could be faster, but there are definitely circumstances which exacerbate its performance, I reckon, and ways to combat it (as above). I know that's not a very good answer, more of a workaround than a reason. I don't know why GHC is particularly slow if it's a lot slower than Yhc was. Other than all its separate build steps, I get the impression from what people say that it's just mounted up and become quite hairy. And memory usage has never been much of a concern for the compiler. That's a pity and I'm all for work being done on improving its performance.