[HN Gopher] Ruby vs. Crystal Performance ___________________________________________________________________ Ruby vs. Crystal Performance Author : ptcodes Score : 118 points Date : 2020-06-05 18:22 UTC (4 hours ago) (HTM) web link (ptimofeev.com) (TXT) w3m dump (ptimofeev.com) | hosh wrote: | The thing I like about Crystal is that I can generate statically- | linked binaries and distribute them. I can quickly build | utilities for people to use and don't have to distribute the | runtime. The type system wasn't as bad as I thought (especially | with type inferencing). Having written Ruby professionally for 14 | years, Crystal was relatively easy to pick up. There are a many | libraries that hew closely to their Ruby counterparts. | | There are some amazing things I love about Ruby, like the | metaprogramming and monkeypatching ... but I have moved on. I do | my server work with Elixir these days. | kotutku wrote: | I would like Crystal to include some modern features instead just | being a fast and nice language. How about stuff like: | immutability, good functional programming support, complex union | types, compile-time guarantees? | brokencode wrote: | Check out Crystal's official website sometime to learn more | about its modern features. Crystal doesn't have immutability, | but does have support for functional programming and union | types. "Compile-time guarantees" is such a vaguely specified | feature that virtually all programming languages have that, but | its type system is actually pretty strong despite how it looks | with all the type inference, and even prevents null pointer | exceptions unlike a lot of static languages. | | It also has a lightweight threading system that makes it work | well as a web server. Not quite as elegant as something like | Elixir, with its isolated processes, but still nice. It also | supports macros, which can make the code a lot more elegant and | performant compared to similar languages. | chrisseaton wrote: | Crystal and Ruby do not even remotely have the same semantics. | They look similar but the similarity is extremely superficial. | The extra semantics that Ruby has have a _massive_ impact on | performance, for even the most optimising implementations. This | is not a reasonable comparison if you do not consider the 'slick | as Ruby' part. No runtime metaprogramming! The entire Ruby | ecosystem is built on runtime metaprogramming! | | (Crystal is a fine language and compiler - but it's nothing to do | with Ruby.) | nine_k wrote: | Ruby has to have runtime metaprogramming because it has no | other time but run time. | | Compile-time metaprogramming is way safer and more performant, | but for it you need a compiler. | norswap wrote: | But Crystal doesn't have those either. | hamandcheese wrote: | Yes it does? https://crystal- | lang.org/reference/syntax_and_semantics/macr... | choxi wrote: | It has macros at least: https://crystal- | lang.org/reference/syntax_and_semantics/macr... | eggsnbacon1 wrote: | Metaprogramming doesn't need to have a performance impact. VM | languages like Java, C#, and JS allow you do define and modify | code at runtime | | JS you can redefine anything, Java support is pretty good, C# | better support is coming with generators | rantwasp wrote: | you obviously did not do metaprogramming in Ruby. Try it and | you will never look at the "metaprogramming" capabilities of | other languages in the same way. | | no offence to JS, but JS and a proper programming language | are not even the same species. | hosh wrote: | Ruby's "metaprogramming" is something Java, C# and JS don't | do well -- dynamically redefining things during runtime. | Everything in Ruby, including literals and operators, can be | redefined during runtime, because everything is an object, | and every message passed to any object can be redirected, | filtered, transformed ad hoc. It's not just classes can be | modified. Specific objects can be modified. Well-crafted Ruby | code breaks things up into mixins that can be composed | together. The closest comparison is one of Ruby's inspiration | -- Smalltalk. | | I think the most exciting optimization people have seen with | Ruby is Truffle. | | I don't regret the 14 years I put into writing Ruby | professionally. I've used and abused metaprogramming, and it | has shaped how I reason and architect things. I learned to | appreciate well-designed, semantically-meaningful DSL. But | I've moved on. I write server code with Elixir these days, | and I'm exploring other ways of reasoning and writing code. | YorickPeterse wrote: | Ruby's meta-programming capabilities is why optimising Ruby | is so darn difficult. It's extremely powerful, but also | complicates a lot of things. | eggsnbacon1 wrote: | I reason the difference is mostly when metaprogramming can | happen. In Java, redefining or adding code is very | explicit, it can't just happen whenever. Same with C#. And | there are lots of rules. A class can't modify itself, and | there's limits to what changes you can make. And to make | changes you must have control over the "classloader" that | loaded that code. | | JS on the other hand can do most of what ruby does, to my | knowledge. Objects are key value pairs so you're free to | mess with them in virtually any way you please. You can | also mess with the inheritance by altering JS prototype | chains. | | I don't think metaprogramming itself has much to do with | the speed of Ruby, with my admittedly limited knowledge of | this stuff | hosh wrote: | However, JS does not treat everything as an object as | pervasively as Ruby does. A JS object and a JS integer | are two different abstract data types. A Ruby number | literal "1" is treated as an object of the Integer class. | There are no separate abstract data type other than an | object. All operators for an Integer can be overriden (in | runtime), or perhaps, a specific object's methods can be | overridden. Now granted, the runtime cheats and | implements certain things in C ... but those can be | overriden during runtime ... | | If you want to find out more about the limits of what can | be done to optimize Ruby, check out the Truffle project. | That came out of someone's PhD dissertation on novel | methods for doing JIT optimization for Ruby. It is | sufficiently difficult and novel to warrant awarding a | PhD for. Last time I heard, Truffle still could not run | Rails. | kipply wrote: | On top of Ruby metaprogramming being unique, V8 and Hotspot | are amazing premium deluxe engines that have had more time | and/or resources. | aardvark179 wrote: | Metaprogramming definitely has a cost, but it's one that you | can minimise if you have the ability to either invalidate and | recompile code at run time, or if you can perform extensive | whole program analysis when ahead of time compiling. | | The more extensive the meta programming you can do, the more | work it is to implement this under the scenes. For example in | Java you can change the visibility of fields, and you can | load new classes, so that's not too hard to take account of, | but in Ruby you can redefine methods, add refinements so they | behave differently depending on where they are called, or | radically change the inheritance hierarchy. Implementations | like TruffleRuby can maintain high performance even with | these features being used, but it's taken a lot of work to | achieve that. | watzon wrote: | Care to explain more? Having been using Ruby for about 8 years | and Crystal for about 4, they actually have an extremely | similar syntax and are also semantically very close. To the | point where many Ruby scripts are completely valid Crystal, or | at the very least require only a few changes. | | I do think that people trying to compare Crystal to Ruby kind | of miss the point though. Ruby as an interpreted language, even | optimized with JIT compilation, will never match the | performance you can get out of a true compiled language. By the | same token, Crystal as a compiled language will never be as | quick to develop with since you have to wait for your code to | compile after each change. | nickbauman wrote: | Yeah how fast is that compiler? If it's just another compiled | language (rather than the kind of wicked fast compiled | language like Go), my enthusiasm will be dampened... | nine_k wrote: | If it has reasonable incremental compilation, it can take a | few seconds to compile. | | With good code structure, I see large Java projects compile | small changes in seconds, even though compiling Java used | to be a hog. You don't often rebuild from scratch during | development, do you? | chrisseaton wrote: | > Care to explain more? Having been using Ruby for about 8 | years and Crystal for about 4, they actually have an | extremely similar syntax and are also semantically very | close. To the point where many Ruby scripts are completely | valid Crystal, or at the very least require only a few | changes. | | It doesn't have Kernel#eval. It doesn't have Kernel#send. It | doesn't have Kernel#binding. It doesn't has Proc#binding. It | doesn't have Kernel#instance_variable_get/set. It doesn't | have Binding#local_variable_get/set. It doesn't have | BasicObject#method_missing. It doesn't have | BasicObject#instance_eval. I could go on. All these methods | have extreme far reaching non-local implications on the | semantic model and practical performance, and specifically | defeat many conventional optimisations. | | > To the point where many Ruby scripts are completely valid | Crystal, or at the very least require only a few changes. | | You can't even load most of the Ruby standard library without | these methods! | | And it doesn't matter if _you_ use them or not. They 're | still there and they impact semantics and performance because | the fact that you _can_ use them affects performance. You can | 't even speculate against most of them as they're so non- | local. | | Rails and the rest of the mainstream Ruby ecosystem | _fundamentally depend on them_. | | > and are also semantically very close | | Sorry I super disagree with this. They look similar. Dig into | it just below the surface? Start to model it formally? Not at | all. Method dispatch, which is everything in Ruby, isn't even | close. | | (Again, Crystal's great as its own thing, it's just not | similar to Ruby's semantics. If you don't need Ruby's | semantics or you can replicate them at compile time then | maybe it's perfect for you.) | dwheeler wrote: | Agreed. Crystal looks like it has many positive | characteristics, but having similar syntax has nothing to | do with having similar semantics. Without constructs like | missing_method, you cannot run practically any of the Ruby | ecosystem libraries, including everything involving Rails. | | Java and C also share a similar syntax, but that does not | make that you can easily swap one for the other. | strken wrote: | To dig into method_missing a bit more: when you call a non- | existent ruby method on any object it has to check for and | run a method called method_missing, which can contain | arbitrarily complex code, on the object itself as well as | every class in the inheritance hierarchy. Because ruby is a | dynamic language with dynamic dispatch, you can't easily | precompute the results of doing this. | deedubaya wrote: | I really like Crystal and have had good success using it for web | stuffs. I still reach for ruby first, because the dev feedback | loop is a lot faster and the ecosystem is more vibrant. | | The biggest gain I've found in using Crystal vs Ruby is reduced | memory usage. | | Most often my code isn't slow because of the language, but the | data access behind it, so counting CPU cycles is less of a | priority for me. | kipply wrote: | I think language-to-language comparisons are often "unfair". | Comparing Ruby and Crystal is probably not much better than | comparing Ruby to C. Syntax is not an absolute signal for | language similarity, and the most important parts of Ruby (in my | opinion) more or less require it to be interpreted and not | compiled. One can always make languages look the same (which | Facebook did with OCaml <-> JS transpiler) without the languages | being similar by other measures. | | tl;dr "yes, that one is faster and looks similar to the other one | so our developers will be less scared and this is good" is | important and valid but "that one is just the fast language but | slower" is not a valid comparison, which I'm kind of getting. | ukd1 wrote: | A massive point for me missing in this article is the experience | of having to wait for Crystal to compile - especially for | development, this sucks. Try timing puts "Hello, world" on both: | ~ % cat test.rb puts "Hello, world" ~ % time ruby | test.rb ruby test.rb 0.03s user 0.05s system 25% cpu 0.312 | total ~ % time crystal run test.rb Hello, world | crystal run test.rb 1.48s user 1.09s system 72% cpu 3.559 total | coder543 wrote: | I find this perspective kind of funny, because every medium- | sized Ruby project I've worked on takes 10+ seconds just to | load and start rspec (before it runs any of my tests) or a | REPL. | | Because of this, working with Ruby is actually substantially | _slower_ than working with a similarly sized Go project, in my | experience, even though Go theoretically has the disadvantage | of needing to compile things before running them. | | Plus, Go's type system will catch tons of bugs that Ruby | optimistically treats as "maybe you _meant_ to do this ". | Obviously Rust's type system is really awesome, but Go's is | still really helpful, while compiling much faster than Rust. | | Since Rust and Crystal are both LLVM-based, I would guess that | Crystal's compile times also tend to be a bit painful on | medium-sized projects, like Rust, but I've never actually used | Crystal for anything more than "Hello, World". | | (I've been paid to work full time with Ruby, Rust, and Go over | the years, and they each have pros and cons, but I just don't | think I would ever personally choose Ruby for any new project | in 2020, if I had a say in it, given how great Go is for web | development.) | ptcodes wrote: | The test was against compiled Crystal code: | | crystal build program.cr | | ./program | moonchild wrote: | I wonder if it would be possible to write in a common subset of | ruby and crystal, and use the former for dev and the latter for | prod? | mauricio wrote: | Here are thorough benchmarks of Crystal and C up against other | interpreted languages: https://github.com/kostya/jit-benchmarks. | It has other examples besides Fibonacci. | tmikaeld wrote: | I'm quite surprised to see Javascript / Node so high up the | ranks, quite impressive the amount of optimizations that's been | done to the engine throughout the years. | nicoburns wrote: | Yeah, a lot of people don't realise that JavaScript | performance is closer to Java than Python, even though the | semantics are much closer to Python. | qeternity wrote: | We recently finished our first node backend (typescript) | and I have to say it was an amazing experience. The npm | ecosystem definitely attracts its fair share of | undesirables, but I struggle to see why JS full stack isn't | the default for 99% of people. | joelbluminator wrote: | Lots of people are sure their stack provides an amazing | experience, for example I don't get why you would choose | node over rails. As for the performance: looks good for | js, but quite a memory hog or am I missing something? | rvz wrote: | Also same source, but more comparison against a wider variety | of other languages and general benchmarks of data structures | and algorithms: https://github.com/kostya/benchmarks | tobyhinloopen wrote: | It's cool if it looks like Ruby but if it doesn't run Ruby gems, | it's kinda pointless to consider it a Ruby alternative. Might as | well consider Rust or C++ | frizkie wrote: | I'm excited to see Crystal grow - I think it has a lot to offer. | I know it's not an intended consequence or a goal of Crystal's | development, but you'd be surprised how easily plain Ruby maps to | Crystal's syntax and method name choices. It can come in handy | when you're looking for an easy performance boost. | danielsokil wrote: | Here is a much more fair comparison: Ocaml vs Crystal: | | https://gist.github.com/s0kil/155b78580d1b68768a6c601a66f8e2... | jimbob45 wrote: | What's the elevator pitch on Crystal? Seems like typed Ruby with | performance considerations. | ksec wrote: | "Go" with Ruby like Syntax. | Subsentient wrote: | Except Crystal has real pointers and can be used in kernel- | space. | Trasmatta wrote: | Exactly, it's basically statically typed, compiled Ruby with | really good performance. | | Also it has really powerful type inference, which allows you to | write really Ruby-like code while maintaining your static | typing. The downside of that is that it makes the compiler kind | of slow. | ngngngng wrote: | Does go-to definition work in crystal? That was always my | least favorite thing about ruby, I find it very awkward to | write code without being able to jump to definitions. | Trasmatta wrote: | There are go-to definition plugins that work for Ruby, | depending on what editor or IDE you're using. They're just | not always perfect, because in a non statically typed | language, you can't always know what the implementation of | a method is. | | Last I checked, there wasn't great editor support for | Crystal yet, but that might have changed by now. | gavinray wrote: | This is the reason why I have to keep IntelliJ Idea | Ultimate/RubyMine laying around. | | I don't have the greatest laptop, and run a lot of | containers, so Jetbrains IDE's are generally a no-go for me | and I stick to VS Code, but trying to develop Ruby/Rails | without a Jetbrains IDE Is crippling. | | I think ctrl+click definition jump does work in Crystal and | it has a decent language server. | ngngngng wrote: | That's good to keep in mind, I strictly write code in vim | but if I have to pick up ruby again I'll give those IDEs | a shot. | | I really like the sales pitch of rails, but as someone | that almost exclusively writes in modern, compiled | languages, it's really tough to adjust to ruby and be | productive with it. | gavinray wrote: | I wrote Rails professionally for a few years back when | that was THE de-facto tech for greenfield projects and | new startups. | | At the time, I really enjoyed it, but I'd never written a | modern-feeling typed/gradually typed language yet (C#, | which I found really verbose and ugly, a lot of pre-ES6 | JS and Ruby, some Lua + Python). | | I would never voluntarily write it again. Ruby/Rails | completely falls apart in larger projects if the entire | team doesn't stick to best practices + stringent code and | doc standards with stuff like YARD or Sorbet. And so many | magic, implicit methods injected everywhere. | | Super happy writing Typescript and occasional Rust/Go | now. I thought Kotlin was pretty nice too, the one small | project I had to implement in it. | joelbluminator wrote: | Sorbet ? So you can't write maintainable dynamic code | without basically making it static is what you're saying. | tomphoolery wrote: | Crystal isn't really "typed Ruby" because that would mean it | includes all of the same metaprogramming capabilities that Ruby | does. Crystal is a lot more limiting than Ruby in this regard. | For example, Crystal doesn't allow you to automatically | generate class constants. | | I would say the elevator pitch is "A systems programming | language with an expressive syntax, similar to Ruby". | kgraves wrote: | Fast as C, Slick as Ruby with their own implementation of Go's | 'goroutines' called Fibers. | | More here: | | https://crystal-lang.org/ | prh8 wrote: | For a while, it was Ruby's ease of use with C's performance. | | I believe that now they're trying to reduce marketing that | relies on Ruby and present more as an independent language | (which it is). | bovermyer wrote: | Coming at Crystal from a Go and PHP development background, here | are my thoughts: | | - The syntax is lovely. No, really. | | - I hate waiting for it to compile, especially compared to Go's | compile time. | | - It's really young yet, and the ecosystem is just getting | started. | mhd wrote: | Crystal is quite interesting, but a fib benchmark? | WJW wrote: | I mean it's kinda trivial but it does have the added benefit of | also showing off that it's statically typed. For a more real | world example check out the website of Kemal (Crystals Sinatra | equivalent) at https://kemalcr.com/, where it benchmarks at | ~46k requests per second in a HTTP benchmark vs 4k in Ruby. | ksec wrote: | Or something like Sidekiq in Crystal [1], which was 7 times | faster. But now I wonder how much faster would Sidekiq run on | TuffleRuby. | | [1] https://github.com/mperham/sidekiq.cr | WJW wrote: | I'd be amazed if TruffleRuby could beat Crystal, given the | problems most JITs have had with Ruby. By restricting the | extreme dynamism of Ruby a bit, Crystal can allow for | optimalisations that no Ruby implementation will be able to | match. It's just really difficult to properly compile | languages where it's possible to dynamically redefine the + | operator based on input from HTTP requests. That said, | Truffle is looking super cool and Crystal does not have | anything near the size and quality of the Ruby ecosystem | yet. | [deleted] | devmunchies wrote: | Neat seeing crystal on here. | | I built my startup using crystal and now I write crystal full | time. | | I'm still only using a single dedicated server with a SQLite | database. | G4BB3R wrote: | I really liked Crystal some years ago, even used in a prototype. | Soon I realized that despite its cute syntax and good | performance, coming from Elm, I would prefer a simpler language | with type safety, good error messages and very fast compile time. | Maybe in the next years I'll experiment again, especially due to | Lucky web framework. | cosenal wrote: | Some underrated live coding tutorials on Crystal on this YT | channel: https://youtu.be/Q1wOFmt3yng | zucker42 wrote: | The recursively written Fibonacci benchmark is terrible for | comparing a interpreted language to a compiled language. When | compiling with optimizations in C, for example, the compiler | significantly transforms the program. [1] It's possible Crystal | does too. | | [1] https://godbolt.org/z/vaESBG | bjoli wrote: | It can be an ok way to measure procedure call overhead though | (between languages with similar semantics) | ken wrote: | Maybe, but these two languages don't exactly have similar | semantics. | | You can't make a Ruby program that uses fixnums-only. You | also can't make a Crystal program that's dynamic like Ruby. | Interestingly, though, when the Crystal implementation is | modified to use bignums, the Ruby implementation is over | twice as fast _while still being dynamic_. | | What I get from this is that Crystal's static compiler is | slower than Ruby's dynamically dispatched interpreter, and | Crystal's bignums are so slow you'll need to think hard about | whether you want a 50x speed boost or correct arithmetic in | all cases. | | I think Crystal is a good concept, but it's only version | 0.34.0. _Every_ implementation is bad at version <<1.0. Ruby | <<1.0 didn't have good performance, either. I'm sure Crystal | will be great by the time it gets to 2.6.5, too. | eeereerews wrote: | Recursive fibonacci is highly artificial, but the fact that a | compiler can greatly transform the program is an important | advantage of compilers over interpreters. | zucker42 wrote: | I had the (mistaken) impression that the GCC code signicantly | transformed the algorithm because there is only one recursive | call instruction, but looking at it closer it's still | exponential. I still think it's a pretty silly idea to use | such an unrealistic benchmark. Especially since it might be | possible to make a compiler turn the exponential algorithm | linear. | ken wrote: | That's sort of true, in the abstract, but it wouldn't explain | what's going on here. Even JavaScript (with a JIT, but not a | separate compilation step, and certainly no type annotations) | is faster at this than compiled Crystal. There's clearly | other factors which are much more significant than simply | having a compiler. | choxi wrote: | If `brew install crystal` is taking more than an hour to | complete, in my case it was because llvm is a dependency and for | some reason it was trying to compile it from source. If you | manually install a precompiled llvm (you can do `brew install | llvm` with some flags) it should skip that step during the | crystal installation. | timon999 wrote: | This article by one of the Crystal developers explains why this | comparison is not fair: https://crystal- | lang.org/2016/07/15/fibonacci-benchmark.html. | | > When doing operations between integers, Ruby will make sure to | create a Bignum in case of overflow, to give a correct result. | | > Now we can understand why Ruby is slower: it has to do this | overflow check on every operation, preventing some optimizations. | Crystal, on the other hand, can ask LLVM to optimize this code | very well, sometimes even letting LLVM compute the result at | compile time. However, Crystal might give incorrect results, | while Ruby makes sure to always give the correct result. | ksec wrote: | You may want to edit the link. | timon999 wrote: | Whoops. Thank you! ___________________________________________________________________ (page generated 2020-06-05 23:00 UTC)