UPDATE: I've stuck a comment below with updated benchmarks after the community helped me to improve my Rust code. Spoiler alert: Rust is wicked fas...
For further actions, you may consider blocking this person and/or reporting abuse
You’re doing a lot more type conversion in Rust than you need to, too. You’re also not checking your inputs in JavaScript like you are in Rust.
I think the biggest thing, though, is that you’re recreating the
HashMap<String, &str>
for the words with every call ofcheck_for_words()
, but you’re using a global in the Node program. Since you’re calling thestr.to_string()
it isn’t optimizing it out in the compiler.Oh man, good catch. I bet that has a pretty significant impact. I'll give it a go!
I think the
check-for_words
func is going to end up being unnecessary, and I'll just end up grabbing the first number I see whether a word or a digit. I'll have to update the js for that to be a fair comparison, though! Either way, you're right... that hashmap needs re-scoped.First thing I see in the rust code is this:
There is no need to actually
.collect
the reversed string, if the last character was a digit, you are still building a possibly thousands of characters long string just to ignore all of them.You should try using the iterators directly, make the function take a character iterator, then for the last digit just send a reversed iterator.
OK, trying this out now. It makes perfect sense that it is more efficient to use the iterators, but I'm running into the problem of mismatched types (which is why I originally wrote the code like this):
input.chars()
returns aChars
iterator, butinput.chars().rev()
returns aRev<Chars>>
iterator.So it seems like the tradeoff is duplicating code (
if reverse, for c in input.chars()
and againelse for c in input.chars().rev()
) to get optimal performance. That's what I'll go with, but is there no way to make it a little DRYer? (I'm guessing the answer is a custom macro? Not sure I'm ready to tackle that!)I believe you could make your function generic over a char iterator, which Rev implements: doc.rust-lang.org/std/iter/struct....
Ah, thanks! I couldn't find it for some reason.
Thanks! I'm going back through this code tonight, so I'll give this a go.
No matter how optimized the code is, if Rust was much faster than JS in a single execution, the noteworthy aspect is likely that JS performed exceptionally well in repeated executions.
This phenomenon could be attributed to hot code optimization and garbage collection.
Though not an expert, I believe JS has features to optimize frequently executed functions, and if GC doesn't run, continuously allocating memory might result in faster measurements than actual performance.
Thanks, Yeom! This was really at the heart of why I was so confused. Node or JS itself has to be doing something like this to achieve the result it does. That also explains (at least partially) why the JS code is so much faster for 1000x the load than a single execution.
EDIT: The commenter above pointing out the HashMap being re-created each loop has probably identified the key issue.
Still really curious why the Rust code doesn't scale linearly. It must be doing something similar, but without relying on the garbage collector?
Interesting 🧐 The first thing that comes into my mind is that Rust shines in cases where garbage collector creates a bottleneck: for example when you store a large amount of data in memory and change it rapidly. In this case you probably have one constant variable and no garbage to collect.
JavaScript indeed has a “cache” for operations: I’ve seen similar results with WASM vs JavaScript comparison.
Once again, it doesn’t mean that JavaScript is faster than Rust, it simply means that in this specific scenario one tool is suited better than the other.
Some years ago I’ve read an article on switching from Go to Rust by Discord and I think this is a great use case where Rust would outperform anything else.
Cool -- I never knew about JS's operations cache. I'll need to look into that more!
A comment above pointed out that I was using a global map in the JS, but recreating the map each time in Rust. I'm updating the code and re-running benchmarks, so we'll see how it shakes out! I'll update the post with results.
UPDATE: I've got my answer. Rust is, in fact, WAY faster, ESPECIALLY in the 1000x test. Many thanks to all who commented and helped me get my Rust up to snuff. 😅
I updated my Rust code to use iterators directly (thanks Eduard!), globalize the HashMap instead of recreating it each function call (thanks Mike Stemle!), and generally be smarter about searching for numbers (not doing full string replaces, but stopping my loops as soon as a valid match was found). Where those changes could be ported to the JS version, I did so. Full code on GitHub.
Here are the new results:
Rust single iteration is down to 10.161ms (almost cut in half from the previous 18.317!).
JavaScript single iteration is up to 71.723. It looks like the previous version was more efficient at ~39ms, which just goes to show you that porting code one-to-one between languages as different as Rust and JavaScript isn't always the best practice. While it would be interesting to investigate further, I think it's most fair to compare the fastest working JS code to the fastest working Rust code here, and Rust ends up at ~25% of the JS code's total the execution time.
Rust 1000x iterations is down to an amazing 385.875 ms, 17x faster than before (at ~6 seconds) and only ~38x slower for 1000x the work. 🤯
JavaScript 1000x iterations comes out to 3691.43. This is, not surprisingly, MUCH slower than the Rust 1000x test (by ~100x) and slower than the previous JS version's 1000x test. What IS surprising is that it's not off the previous version by much, with the old code coming in at 3260.6. So despite the difference in single-iteration performance of the JS code from old to new nearly doubling, the 1000x iteration test only ends up being ~400ms slower.
That said, in that 400ms difference, the Rust code could have run it's full 1000 iterations and had a spot of tea.
It’s marvelous to watch you dig in here and challenge yourself to understand with the help of the community. Solidarity.
Thanks so much! Appreciate the help, and your time!