5 ms·
Crystal seems like a great high-level language and alternative to Ruby, but the biggest blocker for me using it more generally is the lack of multi-threading su
by eatonphil 9y ago
Crystal seems like a great high-level language and alternative to Ruby, but the biggest blocker for me using it more generally is the lack of multi-threading support. There is WIP support for threading but it has not been committed. Moreover the Crystal developers either don't do a great job of updating their multi-threading roadmap [0] or work on multi-threading has died off. (I assume it is the latter because the thread-support branch has not gotten any commits in 10 months [1].) Ultimately if I wanted the high-level aspects of Ruby or Python and wanted more performance and great thread support I'd probably go with Elixir (even taking into consideration BEAM weirdness).
[0] https://github.com/crystal-lang/crystal/wiki/Threads-support https://github.com/crystal-lang/crystal/wiki/Threads-support
[1] https://github.com/crystal-lang/crystal/tree/thread-support https://github.com/crystal-lang/crystal/tree/thread-support
- k__ 9y agoWhat is so good about multi-threading?
- Doctor_Fegg 9y agoIf you're considering Crystal over Ruby, chances are that speed is part of your reasoning. And if speed is important to you, then you want to use all 4/8/16 cores of your machine, not just one.
- smitherfield 9y agoAny web application is going to be almost entirely network and database-bound, not CPU-bound. CPU-bound tasks are stuff like scientific computing, video encoding, the Ruby interpreter and Crystal compiler themselves, etc. The advantage of multithreading in a web application is not "using all cores" but rather that the application can spawn a thread to "asynchronously" wait for a response from the network or database while it continues doing other work in the main execution thread, instead of "synchronously" blocking all work while it waits. In other words, web applications leverage concurrency with their use of multithreading, not parallelism. I will caveat that having more cores can give concurrent applications a small performance improvement, because even though each thread is only using a CPU core at around 0 to 15% (in short bursts) of capacity, switching between two threads on the same core is a context switch, a fairly expensive operation. More cores means fewer context switches. But, unlike with parallelism, making use of more cores is not the reason concurrency offers performance benefits. ~99% of the performance benefit of concurrency is realized on even a single-core machine, whereas parallelism by definition requires multiple cores to offer a speedup. Concurrent applications such as web servers (e.g. Apache, Nginx) typically spawn hundreds or even thousands of threads per CPU core.
- cookiecaper 9y agoWhile this argument is valid and fine, I think it has mostly played out. Previous language design choices that consciously ignored multithreading were made in the mid-late 90s when the future of consumer SMP was unclear. Now it is clear that multi-core is and will be an important component in everything -- we have quad-core phones, dual-core cameras, etc. While an application may be fine without good/native support for parallelism in most cases, it's really nice to be using a language that allows you to bolt it on easily when it turns out that you _do_ need it. At this point, it's a language feature that's not worth leaving on the table without compelling rationale. Arguably, the class of high-level dynamic languages that were on the rise in the mid-00s (Python, Ruby) lost a lot of steam to newcomers like Swift and Go due to the language designers' hesitation around embracing parallelism. Crystal should not repeat that mistake.
- eatonphil 9y agoThis is exactly the reason I'm concerned about investing in Crystal (and OCaml, where multicore is also in an awkward state). I need to be confident that when I need parallelism, the language supports it (good threading primitives are imperative too, so not just pthreads).
- RX14 9y agoWe really really want to support parallelism and we've had some basic successes so far. We're very confident we want this, and we're confident we can come up with a performant solution: despite being a different language our parallelism solution will likely end up close to Go's which has largely solved this already. We're not there yet but I'm confident the main barrier is time and manpower to implement it, not willpower or technical reasons.
- smitherfield 9y agoI'm not saying that Crystal or any language shouldn't support multithreading or parallelism, but that web applications (that is, almost all existing commercial Ruby applications) don't employ and wouldn't be able to benefit from parallelism. This is due to the laws of physics: two CPU cores connected over a network cannot communicate faster than two cores on the same silicon die, so network communication is impossible to meaningfully parallelize at the CPU level. (But, those same characteristics make concurrency a massive performance win). >the class of high-level dynamic languages that were on the rise in the mid-00s (Python, Ruby) lost a lot of steam to newcomers like Swift and Go due to the language designers' hesitation around embracing parallelism. Quite the opposite, Python has a dominant and rapidly-growing market share (as the developer-facing API wrapping Fortran/C/C++ libraries) in CPU-bound, massively-parallel applications (NumPy, Tensorflow, etc). Swift and Go are odd examples to make your point: Swift's support even for concurrency[1] is a low-level and unergonomic Objective-C library, and for parallelism (or implementing other concurrency primitives) an even lower-level and less ergonomic paper-thin Objective-C wrapper around pthreads.[2] That's still better than Swift on Linux, where the only option for either is plain pthreads. Go has excellent concurrency support (with goroutines, aka fibers, aka green threads, which execute one-at-a-time on a single CPU core and from within a single actual OS thread), but for multi-core parallelism its standard library also offers nothing more than a paper-thin wrapper around pthreads.[3] Even C++11 offers a substantially higher-level and more ergonomic parallel-threading API than either Swift or Go.[4] Among recent, relatively mainstream, general-purpose languages, the examples that come to my mind as exemplifying strong parallelism support are Rust, Julia and recent versions of Java (Streams) and C# (TPL, PLINQ). [1] https://developer.apple.com/documentation/dispatch https://developer.apple.com/documentation/dispatch [2] https://developer.apple.com/documentation/foundation/thread https://developer.apple.com/documentation/foundation/thread [3] https://golang.org/pkg/sync https://golang.org/pkg/sync [4] http://en.cppreference.com/w/cpp/thread http://en.cppreference.com/w/cpp/thread
- chrisseaton 9y agoFor the kind of applications I work on (compilers and interpreters) we have large, mutable data structures. The best way we know as an industry to parallelise operations on these data structures is via shared memory, which either means threads, or processes with shared memory mapped into them, which is effectively the same thing (you need the same synchronisation). For me personally, that's what's so good about multi-threading. It's the best tool for my job.
- fridgamarator 9y agoFWIW: Crystal `HTTP::Server` can already use `SO_REUSEPORT`, allowing multiple web servers to run on a multi core machine. This is obviously only useful for web servers and is different from parallel processing.
- deedubaya 9y agoI have some Crystal running in production. It'll be going away -- rewritten in Rust. Crystal has been great, and I really like the way it started going, but... Development of the core language seems to have died off -- either through lack of updates and communication like you've mentioned or development. Regardless, the core team doesn't inspire me enough to take a chance on the language any more -- I've lost hope for a 1.0 release any time in the near future. I'll be killing off my BountySource[0] recurring donation as well. There was/is so much potential in Crystal -- it really is great (joy to write, performant) -- but without real sponsorship/leadership, it just seems to be floundering. I hope I'm wrong and these are just growing pains! [0] https://salt.bountysource.com/teams/crystal-lang https://salt.bountysource.com/teams/crystal-lang
- RX14 9y agoIt'd be great if you could stick around for just a few more months. As a core team member external to Manas, I know how frustrating developing Crystal has been recently but I'm hopeful it'll pick up again in the next few months. Check my top-level comment.
- deedubaya 9y agoYou're awesome. Thanks for the honest insight -- really, that's so much better than the general silence we've been seeing. A regular "State of Crystal-lang" blog post would be sufficient for me to hang on. Even if the posts might be "haven't done much. this is what we'd like to work on next, these are the roadblocks, etc. you can help by doing XYZ".
- RX14 9y agoNo, you're awesome. Looks like we can probably do at least one blog post a month, we'll discuss it internally.
- rbehrends 9y agoThis is a common complaint (not just for Crystal), but I would argue that most people do not actually need or want shared memory concurrency. They need either parallelism and asynchronicity, for which threads are just one possible implementation. I'm assuming here that this is not just about multiple threads of control (which can also be achieved through having multiple processes), but having threads interact through shared memory rather than through message passing. The problem is that with shared memory, there's a constant tension between performance and safety. Pretty much all safe approaches introduce either contention (through mutexes or other synchronization constructs) or considerable overhead (such as the O(log n) factor typical for purely functional data structures or creating duplicates to avoid contention). In order to write actual high performance code accessing shared memory, you will typically have to deal with inherently unsafe constructs. In most cases, it's far easier to optimize message passing, perhaps throwing in some specialized shared memory constructs (such as Mnesia for Erlang) in order to make some common use cases less cumbersome. Note that with current memory hierarchies, the overhead of message passing compared to shared memory may be less than you think; any data that's actually used by more than one core has to pass through the L3 cache at a minimum. I think that the reason why more people don't use message passing approaches is (1) that few languages actually support it well and (2) that message passing as a programming paradigm can be far more intimidating than shared memory, because causality is more difficult to reason about (though the ease of use of shared memory models can be deceptive and it's easy to run into difficult to diagnose correctness or performance problems). But technologically, message-passing is much easier to support. I've written highly parallel code in Python, Ruby, and OCaml, all of which have a GIL and cannot support shared memory parallelism. But they all have (near) universal serialization, so implementing basic message passing is trivial. As an example, one of the main attractions of Go is, I think, that it provides a very accessible programming model for message passing (building on top of CSP). And we have PGAS approaches, which essentially simulate shared memory on top of a distributed architecture. This is not to say that message passing is a panacea; there are plenty of use cases where it's a poor choice. For example, I'm currently working on a problem that is essentially about performing a reduction operation in parallel on multiple threads, where the operation is typically defined through a multi-GB data structure. Copying that data structure to every single thread is prohibitive, and keeping it in one thread would kill all the possible parallelism. But message passing is plenty good enough for 90% or so of all use cases. Generally, I would phrase it so that the problems that shared memory and message passing have are duals of each other: shared memory is concerned with contention as a source of cost and complexity, message passing is concerned with communication as a source of cost and complexity. For the majority of problems, you can engineer solutions that fit either model (though they may look substantively different), so not using shared memory is not going to be a problem for those. Asynchronicity is a different concern and does not actually require threads.