
Measuring GC latencies in Haskell, OCaml, Racket - edwintorok
http://prl.ccs.neu.edu/blog/2016/05/24/measuring-gc-latencies-in-haskell-ocaml-racket
======
ronjouch
Well, Today I Learned that Racket is GCed; I assumed the contrary due to John
Carmack being enthusiast about it in the context of video games.

I understand the fact that not everything in a game belongs to the critical
16ms budget (and know about e.g. Unity, which lets you talk C# to it), but I'm
still puzzled; can someone expand on:

\- Which are these non critical parts? Rendering / physics / animation are
certainly in the critical path. That leaves only simple "client" game logic
out of it, right? What else?

\- For these non critical parts, what are the typical acceptable time
boundaries? For example, the article mentions _" maximal latencies of around
22ms"_ for Racket. How is it acceptable for non-critical-path parts? How much
would still be acceptable? 50ms? 100ms? _" That depends"_?

\- And regarding Haskell and OCaml, which I only vaguely know about, I had
never asked myself how they handle memory. Which makes me ask: are there non-
GCed functional programming languages? Does it make sense? (Especially given
the kinds of deep complex data structures produced by the immutability that
often comes with these languages.)

~~~
agentgt
> _Well, Today I Learned that Racket is GCed_

I'm curious how did you expect Racket to work without a GC or any Lisp like
language for that matter or did you just assume because of video game usage?

> _\- And regarding Haskell and OCaml, which I only vaguely know about, I had
> never asked myself how they handle memory. Which makes me ask: are there
> non-GCed functional programming languages? Does it make sense? (Especially
> given the kinds of deep complex data structures produced by immutability.)_

Rust and C++ depending on your definition of FP. I believe there are some old
ML languages as well (and even some _old_ lisps).

~~~
ronjouch
> _" I'm curious how did you expect Racket to work without a GC or any Lisp
> like language for that matter or did you just assume because of video game
> usage?"_

Just because of video game usage and my perception of Carmack as a performance
freak who wouldn't compromise on performance. (Sloppy reasoning, I know! Thus,
my questions on where GC makes sense in games)

~~~
agentgt
I actually was curious and not trying to be snide. For example I wasn't sure
if Carmack or whoever might be using Racket for code generation (ie multi-
stage programming) which in theory would avoid GC. I wasn't sure how he was
using Racket.

~~~
ronjouch
No problem, your comment was clear and I didn't perceive it as snide, at all.
Thanks for your carefulness in the discussion.

------
OskarS
I have a really dumb question about the Haskell GC, which I've never been
brave enough to ask, but here goes:

I thought everything in Haskell was immutable? If so, wouldn't that imply that
it's impossible to create cyclic references (i.e. A and B can't both reference
each other, because one of them has to be created first, and once created,
it's immutable)?

Couldn't you then just use reference counting to decide when to recycle
objects instead of having full-blown GC pauses? Or is it too expensive to keep
a reference count on all those objects and keep it updated? Whatever cost it
has, it seems much more preferable to me compared to 50 ms GC pauses for
relatively simple code.

~~~
openasocket
Not a dumb question at all! You can create cycles in Haskell thanks to lazy
evaluation. I can make a circular list in Haskell with the following:

x :: [Int]

x = 0 : y

y :: [Int]

y = 1 : x

And thus we have a circular list with the elements 0 and 1. It's technically
an infinite-length list with 0 and 1 repeating forever, but Haskell will
represent this in memory as a circular list.

~~~
OskarS
Ah, I see, I figured that there might be some case like that. I would have
expected that to be a compile-time error because `y` is an unrecognized
identifier by the time `x` is defined, but I guess it makes sense with lazy
evaluation to not make that kind of ordering matter.

~~~
tener
Lazy evaluation is not important item here. It is merely the semantics is
defined in a way that the order of declarations does not matter.

Other languages have this property too - say, C# (not entirely true, but
pretty close - at least if you think about top level methods etc.).

Also an example where the order of declarations does not matter at all,
because there is just one declaration:

    
    
       x :: [Integer]
       x = 0 : 1 : x

------
saosebastiao
Why is it that GC technology is so hard to transfer from one language to
another? I'd kill for a FLOSS implementation of Azul's GC, or a good small-
heap-specialized GC for .NET.

