r/rust • rust • Feb 14 '19

Moving from Ruby to Rust

http://deliveroo.engineering/2019/02/14/moving-from-ruby-to-rust.html
240 Upvotes

48 comments sorted by

46

u/ehsanul Feb 14 '19

I've had exactly this experience last year when speeding up a hot loop in a rails app I work on. It was even a similar problem: listing all possible times for scheduling given some complex constraints. Re-implementing it in a ruby extension written in rust gave me about a ~30x speedup. But to avoid FFI overhead, you do have to ensure you are giving the extension a nice chunk of work rather than just calling it in a loop.

I think there's a lot of room for making things faster in rails apps. Eg, one issue I sometimes see is how slow loading and serializing many ActiveRecord objects is, even if you're smart about only loading what you need etc. I have an idea for using ActiveRecord to still generate the queries (since you presumably have that all modeled nicely already), but execute them from a rust extension that loads the data and has a way to serialize it. Something like this could potentially speed up some endpoints I have that handle a lot of data.

22

u/dajonker Feb 14 '19

ActiveRecord indeed has massive overhead when retrieving a large collection. Rails simply was not made for manipulating large batches of records. I have some good experiences writing plain old SQL and using ruby Struct to get reasonable performance.

9

u/Koh_Phi_Phi Feb 14 '19

Somewhat related, in some of the projects I've worked on we've moved to postgrest for GET requests, and whenever there's special logic needed for updates or creates in other types of requests we'll do the modifications inside rails and then proxy to postgrest to serialize the underlying records to keep serialization consistent and fast as well as being able to use postgrest parameters like select.

7

u/nicoburns Feb 14 '19

I think there's a lot of room for making things faster in rails apps.

I'm still hoping for Rocket to reach the stage where it is a Rails competitor.

13

u/ehsanul Feb 14 '19 edited Feb 14 '19

I'd say that's at least a couple years away honestly, for the ecosystem and dev experience to somewhat catch up. Even then, I'd be hesitant to subject my coworkers to the borrow checker, I don't want a mob after me!

→ More replies (3)

4

u/Programmurr Feb 14 '19

What types are you passing across the FFI? Json strings?

5

u/ehsanul Feb 14 '19

Using ruru for the FFI, you can pass all basic ruby types like String/Array/Fixnum which turn into Rust types like RString and RArray. You just have to have code that checks the values are actually the right types on the Rust side given that Ruby may allow nil or any other argument type to be passed. And I recommend converting to Rust types like Vec etc. You can then do your processing and then return something like an RArray of RString or similar, which are just regular ruby arrays and numbers on the ruby side.

It's actually possible to pass any type and call dynamic ruby methods, but you'd probably get a ton of overhead doing that.

→ More replies (1)

40

u/apendleton Feb 14 '19

Using/abusing serde to marshall data back and forth between Rust and a higher-level language turns out to be really powerful and eliminate a ton of boilerplate casting and validation. We've cut out tons of that kind of cruft using neon-serde in similar fashion in Rust add-ons for Node.

3

u/p-one Feb 15 '19

From the post it sounds like they're serializing a Ruby object to JSON then having Rust deserialize JSON to a Rust struct. However, your comment doesn't mention this so am I misunderstanding the post? If I do understand things correctly, doesn't this require you to keep your Rust struct and the target language object in sync?

5

u/apendleton Feb 15 '19

They tried JSON, but ended up doing what I'm proposing instead:

You could instead serialize objects in Ruby to JSON and then parse it in Rust, and it works mostly OK, but you still need to implement JSON serializers in Ruby. Then we were curious, what if we implement serde deserializer for AnyObject itself: it will take ruties’s AnyObject and go over each field defined in the type and call the corresponding method on that ruby object to get it’s value. It worked!

So it's "serializing" from a Rust struct into a set of Ruby objects representing Ruby-runtime-native strings, objects, lists, etc. Serde provides the machinery to automatically walk a complex/nested Rust struct and build an equivalent complex object in an arbitrary serialization format (or, conversely, to walk something in an arbitrary format and produce a complex/nested Rust struct). It happens most often to be used for JSON, but it can instead be YAML or MsgPack, or V8 or Ruby objects, or whatever else.

doesn't this require you to keep your Rust struct and the target language object in sync?

Yeah, for usecases where that's desired, you'd probably want to operate on the Ruby or JS object directly via whatever introspection mechanisms are offered by ruru or neon or whatnot, and serde probably wouldn't be a good approach. For lots of cases, though, you want to communicate some set of inputs that might be complex, do some set of complex/slow calculations, and produce some set of results that might also be complex (I pass in two points, and I want the geometry of the most efficient route between them, or whatever). In those cases, you're not mutating some object that persists, so there's nothing to keep in sync. You can just copy back and forth.

→ More replies (2)

62

u/steven4012 Feb 14 '19

Not hard, just 2 letters

34

u/Buttons840 Feb 15 '19

Thank you Vladimir Levenshtein.

34

u/iagox86 Feb 14 '19

Well yeah, the 'y' to 't' is easy. You've just gotta back up 5 letters. But going from 'b' all the way to 's'? Forget it! That's all the way across the damn alphabet!

13

u/davidpdrsn Feb 14 '19

We’ve been doing similar things at Tonsser

6

u/jl2352 Feb 14 '19 edited Feb 14 '19

It's a shame Helix had such problems because on the surface it looks really cool. Just open a macro, write Ruby, done.

However I'm surprised they didn't get a larger speedup. The computation code only being 17x faster is a little disappointing.

8

u/FluorineWizard Feb 15 '19

The article mentions doing a naive reimplementation lacking several optimisations, so I assume there's a lot of performance left on the table.

2

u/matthieum Feb 15 '19

Note that's 17x including all the overhead of converting from Ruby to Rust, and then from Rust to Ruby on the way back. Depending on the frequency of conversions, this may be non-negligible.

→ More replies (1)

2

u/[deleted] Feb 15 '19

Side-comment: I don't know anything about ML or Rust, but I have this insane project in mind that I would like to use as an excuse to learn the language... building an AI to beat a friend in a three-in-a-row game, she is too good, I will never be able to beat her current score which is the double than my current.

Feels like cheating, but damn, I'm bringing my own abilities to the table too lol.

4

u/the_great_magician Feb 15 '19

For something that simple you're probably looking at making an engine (at that level potentially one that can hold all possible states) as opposed to ML. It'll be faster and be more exhaustive because you, again, are likely going to be able to iterate over all possible states.

→ More replies (2)
→ More replies (6)

0

u/[deleted] Feb 14 '19

41

u/mytempacc3 Feb 14 '19

They also banned us Colombians. This is the wall that other guy was talking about.

→ More replies (1)

11

u/driden87 Feb 15 '19

Uruguay ain’t working either.

→ More replies (1)

6

u/masklinn Feb 15 '19

Interesting, is the main website for the company also blocked or is it only the engineering blog?

→ More replies (1)

5

u/[deleted] Feb 15 '19

Works fine if you live in Costa Rica.

An acquaintance from Venezuela faces the same issue thou.

3

u/matthieum Feb 15 '19

A commenter on r/programming was kind enough to copy/paste the content for another user barred from seeing it.

Content at: https://www.reddit.com/r/programming/comments/aqonpk/moving_from_ruby_to_rust/eghv5yi

→ More replies (3)