Hacker News new | past | comments | ask | show | jobs | submit login

The two things aren't especially commensurate. A lot of the fun of Haskell is type-level stuff, which is resolved by the compiler at compile-time. There are no direct run-time artifacts. Moreover, several of the things Ed talks about have to do with concurrency, which isn't even something you can do with Ruby. Speculative execution and STM both enable real multicore concurrent processing. Even if it did perform as badly as Ruby, without the GIL and with real concurrency it's in a better position. Adding complexity in the type level does have one major performance effect though, which is on compile time. Does it count as fast and scalable if the compile time reaches into the minutes? In terms of compilation time, Ruby will always beat Haskell, because it doesn't have any. This is a meaningful difference when you're trying to iterate rapidly on your webapp, but most of the web frameworks for Haskell separate out templating to help with this problem.

Most people eventually complain that high-performance Haskell doesn't resemble the Haskell we're taught to program. This is less and less true as things like iteratee-based I/O and really advanced libraries like bytestring and text displace older facilities, but it is still something to think about. Haskell tends to perform well by sacrificing lots of memory. This is why you see discussion about "space leaks" rather than "memory leaks" and whatnot. There are a lot of ways to address the problems that come up, but you need to have a strong handle on the way Haskell works before you can accurately diagnose and treat the problem. This is a significant barrier to using Haskell in production. It doesn't take long to acquire the expertise to throw Ruby into production. The road with Haskell is a bit longer and a bit more taxing, because it's so very foreign.




Haskell tends to perform well by sacrificing lots of memory. This is why you see discussion about "space leaks" rather than "memory leaks" and whatnot.

I agree with your comment, but these sentences are a bit weird. Usually space leaks occur where you should have evaluated things strictly rather than lazily or the other way around. It's a bug (with the amount of memory that we have now).

But I don't agree that Haskell tends to perform well by sacrificing lots of memory. For example, you mention the iteratee proposals/libraries. Iteratees are well-known to be fast and can be used to do constant-space I/O. Bytestrings are much more performant than their precursor (lists of characters, aka String), but are also not demanding in terms of memory use.


Thanks for clarifying that. What I am trying to say with those two sentences is pretty unclear as worded. I'm trying to convey that the kind of Haskell you write as a beginner tends to perform well despite being wasteful. As you get better, you worry more about the waste and use more sophisticated stuff that's less wasteful. It obviously performs better, which isn't really what comes across. But it is usually a little more complex. The penalty is really slight for things like strict folds, strictness annotations and using better libraries like ByteString, but I'm sure you'll agree that it has a much more substantial effect on the look and feel of the code when you get to things like unboxing, using "raw" iteratees, and so forth.


can't the same be said for pretty much any language? If you did C++, and found performance problems, you'd drop down to assembly, which imho, is even more unwieldy than using more advanced haskell libraries.


> Haskell tends to perform well by sacrificing lots of memory

Not more so than any other garbage-collected language


Well, laziness has especially nasty pitfalls sometimes.


Indeed, but it's not "perform well by sacrificing memory" rather, "sometimes use a lot of memory unnecessarily".


> several of the things Ed talks about have to do with concurrency, which isn't even something you can do with Ruby.

