48

When programming in Haskell (and especially when solving Project Euler problems, where suboptimal solutions tend to stress the CPU or memory needs) I'm often puzzled why the program behaves the way it is. I look at profiles, try to introduce some strictness, chose another data structure, ... but mostly it's groping in the dark, because I lack a good intuition.

Also, while I know how Lisp, Prolog and imperative languages are typically implemented, I have no idea about implementing a lazy language. I'm a bit curious too.

Hence I would like to know more about the whole chain from program source to execution model.

Things I wonder about:

  • what typical optimizations are applied?

  • what is the execution order when there are multiple candidates for evaluation (while I know it's driven from the needed outputs, there may still be big performance differences between first evaluating A and then B, or evaluating B first to detect that you don't need A at all)

  • how are thunks represented?

  • how are the stack and the heap used?

  • what is a CAF? (profiling indicates sometimes that the hotspot is there, but I have no clue)

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
sleepyMonad
  • 989
  • 8
  • 13
  • 3
    Not exactly what I'd call introductory, but [SPJ's book on the implementation of functional languages](http://research.microsoft.com/en-us/um/people/simonpj/papers/pj-lester-book/) is a good read. – hammar May 18 '11 at 17:00
  • 2
    Uuhh.. For me, it was a deep dig into the big pile of papers published by the researchers of GHC. If you are interested in data representation, this [series of blog posts](http://blog.ezyang.com/2011/04/the-haskell-heap/) by Edward Z. Yang could be interesting for you. – fuz May 18 '11 at 17:02
  • 3
    Related: [How does a Haskell compiler works?](http://stackoverflow.com/questions/4385160/how-does-a-haskell-compiler-works) – fuz May 18 '11 at 17:05
  • 1
    This is a question I always wanted to ask. Getting acquainted with the papers is a good start. They are readable. You may be interested on those about garbage collection, since they reveal most of the underlying structure (how closure blocks are implemented). For the different compiler passes, I have no idea. – Alexandre C. May 18 '11 at 17:46
  • 1
    Regarding the second item, wouldn't it be the case that if A is truly only used for some values of B, then A will either be evaluated after B or not evaluated at all due to never being forced? Unless something else is creating unnecessary strictness, in which case A is probably getting evaluated even when it's not needed anyway. – C. A. McCann May 18 '11 at 17:47
  • @Alexandre C: that's a good suggestion, as I am pretty familiar with GC algorithms. -- thanks – sleepyMonad May 18 '11 at 18:19

2 Answers2

57

The majority of the technical information about the architecture and approach of the GHC system is in their wiki. I'll link to the key pieces, and some related papers that people may not know about.

What typical optimizations are applied?

The key paper on this is: A transformation-based optimiser for Haskell, SL Peyton Jones and A Santos, 1998, which describes the model GHC uses of applying type-preserving transformations (refactorings) of a core Haskell-like language to improve time and memory use. This process is called "simplification".

Typical things that are done in a Haskell compiler include:

  • Inlining;
  • Beta reduction;
  • Dead code elimination;
  • Transformation of conditions: case-of-case, case elimiation.
  • Unboxing;
  • Constructed product return;
  • Full laziness transformation;
  • Specialization;
  • Eta expansion;
  • Lambda lifting;
  • Strictness analysis.

And sometimes:

  • The static argument transformation;
  • Build/foldr or stream fusion;
  • Common sub-expression elimination;
  • Constructor specialization.

The above-mentioned paper is the key place to start to understand most of these optimizations. Some of the simpler ones are given in the earlier book, Implementing Functional Languages, Simon Peyton Jones and David Lester.

What is the execution order when there are multiple candidates for evaluation

Assuming you're on a uni-processor, then the answer is "some order that the compiler picks statically based on heuristics, and the demand pattern of the program". If you're using speculative evaluation via sparks, then "some non-deterministic, out-of-order execution pattern".

In general, to see what the execution order is, look at the core, with, e.g. the ghc-core tool. An introduction to Core is in the RWH chapter on optimizations.

How are thunks represented?

Thunks are represented as heap-allocated data with a code pointer.

Heap object

See the layout of heap objects. Specifically, see how thunks are represented.

How are the stack and the heap used?

As determined by the design of the Spineless Tagless G-machine, specifically, with many modifications since that paper was released. Broadly, the execution model:

  • (boxed) objects are allocated on the global heap;
  • every thread object has a stack, consisting of frames with the same layout as heap objects;
  • when you make a function call, you push values onto the stack and jump to the function;
  • if the code needs to allocate e.g. a constructor, that data is placed on the heap.

To deeply understand the stack use model, see "Push/Enter versus Eval/Apply".

What is a CAF?

A "Constant Applicative Form". E.g. a top level constant in your program allocated for the lifetime of your program's execution. Since they're allocated statically, they have to be treated specially by the garbage collector.


References and further reading:

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
9

This is probably not what you had in mind in terms of an introductory text, but Edward Yang has an ongoing series of blog posts discussing the Haskell heap, how thunks are implemented, etc.

It's entertaining, both with the illustrations and also by virtue of explicating things without delving into too much detail for someone new to Haskell. The series covers many of your questions:

On a more technical level, there are a number of papers that cover (in concert with other things), parts of what you're wanting to know.:

  • A paper by SPJ, Simon Marlow et al on GC in Haskell - I haven't read it, but since GC often represents a good porton of the work Haskell does, it should give insight.
  • The Haskell 2010 report - I'm sure you'll have heard of this, but it's too good not to link to. Can make for dry reading in places, but one of the best ways to understand what makes Haskell the way it is, at least the portions I've read.
  • A history of Haskell - is more technical than the name would suggest, and offers some very interesting views into Haskell's design, and the decisions behind the design. You can't help but better understand Haskell's implementation after reading it.
lukerandall
  • 2,201
  • 17
  • 29