It seems far too often we run into popular languages whose growth has stalled
due to their GC, or features that have been held back by GC, or whatever. And
then other languages have several different GCs with different characteristics
that can be swapped out with a simple CLI flag.

Is it something that is deeply and strongly coupled to the language structure?
Is the parameter space for tuning just too large to practically swap one GC
for another? Incompatible algorithms? Maybe one of those situations where you
get 80% of the way with an ounce of effort, but to get to 81% takes years?

~~~
waterhouse
Some answers that come to mind:

    
    
      - Azul's GC requires cooperation from the kernel; I believe it
        specifically uses the virtual memory mechanism to implement a read
        barrier.  Basically you need a patched Linux kernel.
      - Real-time GC will assuredly lower overall throughput, and increase
        complexity.  At the very least you need either a read barrier or a
        write barrier.  I don't think anyone uses read barriers normally;
        write barriers are often used for generational GC, but I *think*
        the write barriers used for real-time GC involve more work than
        those for generational GC.
      - Adding a read or write barrier to a language implementation that
        doesn't already have them means you need to guard every single
        place where someone reads or writes what might be a pointer.  This
        is easy to screw up.
      - Several real-time GC algorithms punt on moving objects, and are
        thus vulnerable to memory fragmentation.  This obviously reduces
        the robustness of the runtime system.  (Others use stop-the-world
        to do compaction, and thus are not real-time.)

------
nvartolomei
Follow up: [https://blog.pusher.com/golangs-real-time-gc-in-theory-
and-p...](https://blog.pusher.com/golangs-real-time-gc-in-theory-and-
practice/) and HN:
[https://news.ycombinator.com/item?id=13086059](https://news.ycombinator.com/item?id=13086059)

------
coldcode
I don't know Haskell but it sure has some odd syntax:

    
    
        type Chan = Map.Map Int ByteString.ByteString
    

is odd looking. Why the duplication?

~~~
marcosdumay
That's what you get by importing qualified and being too verbose.

This same can be written as:

    
    
      type Chan = Map Int ByteString
    

People do prefer importing Map and ByteString qualified, so their functions
will have a prefix of their choice. But very few people import ByteString as
"ByteString", most use something shorter.

~~~
moomin
Gotta say, I'm Team Qualified Import. Use it all the time in my Clojure code.
I want to know where things come from.

~~~
marcosdumay
On Haskell you don't even get much choice. The community is so biased towards
qualified importing that most modules clash, and you'd need some incredible
stretching to use them unqualified.

As most things in Haskell, it tends to work greatly. The usual problems go
away due to some combination of Haskell's features and the main problems are
things you wouldn't think about.

------
vegabook
so can we reasonably surmise that Ocaml outperforms Haskell massively on GC
pauses? What's the catch, if any?

~~~
moomin
Catches with OCaml? Mutable strings, unchecked integer arithmetic. It's got
its fair share of ugly corners.

~~~
vegabook
it seems to me that ocaml/(ocaml zealotry) > haskell/(haskell zealotry). This
of course does not suggest that Ocaml>Haskell, only that one must probably
apply some coefficients in favour of Ocaml given that it is extra-anglo-saxon
domain and therefore probably suffers some marketing disadvantage.

Just a casual observation of the commento-sphere after a lay imperative
programmer's cursory research of functional programming. This post, sample-of-
one hugely notwithstanding, seems in the absence of counterpoints to suggest
that, as potentially narrow as the benchmark may be, Ocaml has some very
strong arguments against Haskell on the compiler implemenation front.

I can't help but wonder if this GC outperformance is the flipside of the coin
of Ocaml's much maligned lack of multi-core capability.

~~~
emmelaich
Well said, and I feel exactly the same way (as an interested observer)

------
amelius
This reminds me of the Great Programming Language Shootout.

Anyone here who knows what happened to that great site?

~~~
lionsdan
It evolved into The Computer Language Benchmarks Game.

[https://benchmarksgame.alioth.debian.org](https://benchmarksgame.alioth.debian.org)

~~~
igouy
Roll the credits :-)

[http://benchmarksgame.alioth.debian.org/sometimes-people-
jus...](http://benchmarksgame.alioth.debian.org/sometimes-people-just-make-up-
stuff.html#history)

