[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)