3 ms·
> That is not always true. There’s some overhead to introducing parallelization so it’s not always worth it unless you have a big enough chunk of work. This is
by LightMachine 5y ago
> That is not always true. There’s some overhead to introducing parallelization so it’s not always worth it unless you have a big enough chunk of work.
This isn't fine-grained parallelism though! As I said, HVM spawns threads on to-be-evaluated redexes closes to the root of the program's normal form. So, either there is a significant speedup, or the program is sequential (or too small), and there is no speedup, but the overhead is minuscule, since a bounded, small amount of spawn() occurs. To be clear: there is no situation where HVM's parallelism will make the program significantly slower, which *does* happen if you use too many sparks on Haskell. That will never happen on HVM.
> But the other reason fine grained parallelism isn’t often worth it even if it can provide a speedup for a single computation:
Yep that's definitely a good reason. Thanks for sharing. (You can just run HVM in a single thread, though.)
> It’s very cool in a “wow that’s neat” kind of way, but, well we already have machine ints that are even faster
I couldn't disagree more. Just because I used integers as an example, which happens to be optimized, it means an entire optimization technique isn't interesting? This applies to every data structure that can be defined algebraically.
> My other question - it seems like this approach requires giving up on separate compilation?
Compiling a function in isolation is fine, why wouldn't it be? Not sure I get it.
- pchiusano 5y ago> As I said, HVM spawns threads on to-be-evaluated redexes closes to the root of the program's normal form. So, either there is a significant speedup, or the program is sequential (or too small), and there is no speedup, but the overhead is minuscule, since a bounded, small amount of spawn() occurs. To me that sounds like overhead. Rather than just computing 1 + 1 as a single assembly language instruction you’re sending the 1 + 1 expression tree to a thread’s work queue or something? If I’m misunderstanding can you clarify? > Compiling a function in isolation is fine, why wouldn't it be? Not sure I get it. It seems like optimal evaluation is a whole program optimization - you need the whole program available in order to do it, you can’t just compile one function to assembly language in isolation, and then link that against other precompiled functions. Do I have the wrong idea here? > Just because I used integers as an example, which happens to be optimized, it means an entire optimization technique isn't interesting? This applies to every data structure that can be defined algebraically. I do think it sounds cool as I said, but I want to see it on an example I care about, not Peano arithmetic. Especially since deforestation is already an optimization that is used by languages that don’t do optimal evaluation. So I want to see how HVM does it better, if that’s the case. And not just you telling me how it’s theoretically better. :) Lastly, a while ago I remember you mentioning that you couldn’t compile arbitrary lambda calculus terms. Is that still the case and if so can you better describe what can’t be compiled?
- amelius 5y ago> You can just run HVM in a single thread, though. How easy would it be to share data structures between different threads? Say I have a large binary tree in one thread, can I send it to another thread without much cost? How about lambdas?
- LightMachine 5y agoThat isn't how it works. HVM threads work transparently, you don't need to think about them. The same data structure *can* be visible to multiple threads under some conditions, through cloning; for example, consider the following program: (Sum Nil ) = 0 (Sum (Cons x xs)) = (+ x (Sum xs)) (Head (Cons x xs)) = x (Main x) = let xs = (Cons 1 (Cons 2 (Cons 3 Nil))) (Pair (Sum xs) (Head xs)) The result of this program is `(Pair 6 1)`. Since `Pair` is the topmost (root) node of the normal form, each of its elements will be computed in a separate thread. Because of that, the same `[1,2,3]` list will be accessed by each thread. But that's not actually a shared reference, because values only exist in one place; instead, the lazy cloner makes copies of the list, layer-by-layer, and sends these copies to the thread that will handle them. Note that if the same expression was written in a deeper position of the normal form, it would be evaluated sequentially. This is what avoids the fine-grained parallelism issue. It also means that in many cases HVM will not be as parallel as it could, though! But when the parallelism kicks in, it is always productive.
- amelius 5y agoOk. I'd be interested in the following benchmark: A program that reads in a large map of key-value pairs; it then reads in a list of keys, and produces a list of values (order of the output is not important). The map can be modeled by e.g. a red/black tree. I'd be interested in the performance in Haskell, if it was implemented using a number of worker threads (each with their own reference to the map), versus the best implementation you can think of in HVM.
- LightMachine 5y agoLooks good. I'll keep that in mind and let you know if we write such a benchmark. Opening an issue would be helpful to remind us!
- strangemonad 5y agoI think I very much agree with your points and approach to parallelism. > This applies to every data structure that can be defined algebraically. Is this a mutually true? I’d expect this to hold for inductive algebraic data types but not co-inductive types?