I have a couple of things I'm wondering about though:
- Node.js is pretty good at IO-bound workloads, but I wonder if this holds up as well when comparing e.g. Go or PHP. I have run into embarrassing situations where my RiiR adventure ended with less performance against even PHP, which makes some sense: PHP has tons of relatively fast C modules for doing some heavy lifting like image processing, so it's not quite so clear-cut.
- The "caveman" approach is a nice one just to show off that it still works, but it obviously has a lot of overhead just because of all of the forking and whatnot. You can do a lot better by not spawning a new process each time. Even a rudimentary approach like having requests and responses stream synchronously and spawning N workers would probably work pretty well. For computationally expensive stuff, this might be a worthwhile approach because it is so relatively simple compared to approaches that reach for native code binding.
7 lines of rust, 1 small JS change. It looks like napi-rs supports Buffer so that JS change could be easily eliminated too.
And with just a tiny bit of extra work you can give the worker an http interface.... Wait a minute.,.
Disclaimer: I'm one of the maintainers
i've been a user of uwebsockets.js, uwebsockets is used underneath by bun.
i hope encore does benchmark compared to encore, uwsjs, bun, and fastify.
express is just so damn slow.
Beyond performance, Rust also brings a high level of portability and these examples show just how versatile a pice of code can be. Even beyond the server, running this on iOS or Android is also straightforward.
Rust is definitely a happy path.
My favorite thing about Rust, however, is Rust dependency management. Cargo is a dream, coming from C++ land.
How does a Java style com.foo.bar or Golang style URL help e.g. mitigate supply chain attacks? For Golang, if you search pkg.go.dev for "jwt" there's 8 packages named that. I'm not sure how they are sorted; it doesn't seem to be by import count. Yes, you can see the URL directly, but crates.io also shows the maintainers. Is "github.com/golang-jwt/jwt/v5" "better" than "golang.org/x/oauth2/jwt"? Hard to say at a glance.
On the flip side, there have been several instances where Cargo packages were started by an individual, but later moved to a team or adopted. The GitHub project may be transferred, but the name stays the same. This generally seems good.
I honestly can't quite see what the issue is, but I have been wrong many a time before.
I truly like rust as a performance language but I would rather like real tangible results (admittedly slow is okay) than imagination within the rust / performance land.
I don't want to learn rust to feel like I am doing something "good" / "learning" where I can learn golang at a way way faster rate and do the stuff that I like for which I am learning programming.
Also just because you haven't learned rust doesn't make you inferior to anybody.
You should learn because you want to think differently , try different things. Not for performance.
Performance is fickle minded.
Like I was seeing a native benchmark of rust and zig (rust won) and then I was seeing benchmark of deno and bun (bun won) (bun is written in zig and deno in bun)
The reason I suppose is that deno doesn't use actix and non actix servers are rather slower than even zig.
It's weird .
In python land, uv (for project) and pipx (for CLI tools).
Package management for languages owes its heritage to CPAN, which then, in turn, owes its lineage to StopAlop the first package manager written about 1992, which inspired dpkg. Now there is nix which cuts across system package and configuration management. Perhaps in the future or soon LLMs will be able to rewrite hot sections in other languages and repeatedly benchmark various implementation approaches in a generative manner.
Even self-hosting on an rpi becomes viable.
Or, in other words, it's the unavoidable result of insisting on using a language created for the frontend to write everything else.
You don't need to rewrite your code in Rust to get that saving. Any other language will do.
(Personally, I'm surprised all the gains are so small. Looks like it's a very well optimized code path.)
In my experience, languages like Ruby and Python are slower than languages like Javascript, which are slower than languages like C#/Java, which are slower than languages like C++/Rust, which are slower than languages like C and Fortran. Assembly isn't always the fastest approach these days, but well-placed assembly can blow C out of the water too.
The ease of use and maintainability scale in reverse in my experience, though. I wouldn't want to maintain the equivalent of a quick and dirty RoR server reimplemented in C or assembly, especially after it's grown organically for a few years. Writing Rust can be very annoying when you can't take the normal programming shortcuts because of lifetimes or the borrow checker, in a way that JIT'ed languages allow.
Everything is a scale and faster does not necessarily mean better if the code becomes unreadable.
perhaps you get 1300MB to 20 MB with C# or Java or go, and 13MB with rust . Rust’s design is not the reason for bulk of the reduction is the point
“Less languages features, but a better compiler” was originally the aspirational selling point of Go.
And even though there were some hiccups, at least 10 years ago, I remember that mainly being true for typical web servers. Go programs did tend to use less memory, have less GC pauses (in the context of a normal api web server), and faster startup time.
But I know Java has put a ton of work in to catch up to Go. So I wonder if that’s still true today?
Also you can do that same thing in Rust or C++ too. Very common in C++, speeds up programs quite a bit.
As I said in another comment, the most likely cause is that temporary garbage is not collected immediately in JavaScript, while garbage is collected immediately in Rust. See https://doc.rust-lang.org/nomicon/ownership.html for the key idea behind how Rust manages this.
If you truly believe that it is somehow due to data isolation, then I would appreciate a reference to where JavaScript's design causes it to behave differently.
So "Rust" means "Not JavaScript, and also a bunch of other constraints that mean that Rust is pretty much the only sensible choice."
Basically the best place where Rust can work is one where all variables, all requirements and all edgecases are known ahead of time or cases where manual memory safety is a necessity vis-a-vis accepting a minor performance hike from things like the garbage collector. This works well in some spaces (notably; systems programming, embedded and Browser Engines and I wouldn't consider the latter a valid target), but webserver development is probably one of the furthest places where you are looking for Rust.
It really depends on what you mean by "memory usage".
The fundamental principle of any garbage collection system is that you allocate objects in the heap at will without freeing them until you really need to, and when that time comes you rely on garbage collection strategies to free and move objects. What this means is that processes end up allocating more data that the one being used, just because there is no need to free it. Consequently, with garbage collecting languages you configure processes with a specific memory budget. The larger the budget, the rarer these garbage collection strategies kick in.
I run a service written with a garbage collected language. It barely uses more than 100MB of memory to handle a couple hundred requests per minute. The process takes over as much as 2GB of RAM before triggering generation 0 garbage collection events. These events trigger around 2 or 3 times per month. A simplistic critic would argue the service is wasting 10x the memory. That critic would be manifesting his ignorance, because there is absolutely nothing to gain by lowering the memory budget.
If you rebuild Linux from the ground up with isolation in mind, you will be able to do it more efficiently. People are indeed in the process of rewriting it, but it's far from complete (and moving back and forward, as not every Linux dev cares about it).
I don't think this is an educated take.
The whole selling point of JavaScript in the backend has nothing to do with "frontend" things. The primary selling point is what makes Node.js take over half the world: it's async architecture.
And by the way, benchmarks such as Tech Empower Web Framework still features JavaScript frameworks that outperform Rust frameworks. How do you explain that?
It is the availability of the developers who know the language (JavaScript) (aka cheaper available workforce).
Node.js is popular because js is popular. You pretty much guarantee an infinite pool of developers for, like, ever. And you can even use those developers across the entire stack with greater velocity and much less onboarding.
async is cool, but not that cool. CGI was doing basically that a long time ago, and it was even more automagical.
> Tech Empower Web Framework still features JavaScript frameworks that outperform Rust frameworks. How do you explain that?
The benchmarks are constructed in such a way that highlights the strengths of the particular JS JIT implementation. JS is good at a lot of things, so if you just do those things, it might appear that it has okay performance.
People do the same thing with C# vs C++; this has been a problem forever. Sure, C# is about as fast or close if you have 16 gigs allocated to the GC and your app is using 100 megs. Now run at 95% memory usage with lots of churning and the order of magnitude differences come out. It's just a fundamental problem with GC langs.
C# has excellent async for asp.net and has for a long time. I haven't touched Java in ages so cannot comment on the JVM ecosystem's async support. So there are other excellent options for async backends that don't have the drawbacks of javascript.
This also means that you cannot "simply measure memory usage" (e.g. using `time` or `htop`) without already having a relatively deep understanding of the underlying mechanisms.
Most importantly:
libc / malloc implementation:
glibc by default has heavy memory fragmentation, especially in multi-threaded programs. It means it will not return `malloc()`ed memory back to the OS when the application `free()`s it, keeping it instead for the next allocation, because that's faster. Its default settings will e.g. favour 10x increased RESident memory usage for 2% speed gain. Some of this can be turned off in glibc using e.g. the env var `MALLOC_MMAP_THRESHOLD_=65536` -- for many applications I've looked at, this instantaneously reduced RES fro 7 GiB to 1 GiB. Some other issues cannot be addressed, because the corresponding glibc tunables are bugged [2]. For jemalloc `MALLOC_CONF=dirty_decay_ms:0,muzzy_decay_ms:0` helps to return memory to the OS immediately.
Linux:
Memory is generally allocated from the OS using `mmap()`, and returned using `munmap()`. But that can be a bit slow. So some applications and programming language runtimes use instead `madvise(MADV_FREE)`; this effectively returns the memory to the OS, but the OS does not actually do costly mapping table changes unless it's under memory pressure. As a result, one observes hugely increased memory usage in `time` or `htop`. [2]
The above means that people are completely unware what actually eats their memory and what the actual resource usage is, easily "measuring wrong" by factor 10x.
For example, I've seen people switch between Haskell and Go (both directions) because they thought the other one used less memory. It actually was just the glibc/Linux flags that made the actual difference. Nobody made the effort to really understand what's going on.
Same thing for C++. You think without GC you have tight memory control, but in fact your memory is often not returned to the OS when the destructor is called, for the above reason.
This also means that the numbers for Rust or JS may easily be wrong (in either direction, or both).
So it's quite important to measure memory usage also with the tools above malloc(), otherwise you may just measure the wrong thing.
[1]: https://sourceware.org/bugzilla/show_bug.cgi?id=14827
[2]: https://downloads.haskell.org/ghc/latest/docs/users_guide/ru...
People write bad-performing code not because it's easier, it's because they don't know how to do it better or don't care.
Repeating things like "premature optimization is the root of all evil" and "it's cheaper to get a bigger machine than dev time" are bad because people stop caring about it and stop doing it and, if we don't do it, it's always going to be a hard and time-consuming task.
How many software projects have you seen fail because it couldn't run fast enough or used too many resources? Personally, I've never seen it. I'm sure it exists, but I can't imagine it's a common occurrence. I've rewritten systems because they grew and needed perf upgrades to continue working, but this was always something the business knew, planned for and accepted as a strategy for success. The project may have been less successful if it had been written with performance in mind from the beginning.
With that in mind, I can't think of many things less appropriate to keep in your mind as a first class concern when building software than performance and optimization. Sure, as you gain experience in your software stack you'll naturally be able to optimize, but since it will possibly never be the reason your projects fail and presumably your job is to ensure success of some project, then it follows that you should prioritize other things strongly over optimization.
It is very hard know if your software is going to be popular enough for costs to be factor at all and even if it would be, it is hard to know whether you can survive as a entity long enough for the extra delay, a competitor might ship a inferior but earlier product or you may run out money.
You rather ship and see with the quick and dirty and see if there demand for it to worth the cleaner effort .
There is no limit to that, more optimization keeps becoming a good idea as you scale at say Meta or Google levels it makes sense to spend building your own ASICs for example we won’t dream of doing that today
Sometimes it means spending a couple extra minutes here or there to teach a junior about freeing memory on their PR.
No one is suggesting it has to be a zero-sum game, but it would be nice to bring some care for the engineering of the craft back into a field that is increasingly dominated by business case demands over all.
The trick is getting everyone to switch over and ensure correct security and correctness for the newer software. A good example may be openssh. It is very well established, so many will use it - but it has had some issues over the years, and due to that, it is actually _very_ difficult now to know what the _correct_ way to configure it for the best, modern, performant, and _secure_ operation. There are hundreds of different options for it, almost all of them existing for 'legacy reasons' (in other words no one should ever use in any circumstance that requires any security).
Then along comes things like mosh or dropbear, which seem like they _may_ improve security, but still basically do the same thing as openssh, so it is unclear if they have a the same security problems and simply don't get reported due to lower use, or if they aren't vulnerable.
While simultaneously, things like quicssh-rs rewrite the idea but completely differently, such that it is likely far, far more secure (and importantly simpler!), but getting more eyes on it for security is still important.
So effectively, having things like Linux move to Rust (but as the proper foundation rather than some new and untrusted entity) can be great when considering any 'rewrite' of software, not only for removing the cruft that we now know shouldn't be used due to having better solutions (enforce using only best and modern crypto or filesystems, and so on), but also to remodel the software to be more simple, cleaner, concise, and correct.
I don't see why. People will just discover they rewrote something slower.
From my casual dabbling in python and rust they feel like they’re in similar ballpark. Especially if I want the python code to be similarly robust as what rust tends to produce. Edge cases in python are much more gnarly
Adding Rust into your build pipeline also takes planning and very careful upfront design decisions. `cargo build` works great from your command line, but you can't just throw that into any pre-existing build system and expect it to just work.
The same will happen on any function where you're calling functions over and over again that create transient data which later gets discarded.
The problem is that most developers are not capable of optimizing for efficiency and performance.
Having more powerful hardware has allowed us to make software frameworks/libraries that make programming a lot more accessible. At the same time lowering the quality of said software.
Doesn't mean that all software is bad. Most software is bad, that's all.
> Regarding the abnormally high memory usage, it's because I'm running Node.js in "cluster mode", which spawns 12 processes for each of the 12 CPU cores on my test machine, and each process is a standalone Node.js instance which is why it takes up 1300+ MB of memory even though we have a very simple server. JS is single-threaded so this is what we have to do if we want a Node.js server to make full use of a multi-core CPU.
On a Raspberry Pi you would certainly not need so many workers even if you did care about peak throughput, I don't think any of them have >4 CPU threads. In practice I do run Node.JS and JVM-based servers on Raspberry Pi (although not Node.JS software that I personally have written.)
The bigger challenge to a decentralized Internet where everyone self-hosts everything is, well, everything else. Being able to manage servers is awesome. Actually managing servers is less glorious, though:
- Keeping up with the constant race of security patching.
- Managing hardware. Which, sometimes, fails.
- Setting up and testing backup solutions. Which can be expensive.
- Observability and alerting; You probably want some monitoring so that the first time you find out your drives are dying isn't months after SMART would've warned you. Likewise, you probably don't want to find out you have been compromised after your ISP warns you about abuse months into helping carry out criminal operations.
- Availability. If your home internet or power goes out, self-hosting makes it a bigger issue than it normally would be. I love the idea of a world where everyone runs their own systems at home, but this is by far the worst consequence. Imagine if all of your e-mails bounced while the power was out.
Some of these problems are actually somewhat tractable to improve on but the Internet and computers in general marched on in a different more centralized direction. At this point I think being able to write self-hostable servers that are efficient and fast is actually not the major problem with self-hosting.
I still think people should strive to make more efficient servers of course, because some of us are going to self-host anyways, and Raspberry Pis run longer on battery than large rack servers do. If Rust is the language people choose to do that, I'm perfectly content with that. However, it's worth noting that it doesn't have to be the only one. I'd be just as happy with efficient servers in Zig or Go. Or Node.JS/alternative JS-based runtimes, which can certainly do a fine job too, especially when the compute-intensive tasks are not inside of the event loop.
Retry for a while until the destination becomes reachable again. That's how email was originally designed.
Sure, the SMTP email protocol states guidelines for "retries" but senders don't waste resources retrying forever. E.g. max of 5 days: https://serverfault.com/questions/756086/whats-the-usual-re-...
So gp's point is that if your home email server is down for an extended power outage (maybe like a week from a bad hurricane) ... and you miss important emails (job interview appointments, bank fraud notifications, etc) ... then that's one of the risks of running an email server on the Raspberry Pi at home.
Switching to a more energy-efficient language like Rust for server apps so it can run on RPi still doesn't alter the risk calculation above. In other words, many users would still prioritize email reliability of Gmail in the cloud over the self-hosted autonomy of a RPi at home.
At least with Rust it is safer.
Yes, for most languages. For example, in Zig (https://ziglang.org/documentation/master/#WebAssembly) or in C (https://developer.mozilla.org/en-US/docs/WebAssembly/C_to_Wa...)
> Relatedly, is it as easy to generate a QR code in C as it is in Rust (11 LoC)?
Yes, there are plenty of easy to use QR-code libraries available, for pretty much every relevant language. Buffer in, buffer out.
(Obviously there are other advantages to Rust)
Writing Rust for web (Actix, Axum) is no different than writing Go, Jetty, Flask, etc. in terms of developer productivity. It's super easy to write server code in Rust.
Unlike writing Python HTTP backends, the Rust code is so much more defect free.
I've absorbed 10,000+ qps on a couple of cheap tiny VPS instances. My server bill is practically non-existent and I'm serving up crazy volumes without effort.
I had fun writing it, learned some new stuff along the way, and ended up with an API that could serve 80K RPS (according to the venerable ab command) on my laptop with almost no optimization effort. I will absolutely reach for Rust+Actix again for my next project.
(And I found, fixed, and PR’d a bug in a popular rate limiter, so I got to play in the broader Rust ecosystem along the way. It was a fun project!)
I would definitely disagree with this after building a micro service (url shortener) in rust. Rust requires you to rethink your design in unique ways, so that you generally cant do things in the 'dumbest way possible' as your v1. I found myself really having to rework my design-brain to fit rusts model to please the compiler.
Maybe once that relearning has occurred you can move faster, but it definitely took a lot longer to write an extremely simple service than I would have liked. And scaling that to a full api application would likely be even slower.
Caveat that this was years ago right when actix 2 was coming out I believe, so the framework was in a high amount of flux in addition to needing to get my head around rust itself.
This has been my experience. I have about a year of rust experience under my belt, working with an existing codebase (~50K loc). I started writing the toy/throwaway programs i normally write, now in rust instead of go halfway through this stretch. Hard to say when it clicked, maybe about 7-8 months through this experience, so that i didn't struggle with the structure of the program and the fights with the borrow checker, but it did to the point where i don't really have to think about it much anymore.
What is it about Rust that makes it so appealing to people to use for web backend development? From what I can tell, one of the selling points of Rust is its borrow checker/lifetime management system. But if you're making a web backend, then you really only need to care about two lifetimes: the lifetime of the program, and the lifetime of a given request/response. If you want to write a web backend in C, then it's not too difficult to set up a simple system that makes a temporary memory arena for each request/response, and, once the response is sent, marks this memory for reuse (and probably zeroes it, for maximum security), instead of freeing it.
Again, I don't really have any experience with Rust whatsoever, but how does the borrow checker/lifetime system help you with this? It seems to me (as a naïve, outside observer) that these language features would get in the way more than they would help.
> Again, I don't really have any experience with Rust whatsoever, but how does the borrow checker/lifetime system help you with this? It seems to me (as a naïve, outside observer) that these language features would get in the way more than they would help.
You're absolutely right that the borrow checker would get in the way. But it's mostly irrelevant in Rust web development. Backend request flow code almost never shares references or changes ownership, so you don't need to think about ownership much in Rust webdev. And since most of the time Rust can infer the lifetimes of variables, you can almost entirely ignore the system and not even annotate lifetimes in your types.
So what you are left with is a language with an incredible type system, extremely modern semantics and ergonomics, zero cost functional abstractions that have no overhead, trait-based OO instead of classes, sum types (Rust enums) and fantastic syntax around matching [1], option and result types (themselves sum types) with fantastic ergonomics, syntax and error handling designed to result in fewer defects in your code, incredible package manager, incredible build system, single binary build targets, the best compiler error messages and lints in the world currently, cross compilation for a wide variety of systems, bare metal performance with no garbage collection.
It's a phenomenal language and offers so much.
And it's insane that you get bare metal / C performance in web code without even having to think about it.
Rust never set out to be a backend web development language, but because the borrow checker disappears when doing web development, you get so many free things from the language that you don't have to pay for. This post [2] explains it pretty well.
[1] One of the best things about the language
Don't you end up paying for it with compile times? Because the borrow checker has to check all your lifetime annotations and do a bunch of work, just to come to the conclusion that your simple two-lifetime (or whatever) setup is in fact valid?
This metric doesn't convey any meaningful information. Performance metrics need context of the type of work completed and server resources used.
Oh jeez, hard disagree. I absolutely love Rust, but spinning up something in Flask is so so so much easier than in Rust (warp and axum are where I have experience). Certainly some of this is just a part of the learning curve of figuring out a Rust crate you haven't used before. But still, I don't think it's credible that Rust web development is just as productive as the others you mention.
> Safety : The code you write in a Rust NIF should never be able to crash the BEAM.
I tried to find some documentation stating how it works but couldn't. I think they use a dirty scheduler, and catch panics at the boundaries or something? wasn't able to find a clear reference.
Super surprised that shelling out was nearly as good any any other method.
Why is the average bytes smaller? Shouldn't it be the same size file? And if not, it's a different alorithm so not necessarily better?
The content being encoded in the PNG was different ("https://www.reddit.com/r/rustjerk/top/?t=all" for the first, "https://youtu.be/cE0wfjsybIQ?t=74" for the second example - not sure whether the benchmark used different things?), so I'd expect the PNG buffer pixels to be different between those two images and thus the compressed image size to be a bit different, even if the compression levels of DEFLATE within the PNG were the same).
[0]: https://github.com/pretzelhammer/using-rust-in-non-rust-serv...
[1]: https://zlib.net/manual.html#Advanced:~:text=The%20strategy%...
[2]: https://github.com/rust-lang/flate2-rs/blob/1a28821dc116dac1...
Edit: Nevermind. If you look at the actual generated files, they're 594 and 577 bytes respectively. This is mostly HTTP headers.
[3]: https://github.com/pretzelhammer/rust-blog/blob/master/asset...
[4]: https://github.com/pretzelhammer/rust-blog/blob/master/asset...
It may be just additional HTTP headers added to the response, but then it's hardly fair to use that as a point of comparison and treat smaller as "better".
Average response size also halved from 1506 bytes to 778 bytes, the compression algo in the Rust library must be better than the one in the JS library
This would entail zero network hops, probably 100,000+ QRs per second.
IF it is 100,000+ QRs per second, isn't most of the thing we're measuring here dominated by network calls?
Not a bad idea for an internal office network where every computer is hooked up with a gigabit or better, but not great for cloud hosted web applications.
The article is mostly about exemplifying the various leve of optimisation you can get by moving “hot code paths” to native code (irrespective whether you write that code in rust/c++/c.
Worth noting that if you’re optimising for memory usage, rust (or some other native code) might not help you very much until you throw away your whole codebase, which might not be always feasible.
But assuming that the hypothetical C and C++ versions would be using generators and compressors of similar quality, it performance characteristics should be similar.
The big plus(es) to using Rust over C/C++ are a) the C and C++ versions would not be memory-safe, and b) it looks like Rust's WASM tooling (if that's the approach you were to use) is excellent.
(As someone who has written C code for more than 20 years, and used to write older-standard C++ code, I would never ever write an internet-facing server in either of those languages. But I would feel just as confident about the security properties of my Rust code as I would for my Java code.)
It runs on any JVM and has a couple flavors of "ahead-of-time" bytecode compilation.
I didn’t notice this on the front page, what JVM versions is this compatible with?
On Linux, fork() is actually reasonably fast, and if you're exec()ing a binary that's fairly small and doesn't need to do a lot of shared library loading, relocations, or initialization, that part of the cost is also fairly low (for a Rust program, this will usually be the case, as they are mostly-statically-linked). Won't be as low as crossing a FFI boundary in the same process (or not having a FFI boundary and doing it all in the same process) of course, but it's not as bad as you might think.