Concurrency has always been possible to do with Ruby just fine. There's just been a limit on system level threads until 1.9 (and there still is a limitation in terms of the interpreter lock). There's on the other hand nothing that has ever stopped us from multi-process concurrency, including using shared memory (though with the caveat that Ruby won't let us put Ruby objects there).


https://news.ycombinator.com/item?id=6198068

Haskell allows you to run "green" or userspace threads in the millions, similar to what erlang and scala (akka) provide (and F#), with dispatcher designed to run i nthose numbers. So it's something like Celluloid, but I don't know how many people have used celluloid in production, in haskell the runtime's been well documented (Simon Marlow's oreilly book) and tested in heavily loaded systems.


I just ran a small test that started and ran one million green threads on Ruby 1.8.7, and it worked just fine. I won't make any claims about the performance, but it certainly works. 1.9+ supports both green threads (fibers) and system/kernel threads, and would be much better to test.

MRI is slow regardless, so unless you're bound by extremely slow IO from other components, you're going to run into other limits long before you reach those kind of numbers, though.


Do note that the changes described in the paper to allow green threads in the millions won't hit GHC until version 7.8.1 is released.


You can use millions of threads in versions <7.8.1 as well, they'll just run a lot slower if they're all actively doing IO.


There's a wide gulf between Ruby concurrency and Haskell concurrency.


There's a rather wide gulf between a moderate claim that there's a wide gulf between Ruby and Haskell concurrency, and the claim I responded to that it "isn't even something you can do with Ruby".

I am curious, though, what Haskell provides that can't be done with any of the current Ruby implementations? (not least because I'm back to working on my ahead of time/static Ruby compiler)


Generally parallelism is really much easier to express in a pure language and thus "sparked" parallelism is almost trivial to use in Haskell. I'm not sure that MRI even allows for parallel processes in its green threads due to GIL? Other Ruby implementations might have different restrictions there.

Also, as Edward mentions in his post on reddit, speculative parallelism is a Haskell one-liner.

    spec guess proc actual = 
      let speculation = proc guess 
      in s `par` if guess == actual 
                 then speculation 
                 else proc actual
(written in 5 lines for extra clarity on the syntax)


> I'm not sure that MRI even allows for parallel processes in its green threads due to GIL?

The GIL has no relevance when talking about processes. The GIL affects simultaneous execution of threads on multiple processor cores. It also does not prevent concurrency for threads or fibers. What it does prevent is for two system/kernel threads to be executing the Ruby interpreter code itself on different processors or cores at the same time.

This is generally not all that big of an issue, as e.g. any C extensions that are thread safe can tell the VM to yield from the thread that calls it, and any Ruby thread that does IO etc. will also be put back in the waiting queue and so won't hold the GIL.

> Other Ruby implementations might have different restrictions there.

jRuby, Rubinius and MacRuby are all GIL-less as far as I remember.

> Also, as Edward mentions in his post on reddit, speculative parallelism is a Haskell one-liner.

It's a little bit wordier in Ruby, but not much. There may better ways of doing it, but this spawns the "guess" thread (g), then spawns an "actual" thread (a), that if it finishes first will kill the guess thread (otherwise the guess thread will already have terminated, and the "kill" will do nothing. The main thread then waits for the g thread to terminate, either with a result or because it was killed. We then just try to kill "a" because we know either g terminated first, or a terminated it after it had finished - either way a is not needed any more. We then return whichever thread local variable has content.

    def spec guess, actual
      g = Thread.new { Thread.current[:ret] = guess.call }
      a = Thread.new { Thread.current[:ret] = actual.call; g.kill }
      g.join; a.kill
      g[:ret] || a[:ret]
   end
This requires "guess" and "actual" to be any object with a call method, which includes Proc/blocks/"lambda" statements (or "->" in Ruby 1.9). E.g:

   puts spec(-> { sleep(3); return "GUESS" }, -> { sleep(1); return "ACTUAL" })
In MRI 1.8 this will only execute on a single core. In 1.9+ it can use multiple cores, but will be subject to the GIL. On Rubinius and jRuby etc. it won't have to deal with a GIL. A multi process version would be immune to both. There are additional caveats to think about, like signals.

Of course this requires "guess" and "actual" to be thread safe.


Yeah, you're getting there at the end when you have no GIL and multiple cores being utilized. Without that, your speculative parallelism isn't faster, it's just as long unless it just happens to be threadsafe C extension IO.

Which is exactly the point. Haskell performs better with fewer caveats all the time.


No, it is substantially faster with the GIL too, for almost all "real" Ruby code. There are exceptions: code that spends most of their time in the interpreter. That's pretty much no Ruby code. If you're doing numeric computation etc. with present Ruby implementations, that would be impacted by it, you're doing something wrong - the method call overhead in all current Ruby implementations is too high for that to be a good idea.

And as I pointed out, the GIL is an issue in only one of at least 4 available Ruby implementations as of last count. So you're discounting the implementation based on the one Ruby implementation that is potentially scaling worse. Great. Let's find an old slow Haskell implementation to compare to too.

> Without that, your speculative parallelism isn't faster, it's just as long unless it just happens to be threadsafe C extension IO

Performance wasn't what was being discussed. The claim I responded to originally was that concurrency isn't "even something you can do with Ruby". I've demonstrated why that claim is flat out wrong, for every version of Ruby.

You then claimed "Generally parallelism is really much easier to express in a pure language and thus "sparked" parallelism is almost trivial to use in Haskell. I'm not sure that MRI even allows for parallel processes in its green threads due to GIL? Other Ruby implementations might have different restrictions there."

And I demonstrated how easy implementing speculative execution is in Ruby too, and pointed out that Ruby allows parallelism just fine even in old MRI version with green threads, new MRI versions with system threads, processes, or any of the number of non-MRI Ruby implementations that don't use a GIL, but with different tradeoffs.

You can continue to shift the goalposts if you like, but I've addressed the original claims sufficiently. And bringing up performance is irrelevant - I've pointed out the performance limitations myself repeatedly.

(and to whomever downvoted my reply above: that's the kind of childish reaction I'd have expected at Reddit, not here)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: