Improving Ruby Performance with Rust
blog.codeship.com
blog.codeship.com
I see we're back to stuff like
let ptr = path.as_ptr();
let c = unsafe { *ptr.offset(i) };
if c == SEP {
return &path[(i + 1) as usize..end];
};
It's as low level as it can get. I remember that I wrote a small web app in C in 1994 but I wrote the next one in Perl and never looked back. It was some hundreds lines, countless core dumps and many hours vs much less code and pain and time.Again, Ruby and Python are all about connecting those small pieces of C code that implement their builtin and library methods / functions. Writing them in C or Rust makes no difference. I personally won't write code in those kind of languages again unless I find myself in a scenario where CPU time is worth more than my time. Maybe we'll be back to that with functions in the cloud billed by the millisecond. Programming is going to be a pain again. At least modern languages like Rust don't crash like C and have a better concurrency story. Meanwhile I'll keep using Ruby and Python with C or Rust extensions from GitHub written by somebody else (a big thank you!), and Elixir where concurrency matters.
extract_last_path_segment(&str, &str)
This is pure Rust, no C interface involved. let mut bytes = path.bytes();
if let Some(end) = bytes.rposition(|&x| x != SEP) {
if let Some(idx) = bytes.rposition(|&x| x == SEP) { &path[idx..end] } else { &path[..end] }
} else { "" }
There are other ways to do this, like using a single iterator where we scan manually, but I just threw this together to demonstrate that this code needn't use unsafe, & by extension needn't use pointers. All that unsafe stuff is more so that if you need to drop down to C from safe-Rust you can instead use unsafe-Rusthttps://doc.rust-lang.org/src/std/path.rs.html#1801-1808
I don't see how Rust vs C makes no difference. Both languages have trade-offs and I'm pretty sure that Rust advocates would claim the increased safety of the Rust part as a big advantage, and it will certainly reduce those core dumps. Probably the reason there are so many of those advocates is because they don't have to write such error-prone low-level code. Expressive code is more fun, in my experience.
It is very much possible, that if you run code on a big enough number of CPUs , it'll worth more than an average developer time.
let c = s.as_bytes()[i];
In unoptimized/debug mode that's going to do array bounds checks that the unsafe code doesn't, but I think the compiler is smart enough here to see that you're doing the exact same checks in the loop condition too, and it can optimize them out in release mode?Was doing some work of converting [0,1] signals to 16-bit PCM data with 550Hz tone in numpy.
Python version took ~15 minutes to generate 5,000 4 second files. Broke out the inner loop into Rust with FFI via ctypes and cut that time to ~10 seconds with nearly identical code.
Seriously though Rust was 43 lines of code + cargo build vs 33 lines of Python and a simple C FFI so it was pretty straightforward to drop in.
I can write in anything I want since every language supports C ABI for system calls.
I don't want to have to learn a whole new language and new API at the same time.
Also a cursory glance at Julia's numpy support looks like it requires deep copies which is painful when you want things to be fast.
I'm sure it's a great language but needing to drag in a whole new VM means it doesn't fit my needs.
It removes the complexity of needing to do the FFI at all.
It look me 3 lines of code to import the dll, assign the signature and call the function, I'd hardly call that complex.
#[no_mangle]
pub unsafe extern "C" fn transform_pcm(
data: *mut c_short,
len: c_uint,
ramp_time: c_uint,
tone_delta_frame: c_float) {
then call from python: gen_pcm_dll = ctypes.cdll.LoadLibrary("gen_pcm_rust/target/release/gen_pcm.dll")
gen_pcm_dll.transform_pcm.argtypes = [ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_float]
gen_pcm_dll.transform_pcm(ptr, len(pcm), ramp_time, tone_delta_frame)
Rust doesn't generate headers(although there are helper libs if you want) and you don't need them to FFI.Rust's ability to compile to a small C-compatible binary is definitely an advantage here, although Julia will have similar capabilities soon.
Do you have a link to further info on that?
A lot of this stuff has worked for ages, but it's rough in various places and needs a better interface. I'm not sure to what extent exporting C functions is actually supported in that wrapper script, or whether you'll need to fiddle with compiler options.
I'm very tempted to compare that with LuaJIT and/or its FFI[0] (just to use structs, not to call any native code) just to see the results.
We have https://doc.rust-lang.org/std/primitive.str.html#method.as_b... for accessing a str bytewise. No need for pointers and `unsafe`.
The whole point of Rust is being safer and higher-level than C, and if you don't want to use its features, there is no improvement over C.
I mean, what's the benefit you could get from FFI Rust code into Ruby that you cannot get by directly writing Rust?
YMMV.
The mental effort of writing Erlang is an order of magnitude lower compared to Rust. While Rust is more fun, it does not and can not come close.
I'm not much for Java/Python/Ruby. I don't see those as productive as Erlang so Rust may be closer.
However, I don't think Rust is a more productive general programming language than Ruby/Python. I think Rust is more productive when solving systems-level concerns, but most problems aren't systems problems. Crystal is also looking to be a nice compromise for a productive, C/C++ level performance language (if the ecosystem catches up).
Rust is quite a good language though, I'll admit that, I'm currently learning it by way of writing a kernel in it. But there are more productive languages out there.
When working that low level, there are a lot of lifetime issues your going to need to manage, and that can definitely make Rust have a higher cognitive load.
On a side note have you looked at this blog series? https://www.tockos.org/blog/2017/apsys-paper/
They’re definitely paving an interesting path in the kernel space for Rust.
There is also plenty of unsafe stuff that won't go away or stuff that is unsafe not because of the code but because x86. For example, you can't properly handle a null pointer in rust, however, at low-level, the pointer 0x00 is completely valid and I hate to have to waste that address because Rust and LLVM won't allow it.
The major reason I even bothered to learn Rust for this way because I didn't want to manage all of this in C on top of having only printf as my debugging tool.
I find personally that if there are libraries to help me out, I'm only slightly slower in Rust than I am in Ruby, but then there's no debugging time in Rust, and invariably, there will be tons of work I have to do later with the Ruby to shake out bugs.
YMMV.
Despite the fact that I chose to do this in Python, I'm not at all sure I wouldn't have had a working solution faster in a language that had a good type system and a error handling strategy that made errors explicit and forced them to be handled. I'm almost certain that writing in Rust would have ended up with something that was more reliable in the long term.
(FTR I imagine that Go may also have been a reasonable choice for this kind of work, but I haven't used it).
By leveraging this you could quickly re-write some slow parts in Rust, gaining the benefits with only a few days work. It's certainly worth exploring.
Write the speed-sensitive bits in Crystal. Aka 'Improving Ruby Performance with Almost-Ruby'.
(Unfortunately development seems to have stopped.)
There are some technical limitations with how I handled defining functions, which has also become obsolete with recent changes to Crystal. However, there is another approach (macro based) demonstrated at https://github.com/spalladino/crystal-ruby_exts. The macro approach is what is needed.
At time of first development, this was just an experiment and I didn't feel like redo-ing it this way. Now that core team has plans for similar functionality, I'd rather not finish a half baked solution that distracts from what theirs will likely be.
Another advantage is that there are Rust packages for Ruby interop -- the article ended up using ruru. There are similar libraries for Python interop -- I believe the most actively developed right now is PyO3.
I'm sure people are sick of Rust evangelists on HN but it really is a wonderful language, and in certain cases it really does seem to empower people to do things they wouldn't have been able to do otherwise.
I guess its cool to use something as hipster as Rust to speed up as something as hipster (albeit waning in popularity these days) as Ruby.
However, a better approach would be a straight forward implementation of caching (memcache/redis/static CDN) or to simply rewrite that part of the code to not rely on constantly parsing file paths? Or use something like https://github.com/google/re2, you probably won't be able to do any better than that when doing any kind of string matching and parsing.
If you're concerned at all about performance you shouldn't be doing anything with the file system period.