Fork me on GitHub
#core-async
<
2017-04-18
>
Yehonathan Sharvit14:04:38

In clojure (not clojurescript), what’s the big advantage of having a go block with <! instead of a thread block with <!!?

Yehonathan Sharvit14:04:34

In the docstring of go, it is written:

Additionally, any visible calls to <!, >! and alt!/alts!
channel operations within the body will block (if necessary) by
'parking' the calling thread rather than tying up an OS thread (or
the only JS thread when in ClojureScript)

Yehonathan Sharvit14:04:43

But it’s a bit cryptic to me

tbaldridge14:04:19

@viebel go is a re-writing macro. So it turns code like (go (println "got" (<! c))) into (take! c (fn [x] (println "got" x)))

tbaldridge14:04:19

So the cost of creating a go-block is the cost of allocating a few closures. The cost of creating a thread is the cost of creating a JVM thread (or about 2MB of data + some other bookkeeping).

Yehonathan Sharvit14:04:31

Thanks @tbaldridge. And from the running time perspective: should a go block runs faster than a thread block?

tbaldridge14:04:31

No, in fact a go will run roughly at about 50% of a thread. And that's why you should keep complex logic/loops in functions called by a go.

tbaldridge14:04:49

Go blocks are really all about reduction of memory usage and putting less pressure on the thread scheduler.

tbaldridge14:04:18

You can easily create hundreds of thousands of go blocks, JVM threads, not to so much.

Yehonathan Sharvit15:04:23

> And that’s why you should keep complex logic/loops in functions called by a go. The reason is because the code that is rewritten by the go macro runs more slowly? (And code inside the function called by the go block are not re-written)

tbaldridge15:04:26

Right, there isn't always a clean transform. So if you look at something like: `(go (doseq [s data] (>! c s)))`

tbaldridge15:04:24

What the go macro will do, is build a state machine out of that code. That state machine has overhead. So while the semantics match normal clojure code, you're left with code that runs slower.

tbaldridge15:04:20

This is the same in any language that performs this transform. C#, Python, and Cython are a few other examples.

dominicm15:04:58

@tbaldridge Hmm, so what is the use-case for a go block on the JVM? I assumed parallelism, but not I/O based, but instead CPU-bound parallelism.

tbaldridge15:04:07

That's correct, I'm saying that in this snippet (go (doseq [x s] (foo))), the code inside foo itself will run at 100% speed, but the doseq will run a bit slower.

dominicm15:04:56

@tbaldridge Does go have an explicit "when hitting doseq do X" piece of code?

tbaldridge15:04:40

no, although I'm not 100% sure I know what you're asking?

dominicm16:04:54

@tbaldridge Why does go maintain a state machine for doseq and not other things? I may be being overly-specific to your example.

tbaldridge16:04:08

Yeah, it is overly specific. The state machine always exists. The go macro translates the code inside the macro into a SSA based state machine. https://en.wikipedia.org/wiki/Static_single_assignment_form It then compiles that machine into Clojure code that is the output of the macro

dominicm16:04:05

I feel like there's a good gif where something goes over a persons head. 🙂. I'll have to dig in. @tbaldridge I'm guessing a little, but does the go macro only rewrite the top level forms? So they're slower? (I think I'm struggling to see why doseq is slower, but foo is just as fast)

tbaldridge16:04:44

So the go block is a macro, and macros only have access to the code within their bodies. We could have gone with a system that rewrites all code in the JVM to use these state machines (this is what pulsar and erjang do), but we considered that too intrusive. Better to only do local transformations.

tbaldridge16:04:21

So this is why you can't do code like this (go (mapv <! chans)), as the go won't dig into mapv and turn it into a state machine as well.

tbaldridge16:04:35

But backing up, the problem at hand is this: how do you "pause" the execution of a function on the JVM? In the case of (go (<! c) (>! c 4)) how do you create a thing that can be paused and turned into a callback that can be attached to c?

tbaldridge16:04:16

core.async, C#, Python, etc. all do this by translating the code into a state machine and then pausing the execution of the machine at certain points.

dominicm16:04:07

Got it. Makes sense.