I do occasionally encounter a need for raw speed, so I'm looking for a complement to Racket for those few times when it's not fast enough. I chatted briefly about this on IRC the other day, but I thought I'd tap into the collective wisdom on the mailing list.
I'm open to suggestions, but I've tentatively narrowed the list down to Rust and C. I programmed in C/C++ for about a decade, then Java for a decade, and most recently in Ruby for a decade, so it's been a while since I was an expert C hacker, and my recent Ruby experience has lessened my polyglotness :)
If I were to do a *lot* of lower level coding, I think the niceties of Rust would win out over C, but I'm planning on doing most of my application coding in Racket, and only needing a lower level language for a few speedups, some ad-hoc file crunching programs, etc.. I like the memory safety Rust provides without giving up too much performance; the standard library seems fairly rich; the language features are fairly nice, etc.
Although I think there is a goal to reduce the amount of C code in Racket, I expect there will continue to be a fair amount for the foreseeable future, so getting my C chops back would allow me to possibly contribute in that area eventually (although I'd prefer to contribute Racket code). And there are some Schemes that compile down to C which would allow me to use them in some challenged environments (e.g. for Robotics, etc.), so renewing my C proficiency would be handy.
I think the FFI interaction between Racket & C may be smoother than between Rust & C, but that is conjecture.
Multi-core complicates things a bit. The specific program that motivated me to consider Rust or C is an easily parallellizeable program to parse & dump a file into a different format, and even though I wrote plenty of multi-threaded C code in the past, I'm almost positive this would be much more pleasant, and less error prone, in Rust. And given current CPU advances, I think multi-core is important for getting the most out of code these days.
Ultimately they're not mutually exclusive, but in the near term they are, I only have so much time.
Any thoughts from folks that are FFI'ing from/to Racket and/or using a second language in the same system as Racket ?
Thanks,
Brian
I would say more the latter - less than 2x of C speed.
> -- when you say you need many cores for your computations. That sounds like very-raw speed, not just raw speed. This may call for C after all.
The example that provided the initial motivation is extremely simple - process 45M+ text records with some minor transformations (including computing a couple soundex values on first/last names). Racket came in at 2.4x faster than Ruby, but still CPU bound, and it really should be I/O bound, so I figure I'll process N records in parallel. But I'm really thinking more generally - I know I'll occasionally encounter these types of issues.
> -- then again, if it is just about trying to exploit the parallelism of your computer when possible, why not use places (or futures) in Racket? Yes, Rust's type system makes this a bit safer. It basically rules out race conditions via its type system. But this one depends on your take of how much you fear race conditions and how familiar you are with a mostly-functional approach of our CML library, which in my experience reduces this danger, too.
Yes, re-implementing the Racket version with places is on my list, and I'll compare with the Rust or C multi-threaded version. I was getting ~ 10 MB/s I/O on my SSD in Racket, but I should be able to get ~ 70 MB/s, so even using 4 cores may not be quite enough.
> -- if you opt for Rust, consider the callback problem. My (passive) experience with lots of code that has to set up call backs from C to Rust, is that this area is a bit problematic. I scanned John's blog post and didn't see how he addressed that, though I didn't read his git repo. Perhaps there are some examples there. And yes, in the end it's doable; it just seems to require a lot of fiddling.
In this specific case, I would probably just create a standalone program in the lower level language since it won't need much domain/business logic, so no FFI would be involved. In other cases, I'll definitely want to use FFI though, so if turns out that C is much better for FFI w/ Racket, that would influence me.
> -- finally, beware of the need to drop from Rust to unsafe Rust -- where the type system just gives up the key innovations that Rust introduces. This is especially noticable for callbacks, but even other code (see John's) tends to include one or the other unsafe block.
>
> -- Matthias
Thanks - good things to consider.
I did *some* hand optimizing. Here's the code for the soundex and some string helper functions I created:
https://gist.github.com/lojic/1deba97f2e2eb2fe3fc0
I fired up the profiler (in error trace mode) and worked on the critical paths. string-replace string-trim showed up. I hand rolled non-upcase-alpha and remove-zeros because they were faster than string-replace with regexes.
I'm sure more could be done, but I'd be a bit surprised if I can go from 2.4x to even 3x the speed of Ruby given the optimizing I've already done.
Brian
I think the set!'s are what make it faster. Folks proposed various for statements which were much more elegant, but they were slower. I usually favor elegance over speed, and for the vast majority of my code, elegance/readability/etc. are more important, but this code takes hours to run, so speeding it up is helpful.
> But more important is:Do you want to preserve the general structure of the code?
>
> It has many small functions that are nice to write and debug, but each
> one creates an intermediate string. In this algorithm, most of the
> processing is character-by-character. So I think that most of the
> calculations can be merged in a megafunction that avoids most of the
> allocations.
Possibly, but profiling showed that various string functions were on the critical path.
>
> Gustavo
FWIW here's some profile output:
https://gist.github.com/lojic/f1ea6371155db861e2cd
and the output from time on one run showing gc time
cpu time: 2908 real time: 2915 gc time: 84
And for completeness, I added the parser.rkt file to the gist, so all 3 files are there now:
https://gist.github.com/lojic/1deba97f2e2eb2fe3fc0
I think I'll code up a multi-threaded Rust or C version and see how the numbers compare, and then the Racket version with places.
I've actually spent a fair amount of time with OCaml, and I like the language a lot (better than Haskell), but for *me* it's kind of in an awkward spot - on the one hand, it's not quite as fast as Rust/C (although for a functional language, it's really fast), and multi-core requires async (got tired of waiting for the multi-core promise of 4.03), and on the other hand, I find Racket much more enjoyable, productive and a better fit for the way I think.
I would consider Gambit for a special purpose app that *needed* to compile down to C (maybe an embedded platform, robotics, etc. where Racket isn't available), but for my general application development, I'm sticking with Racket. I even have Racket running on my Raspberry Pi, and I think Linux is becoming more available for robotics, so I may be able to skip the whole "C" thing there as well.
Currently, the C++ library is mostly executed by Python, but Racket is gradually supplanting it.
Anyway, it works quite well from Racket.