Hacker News new | ask | show | jobs
by Rapzid 4154 days ago
I would LOVE for C# to get language support for go style channels complete with select and friends.

C# already has the fantastic Task stuff, and while they aren't as cheap as go's go-routines they work very well. Channels would just ice the cake so well. :D~~~

And while I'm at it. I did some testing recently and realized that manually currying in a for loop is faster than using function composition with delegates by about 40% :| It feels like in theory it should be compiled down to code with the same efficiency? This is probably more of a CLR/JIT issue though?

5 comments

Look into Microsoft's Task Parallel Library, it's fantastic! It gives you something similar to channels.

I wish more people knew about it. As someone else mentioned, Reactive Extensions are also great - I believe they will be getting backpressure support which will allow similar semantics to channels (but with first-class error handling)

If you mean what I think you mean by "manually currying", it's likely because behind the scenes, there is a proxy class created for most (all?) lambdas that contains everything it needs to execute, including any scope variables maintained. So there's some extra overhead there, though not as much as you'd expect since everything, even value types, are pulled in by reference in the context of lambdas.
I did not know that! That explains a lot of the memory performance problems I was having (structs being moved to the heap...icky); lambdas are sneaky little turds.

I solved the problem by avoiding lambdas altogether in code where performance is a concern, using delegates instead and ensuring all arguments are passed in rather than closed on (so structs would remain structs, GC pressure is avoided).

Imagine if the CLR allowed optional borrowing annotations ala Rust. Then safe non leaking functions would no longer need their callers to use the heap. Even APIs like String.Split could be actually efficient. In fact you might even get away with zero alloc for it, for some small cases.
Lambdas kind of mess that up, since they are indirect and dynamic by their nature and basically require GC to work. Heck, GC was originally invented to support Lisp.

There was, maybe is, some work being done on systems C#, not sure about the status though.

I don't see how that is any different, and in fact, lambdas would be one of the great things to benefit from stack allocation.

You just have to annotate or analyze the functions that don't takeover/leak a reference to something.

For something like List.ConvertAll (aka map), ConvertAll does not leak the lambda, it's pure in both input and output. So a stack allocated lambda works just fine. So the type signature for ConvertAll would indicate that it's Pure for parameters passed in. With that info, the compiler is free to pass lambdas on the stack to it.

So whenever the lambda is going to be created, the C# compiler just needs to look at the usage of it - is it passed only to Pure functions? If so, then no need to allocate a whole new object on the heap.

They could make it even cooler by doing Haskell's "fusion" which also contributes to deforesting. We just need the CLR (or even the C# and F# compilers) to provide real inlining. Then when you pass a lambda to an inlined function, you can avoid creating the closure in the first place! For common patterns like "xs.Select(...).Where(...).Aggregate(...)", if each function was inlined and pure, the resulting code could literally be equivalent to a big loop, as if you had hand-written the thing. No allocation all over the place, and much better resulting JIT'd code. I've wanted this in F# for a while (and F# does have inlining so you do get better code than C#), but Rust actually implements it, so lambdas are cost-free in many cases. That is progress.

> I did some testing recently and realized that manually currying in a for loop is faster than using function composition with delegates by about 40%.

Interesting. Do you have this benchmarking code posted somewhere? What do you mean by "manual currying"?

You can already get a long way towards channels by using reactive extensions (E.G. http://pastebin.com/h1xteunX)

Some syntactic sugar a la await could be nice though.

Not sure if you're aware, but your Rx example has a few problems:

1.) In the StartSenders method, you're violating the Rx grammar by calling channel.OnNext concurrently. Wrap it on a lock statement.

2) In StartReceiver you're missing out messages the way you handle the channel. Replace the loop with a call to channel.Subscribe

I did not know either of those things! Thanks!
40% is nothing, the last time I tested jquery.each vs a for loop, the for loop was at least 50x faster.