Hacker News new | ask | show | jobs
by xyst 605 days ago
In my opinion, the significant drop in memory footprint is truly underrated (13 MB vs 1300 MB). If everybody cared about optimizing for efficiency and performance, the cost of computing wouldn’t be so burdensome.

Even self-hosting on an rpi becomes viable.

8 comments

It's the result of the data isolation above anything else attitude of Javascript.

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.)

I rewrote the same web API in Javascript, Rust, C#, and Java as a "bench project" at work one time. The Rust version had smallest memory footprint by far as well as the best performance. So, no, "any other language" [than JS] is not all the same.
C# and Java are closer but not really on the level of Rust when it comes to performance. A better comparison would be with C++ or a similarly low-level language.

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.

> A better comparison would be with C++ or a similarly low-level language.

Right, but then I'd have to write C++. Shallow dismissal aside (I really do not enjoy writing C++), the bigger issue is safety: I am almost certain to write several exploitable bugs in a language like C++ were I to use it to build an internet-facing web app. The likelihood of that happening with Rust, Java, C#, or any other memory-safe language is much lower. Sure, logic errors can result in security issues too, and no language can save you from those, but that's in part the point: when it comes to the possibility of logic errors, we're in "all things being equal" territory. When it comes to memory safety, we very much are not.

So that pretty much leaves me with Rust, if I've decided that the memory footprint or performance of Java or C# isn't sufficient for my needs. (Or something like Go, but I personally do not enjoy writing Go, so I wouldn't choose it.)

> Everything is a scale and faster does not necessarily mean better if the code becomes unreadable.

True, but unreadable-over-time has not been my experience with Rust. You can write some very plain-vanilla, not-"cleverly"-optimized code in Rust, and still have great performance characteristics. If I ever have to drop into 'unsafe' in a Rust code base for something like a web app, most likely I'm doing it wrong.

Rust provides better tools to handle logic errors as well. Sum types/exhaustive pattern matching, affine typing, and mutability xor aliasing let you model many kinds of real-world logical constraints within the type system. (And not just theoretically -- the teams and projects I work on use them every day to ship software with fewer bugs than ever.)
I'd even argue that idiomatic Rust is less prone to those "logic errors" than C++ and the language design gives you fewer chances to trip over yourself.

Even the basics, nobody is calling Rust's [T]::sort_unstable without knowing it is an unstable sort. Even if you've no idea what "stability" means in this context you are cued to go find out. But in C++ that is just called "sort". Hope you don't mind that it's unstable...

[Edited because I can't remember the correct order of words apparently.]

> when it comes to the possibility of logic errors, we're in "all things being equal" territory. When it comes to memory safety, we very much are not.

Very well summed. I'll remember this exact quote. Thank you.

My goal with the project was to compare higher performance memory safe languages to Javascript in terms of memory footprint, throughput, latency, as well as the difficulty of implementation. Rust was, relatively speaking, slightly more difficult: because concurrently manipulated data needed to be explicitly wrapped in a mutex, and transforming arbitrary JSON structures (which was what one of the endpoints did) was slightly more complex than in the others. But, overall, even the endpoints that I thought might be tricky in Rust weren't really what I'd call difficult to implement, and it wasn't difficult to read either. It seemed worth the trade-off to me and I regret not having more opportunities to work with it professionally in the time since.
C and Fortran are not faster than C++, and haven't been for a long time. I've used all three languages in high-performance contexts. In practice, C++ currently produces the fastest code of high-level languages.
Because C++ doesn't restrict aliasing there are a bunch of cases where it's just unavoidably worse. The compiler is obliged to assume that if there are potentially aliasing objects of type T: T1 and T2 then mutating T1 might also mutate T2 (because it may be an alias), so therefore we must re-fetch T2.
That is more theory than reality in high-performance code, and was noted as such even back when I was in HPC. The compiler isn’t stupid and normal idiomatic high-performance code in C++ has codegen that is essentially indistinguishable from the FORTRAN in virtually all cases. It has been a couple decades and many compiler versions since anyone had to worry about this. One of the things that killed the use of FORTRAN in HPC is that it empirically did not produce code that was any faster than C++ in practice and was much more difficult to maintain. Advantage lost.

The extensive compile-time metaprogramming facilities in C++ give it unique performance advantages relative to other performance languages, and is the reason it tends to be faster in practice.

Generally, the reason C++ is so stupidly fast compared to even C is because a lot is pushed to compile-time via templates. You can avoid passing pointers, doing indirection, and you can even inline functions altogether. Flattening objects and methods to encode as much information as you can in the type at compile-time will almost always be much faster than doing dynamic redirection at runtime.

For example, compare the speed and implementation of std::sort and qsort (it's almost an order of magnitude difference in run time for big N!)

I have written and worked on more than my fair share of Rust web servers, and the code is more than readable. This typically isn't the kind of Rust where you're managing lifetimes and type annotations so heavily.
> A better comparison would be with C++ or a similarly low-level language.

You probably want the apples-to-apples comparison but this looks an artificially limiting comparison; people are shilling, ahem, sorry, advocating for their languages in most areas, especially web / API servers. If somebody is making grandiose claims about their pet language then it's very fair to slap them with C++ or Rust or anything else that's actually mega ultra fast.

So there's no "better" comparison here. It's a fair game to compare everything to everything if people use all languages for the same kinds of tasks. And they do.

C# and Java are languages with very different performance ceilings and techniques available for memory management.
They are not saying every language will have same level of improvement as Rust, they are saying you can most of the improvements is available in most languages.

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

Sure, but until people actually have real data that’s just supposition. If a Java rewrite went from 1300MB to, say, 500MB they’d have a valid point and optimizing for RAM consumption is severely contrary to mainstream Java culture.
I’m curious how Go stacks up against C# and Java these days.

“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?

Go compiler is by far the weakest among those three. GC pause time is a little lie that leaves the allocation throttling, pause frequency and write barrier cost out of the picture. Go works quite well within its intended happy path but regresses massively under heavier allocation traffic in a way that just doesn’t happen in .NET or OpenJDK GC implementations.
You also have to think about your target audience.

Are you hiring developers that are 100% fully conscious of concurrency and starvation or people that are only concerned with rest and vest and TC?

For either case Go is better.

* For people that are aware of concurrency, they will select Go because they appreciate its out-of-the-box preemptive concurrency model with work stealing.

* For people that are not aware of concurrency, then you should definitely use Go because they are not qualified to safely use anything else.

That’s why I specifically qualified my comment “within the context of a typical crud api server”.

I remember this being true 10 years ago. Java web servers I maintained had a huge problem with tail latency. Maybe if you were working on a 1 qps service it didn’t matter. But for those of us working on high qps systems, this was a huge problem.

But like I said, I know the Java people have put a ton of work in to try to close the gap with Go. So maybe this isn’t true anymore.

You can't compare 10 years ago Java to current Go. 10 years ago was Java 8, we are currently on Java 23. The performance difference is massive between these 2 runtimes especially between the available garbage collectors.

Hazelcast has a good blog [0] on their benchmarks between 8 and some of the more modern runtimes, here is one of their conclusions:

> JDK 8 is an antiquated runtime. The default Parallel collector enters huge Full GC pauses and the G1, although having less frequent Full GCs, is stuck in an old version that uses just one thread to perform it, resulting in even longer pauses. Even on a moderate heap of 12 GB, the pauses were exceeding 20 seconds for Parallel and a full minute for G1. The ConcurrentMarkSweep collector is strictly worse than G1 in all scenarios, and its failure mode are multi-minute Full GC pause

[0] https://hazelcast.com/blog/performance-of-modern-java-on-dat...

Typical CRUD API server is going to do quite a few allocations, maybe use the "default" (underwhelming) gRPC implementation to call third-parties and query a DB (not to mention way worse state of ORMs in Go). It's an old topic.

Go tends to perform better at "leaner" microservices, but if you are judging this only by comparing it to the state of Java many years ago, ignoring numerous alternative stacks, it's going to be a completely unproductive way to look at the situation. Let's not move the goalposts.

Depends if you're measuring .net as written by members of the core team with all the tricks and hacks or .net as written by everyone else
> “Less languages features, but a better compiler” was originally the aspirational selling point of Go.

A faster compiler was the aspirational selling point. As legend has it, Go was conceived while waiting for a C++ program to compile.

Before what was called "Go 2" transitioned the project away from Google and into community direction there was some talk of adding no more features, instead focusing on improving the compiler... But since the community transition took place, the community has shown that they'd rather have new features.

The "Go 1" project is no longer with us (at least publicly; perhaps it lives on inside Google?)

One of the big draws of go is ease of deployment. A single self contained binary is easy to package and ship, especially with containers.

I don’t think Java has any edge when it comes to deployment.

Java AOT has come a long way, and is not so rare as it used to be. Native binaries with GraalVM AOT are becoming more a common way to ship CLI tools written in JVM languages.
Native image continues to be relegated to a “niche” scenario with very few accommodations from the wider Java ecosystem.

This contrasts significantly with effort and adoption of NativeAOT in .NET. Well, besides CLI, scenarios where it shines aren’t those which Go is capable of addressing properly in the first place like GUI applications.

It's hard to compare Rust or C++ to GC langs like C# and Java because their runtimes are greedy. The CLR will easily take 10x more memory than it's currently using such that future allocations are much, much faster. So measuring the memory consumption of a JVM/CLR application is not simple. You need to ask the GC how much memory you're actually using - you can't just check the task monitor.

Also you can do that same thing in Rust or C++ too. Very common in C++, speeds up programs quite a bit.

> The CLR will easily take 10x more memory

CoreCLR itself doesn't take much memory - GC might decide on a large heap size however. Do give .NET 9 a try with Server GC which has enabled DATAS by default. It prioritizes smaller memory footprint much more heavily and uses a much more advanced tuning algorithm to balance out memory consumption, allocation throughput and % of time spent in GC.

Your claim makes zero sense to me. Particularly when I've personally seen similar behavior out of other languages, like Java.

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.

"Rust" really just means "Not javascript" as a recurring pattern in these articles.
Not exactly. It wouldn't help if you moved your JavaScript to Python or Ruby or PHP... and anyway it's not really feasible from an FFI perspective to move it to anything other than Rust or C/C++ or maybe Zig. There's no good reason to pick C/C++ over Rust in most of these cases...

So "Rust" means "Not JavaScript, and also a bunch of other constraints that mean that Rust is pretty much the only sensible choice."

> It wouldn't help if you moved your JavaScript to Python or Ruby or PHP...

Hum, no. The point is exactly that it would help a great deal if you moved to Python or Ruby or PHP.

Of course, Rust will give you even better memory efficiency. But Javascript is a particularly bad option there, and almost anything else would be an improvement. ("Almost", because if you push it enough and move to something like MathLab, you'll get worse results.)

If moving from JS to CPython would help, it might help memory consumption, because JITs generally trade speed for increased memory. But then you'd get slower execution, because CPython is slower than the JS engines we tend to use. PyPy might generally track JS on performance (big, BIG "it depends" because the speed profile of JITs are crazy complicated, one of my least favorite things about them) but then you're back to trading memory for speed, so it's probably net-net a sideways move.

Also, I don't know what Node is doing exactly, but if you take a lot of these dynamic languages and just fork them into multiple processes, which they still largely need to do to effectively use all the CPUs, you will generally see high per-process memory consumption just like Node. Any memory page that has a reference counter in it that is used by your code ends up Copied-On-Write in practice by every process in the steady state because all you need to do to end up copying the page is looking at any one reference it happens to contain in such a language. At least in my experience memory sharing gains were always minimal to effectively zero in such cases.

> But then you'd get slower execution, because CPython is slower than the JS engines we tend to use

I have not found this to be generally true. It depends heavily on whether your code is limited by pure high level language code[1] and culture makes comparisons harder if you’re not just switching languages but also abstraction models and a big stack of optimizations. In theory Java beats Python but in practice I’ve seen multiple times where a Java program was replaced by Python seeing whole number multiple improvements in performance and reductions in memory consumption because what was really happening is that a bunch of super complicated, optimization-resistant Java framework code was being replaced with much simpler code which was easier to optimize. Node is closer to that side of Java culturally, I think in both cases because people reacted to the limited language functionality by building tons of abstractions which are still there even after the languages improved so even though it’s possible to do much better a lot of programmers are still pushing around a lot of code with 2000s-era workarounds buried in the middle.

1. I’m thinking of someone I saw spend months trying to beat Python in Go and eking out a 10% edge because the bulk of the work devolved to stdlib C code.

It depends, of course, on what you're doing. Re-using the toy web API in the article, I expect Python would be significantly faster. The QR code library you'd end up using in Python is probably written in C, and the web-serving portion should have comparable performance characteristics as you'd get with nodejs.

My guess is that if you were to rewrite this same app in straight Python (no Rust at all), it would probably already give you "Tier 3" performance.

But sure, I bet there are a bunch of use cases where nodejs would be faster than Python.

This seems a bit unfair to JavaScript. There’s a lot of optimizations made to the language and its runtimes that have made a more than viable choice for server side applications over the years. The JavaScript that started as a Webbrowser client side language is very different from the ECMAScript that we have today. Depending on its usage it can also be one of the fastest, only regularly eclipsed by rust[1]. So no, JavaScript really isn’t a bad option for server side applications at all.

[1] https://www.techempower.com/benchmarks/#hw=ph&test=composite...

Not sure about Python or Ruby, but PHP is definitely MUCH faster. It helps a lot when a ton of the code is in C modules (which I guess is maybe the case for Python too?)
> There's no good reason to pick C/C++ over Rust in most of these cases...

What leads you to believe in that?

Because except in rare cases Rust can do everything C++ can do with basically the same performance profile, but it does it with modern tooling and without the security, reliability and productivity issues associated with C++'s pervasive Undefined Behaviour.

There are some cases where C++ makes sense:

* You have a large existing C++ codebase you need to talk to via a large API surface (C++/Rust FFI is not great)

* You have a C++ library that's core to your project and doesn't have a good Rust alternative (i.e. Qt)

* You don't like learning (and are therefore in completely the wrong industry!)

The constant stream of CVEs caused by even experts failing to use those languages correctly on the one side, and the much better developer experience on the other. C++ isn’t horrible but it’s harder to use, harder to find good developers, and there are relatively few cases where there’s something easier to do in C++ than Rust which would warrant picking it. In most cases, it’ll be both faster and safer if you use a modern language with good tooling instead and take advantage of the easy C bindings if there’s a particular library you need.
> The constant stream of CVEs (...)

It's a function of popularity and widespread use. The only languages that do not feature CVEs are the ones that are not used.

Eve Rust started to feature in CVEs, including memory safety problems in it's standard library. Somehow that fact is omitted from these discussions.

> (...) even experts failing to use those languages correctly (...)

I couldn't help noticing you felt the need to resort to weasel words like "correctly" to add color to an unsubstantiated personal assertion.

What's the best example you can come up with to support your opinion?

> C++ isn’t horrible but it’s harder to use, harder to find good developers (...)

This personal assertion is comical, as recruiters are systematically targeting C++ developers for Rust positions, and Rust is notoriously bad for newbies to onboard onto.

I'd prefer these debates were kept at an objective and substantiated level, but it seems that's too much to ask. It seems it's easier to throw unsubstantiated claims around and wait to see if half the bullshit sticks.

I’m not a big believer in absolutes like that, but unless a person is already proficient in C or C++, or there’s an existing C++ library, etc., I find it hard to justify using those over Rust. Rust has great tooling, good cross compilation support, good quality standard library and very good 3rd party ecosystem.

Also, it has so few footguns compared to C or C++ even modestly experienced developers can safely use it.

A host of a prominent C++ podcast expressed more or less this sentiment recently (on an ep within the last year). He was being a little bit "devil's advocate", and not suggesting stopping working with C++ altogether. But he could see most use cases of C++ being well satisfied by Rust, and with more ergonomic features like Cargo making the overall experience less of a chore.
It's also frankly kinda like comparing apples and oranges as a language. JavaScript (and many of the "bad performance" high level languages minus Rails; Rails is bad and should be avoided for projects as much as possible unless you have lots of legacy cruft) are also heavily designed around rapid iteration. Rust is however very much not capable of rapid iteration, the borrow checker will fight you heavily every step of the way to the point where it demands constant refactors.

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.

I found this to be untrue after I spent a little energy learning to think about problems in rust.

In a lot of languages you're working with a hammer and nail (metaphorically speaking) and when you move to a different language its just a slightly different hammer and nail. Rust is a screwdriver and screw though, and once I stopped trying to pound the screw in with the screwdriver, but rather use the one to turn the other, it was a lot easier. Greenfield projects with a lot of iteration are just as fast as doing it in python (although a bit more front-loaded rather than debugging), working new features into existing code - same thing.

I have often thought that programmers can actually just choose to make Rust easy by using a cyclic garbage collector such as Samsara. [1] If cyclic GC in Rust works as well as I think it can, it should be the best option for the majority of high level projects that need fast development with a trade-off of slightly lower efficiency. I suspect we'll see a "hockey stick" adoption curve once everyone figures this out.

[1] https://github.com/chc4/samsara

I am still waiting for a scripting language to be bolted on top of Rust. Something that will silently Box all the values so the programmer does not have to think about the Rust specifics, but can still lean on all of the Rust machinery and libraries. If performance/correctness becomes a problem, the scripting layer could be replaced piecemeal with real Rust.
Perhaps you mean to say that you're waiting for a new scripting language to be created that's designed to be "almost Rust." That could be interesting! OTOH, the bindings for existing languages have matured significantly:

  - https://pyo3.rs/
  - https://github.com/neon-bindings/neon
  - https://github.com/mre/rust-language-bindings
And then we would’ve come full circle.

Beautiful

This is what async/await rust programmers need

They are comfortable with runtimes

> Rust is however very much not capable of rapid iteration, the borrow checker will fight you heavily every step of the way to the point where it demands constant refactors.

Misconception.

You will encounter the borrow checker almost never when writing backend web code in Rust. You only encounter it the first time when you're learning how to write backend code in Rust. Once you've gotten used to it, you will literally never hit it.

Sometimes when I write super advanced endpoints that mutate global state or leverage worker threads I'll encounter it. But I'm intentionally doing stuff I could never do in Python or Javascript. Stuff like tabulating running statistics on health check information, batching up information to send to analytics services, maintaining in-memory caches that talk to other workers, etc.

To put this another way: the Rust borrow checker attempts to tie memory lifetime to stack frames.

This tends to work well for most crud api servers, since you allocate “context, request, and response” data at the start of the handler function, and deallocate at the end. Most helper data can also be tied to the request lifecycle. And data is mainly isolated per-request. Meaning there isn’t much data sharing across multiple request.

This means that the borrow checker “just works”, and you probably won’t even need lifetime annotations or even any special instructions for the borrow checkers. It’s the idealized use case the borrow checker was designed for.

This is also the property which most GC languages like Java, Go, and C# exploit with generational garbage collectors. The reason it “works” in Java happens to be the same reason it works in Rust.

If your server does need some shared in-memory data, you can start by just handing out copies. If you truly need something more complicated, and we are talking about less than 10% of crud api servers here, then you need to know a thing or two about the borrow checker.

I’m not saying to rewrite web servers in Rust, or even advocating for it as a language. I’m just pointing out that a crud api server is the idealized use case for a borrow checker.

Incredibly well said. This is precisely what makes it work so well.

The language never set out to solve this problem. It wasn't an intentional design goal. The language design and problem space just happen to overlap more or less perfectly.

Complete serendipity.

> the borrow checker will fight you heavily every step of the way to the point where it demands constant refactors.

No

Once you learn to surrender to the borrow checker it becomes friend, not foe

You must submit

You've described Rust's niche circumspectly: Systems, which have strict requirements and are fragile minefields.

The performance benefits of Rust were supposed to be a non-penalty: "Look, you can please please use this where you'd use C or C++ I promise it won't impact your performance!" Performance and GC overhead was the rejection de jure of every other C replacement.

But here we are: All friends are javascript front-enders-turned-backenders and are wondering if they should pick up Rust. It fits into a pattern of "new shiny" but it's good, don't get me wrong, if everyone experiences compiled languages and starts writing their headless code in sensible languages.

Repeating myself, but I'm just wondering why not Go? Why now?

>Rust is however very much not capable of rapid iteration, the borrow checker will fight you heavily every step of the way to the point where it demands constant refactors.

If you have sufficient experience, that's not really the case. Certainly compared to "comparable" languages like C++ where that time fighting the borrow checker might instead have been spent chasing random crashes.

I have written both professionally for long enough to say that there's not real comparable advantage to either. You trade one complexity to fight for another when refactoring or iterating.
I don't think this is very true. Certainly, you can rapidly iterate in C#, and it's so much faster it's not even close.

But, if you want a dynamically typed experience, look no further than PHP or Perl. Also significantly faster, and, if I had to bet, you could probably iterate much faster in Perl. It wouldn't be fun, but honestly, I doubt that Perl is more footgunny than JS.

Writing server API'n'co is not unknown path that needs rapid prototyping.
There is no reason data isolation should cost you 100x memory usage.
> There is no reason data isolation should cost you 100x memory usage.

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.

> That critic would be manifesting his ignorance, because there is absolutely nothing to gain by lowering the memory budget.

Given that compute is often priced proportional to (maximum) memory usage, there is potentially a lot to be gained: dramatically cheaper hosting costs. Of course if your hosting costs are small to be begin with then this likely isn't worthwhile.

> Given that compute is often priced proportional to (maximum) memory usage, (...)

Let's look at numbers.

Hetzner sells vCPUs with 4GB of RAM for less than 5$/month, and 8GB of RAM for less than $10/month.

https://www.hetzner.com/cloud/

In my example, the cost of having garbage collection generation 0 events triggering twice a year would be an extra $5. If I wanted the frequency of these events to double, in theory I would save perhaps $2/month.

If I ran a web-scale service with 10 times the nodes as-is, we're talking about a $50/month price tag difference.

How much does a company charge for an engineer's hourly labor? How many years would it take to recover the cost of having an engineer tune a service's garbage collection strategy?

People need to thing things through before discussing technical merits.

> That critic would be manifesting his ignorance, because there is absolutely nothing to gain by lowering the memory budget.

Well, that depends on information you haven't provided. Maybe your system does have an extra 900 MB of memory hanging around; I've certainly seem systems where the minimum provisionable memory[1] is more than what the system will use for program memory + a full cache of the disk. If that's the case, then yeah, there's nothing to gain. In most systems though, 900 MB of free memory could go towards caching more things from disk, or larger network buffers, or something more than absolutely nothing.

Even with all that, lowering your memory budget might mean more of your working memory fits in L1/L2/L3 cache, which could be a gain, although probably pretty small, since garbage isn't usually accessed. Absolutely nothing is a pretty low barrier though, so I'm sure we could measure something. Probably not worth the engineering cost though.

There are also environments where you can get rather cheap freeing by setting up your garbage to be easily collected. PHP does a per-request garbage collection by (more or less) resetting to the pre-request state after the request is finished; this avoids accumulating garbage across requests, without spending a lot of effort on analysis. An Erlang system that spawns short lived BEAM processes to handle requests can drop the process heap in one fell swoop when the process dies; if you configure the initial heap size so no GCs are triggered during the lifetime of the process, there's very little processing overhead. If something like that fits your environment and model, it can keep your memory usage lower without a lot of cost.

[1] Clouds usually have a minimum memory per vCPU; if you need a lot of CPUs and not a lot of memory, too bad. I don't think you can buy DDR4 SIMMs of less than 4GB, or DDR5 of less than 8GB. Etc

> Well, that depends on information you haven't provided. Maybe your system does have an extra 900 MB of memory hanging around;

That's not how it works. You cannot make sweeping statements over how something is bad when you fail to consider how it's used and what are the actual real world constraints.

For example, you're arguing that minimizing memory consumption is somehow desirable, and if you're making that claim you need to actually make a case. I clearly refuted your point by clarifying how things work in the real world. If you feel you can come up with a corner case that refutes it, just do it. So far you haven't, but that didn't stopped you from making sweeping statements.

There are plenty of reasons. They are just not intrinsic to the isolation, instead they come from complications rooted deeply on the underlying system.

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).

Unless you can be concrete and specific about some of those reasons, you're just replacing handwaving with more vigorous handwaving.

What is it specifically about JavaScript's implementation of data isolation that, in your mind, helps cause the excessive memory usage?

Just a day or two ago, there was an article here about problems implementing a kind of read-only memory constraint that Javacript benefited from in other OSes.
I must have missed that article. Can you find it?

Unless you can come up with a specific reference, it seems unlikely that this would explain the large memory efficiency difference. By contrast it is simple and straightforward to understand why keeping temporary garbage until garbage collection could result in tying up a lot of memory while continually running code that allocates memory and lets it go out of scope. If you search, you'll find lots of references to this happening in a variety of languages.

> Or, in other words, it's the unavoidable result of insisting on using a language created for the frontend to write everything else.

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?

> The primary selling point is what makes Node.js take over half the world: it's async architecture.

It is the availability of the developers who know the language (JavaScript) (aka cheaper available workforce).

I disagree, it's 100% to do with the frontend and pretty much only because of that.

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.

Rust has had async for a while (though it can be painful, but I think request/response systems like APIs should not run into a lot of the major footguns).

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.

It's important to be aware that often it isn't the programming language that has the biggest effect on memory usage, but simply settings of the memory allocator and OS behaviour.

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...

Why does no one ever talk about this? It is so weird to see a memory pissing match with no context like this. Thank you
Because people don't know.

That includes users of low-level languages. They assume free() means free when it doesn't.

And assumption- and hope-driven development are less bothersome to the mind!

It's annoying to have to fact-check every sane assumption, but unfortunately it's required. Of course for anything that exists, somebody somewhere built a cache around it for average-case performance gains that destroys simplicity and adds pathological edge cases.

Most people learn this only when they try to run a real-world system that inexplicably runs out of RAM, or if they see unreasonably large number and actually start digging instead of just accepting it.

If every developer cared for optimizing efficiency and performance, development would become slower and more expensive though. People don’t write bad-performing code because it’s fun but because it’s easier. If hardware is cheap enough, it can be advantageous to quickly write slow code and get a big server instead of spending days optimizing it to save $100 on servers. When scaling up, the tradeoff has to be reconsidered of course.
We all should think about optimization and performance all the time and make a conscious decision of doing or not doing it given a time constraint and what level of performance we want.

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.

It is even worse for widely deployed applications. To pick on some favorites, Microsoft Teams and One Drive have lousy performance and burn up a ton of cpu. Both are deployed to tens/hundreds of millions of consumers, squandering battery life and electricity usage globally. Even a tiny performance improvement could lead to a fractional reduction in global energy use.
I wish they would do this. But my experience is that building efficient software is hard, and is very very hard the larger the team gets or the longer the product exsits.

Even zoom, used to be very efficient, but has gradually got worse over time :-(

I would find this more compelling if we were not discussing a trillion dollar company that employs tens of thousands of programmers. The One Drive performance is so bad I cannot imagine anyone has put any effort into prioritizing efficiency. Naive, first effort attempt was packaged up and never revisited.
While that is true, its really not easy to do without re-writing from scratch and scrapping a load of features which is organisationally difficult to do.

What large piece of software with a user interface do you work with that is actually fast and stays fast? For me, its probably just Chrome / Firefox. Everything else seems to get slower over time.

I doubt that it would be good business for Microsoft though. The people who use them, and the people who buy them and force others to use them are two separate groups, and anyone who cares even a bit about user experience and has power to make the decision has already switched to something different. It's also the users, not Microsoft who pays for the wasted power and lost productivity.
Strongly disagree with this sentiment. Our jobs are typically to write software in a way that minimizes risk and best ensures the success of the project.

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.

I see it all the time, applications that would be very usable and streamlined for users from a ui perspective are frustrating and painful to use because every action requires a multi second request. So the experience is mostly reduced to staring at progress spinners.
> every action requires a multi second request

This is doing a lot of heavy lifting. Just because an app is slow doesn't mean Rust would've made it faster. It may just be slow because of a bad query or otherwise poor architecture, especially in web development. The commenter is asking what projects you've seen fail because the language itself hit a performance limit that couldn't be worked around.

this honestly sounds like you're describing the most successful software on the market. I can't think of many social media sites slower than facebook or instagram, or chat slower than slack or email clients slower than gmail.
Sure but it seems like race to the bottom. Faster development will beat better quality in the market. Especially in unregulated industry like this.
I'm not so sure this "race to the bottom" is a characteristic I want to avoid. The most competitive markets in the world are always in first world countries or in countries quickly turning into a first world country. Take south korea for example. They had sweatshops maybe 70 years ago and now they arguably have the best healthcare in system in the world.

I'll take a fast food job for 40 hours any day of the week over most of the options in poor countries. Sure, nothing but the best, fulfilling jobs for everyone is ideal, but until I see that exist I'm not informed enough to know whether it's possible outside of someones political ideals.

It also depends on where the code is running. To put it simply; nobody cares how much RAM the server is using, but they do care if their clientside application isn't responsive. UI being performant and responsive should have priority over everything else.
Worse even: it's super bad for the environment
Are you sure? Is my one PHP server running with 50% more electricity gonna outweigh the 12 developers with beefed rigs just to get Rust compile times somewhere reasonable? Or how much longer they will be using their computers because it will take longer to write the code itself? Especially when I have 1000 monthly users and a $6 VPS is more than enough anyway?

This has always been a poor argument.

We have Electron and we don't get rid of it for a decade, at least.
I'm not so sure. I use Rust for simple web services now, when I would have used Python or JS/TS before, and the development speed isn't much different. The main draw is the language/type system/borrow checker, and reduced memory/compute usage is a nice bonus.
Which framework? Do you write sync or async? I’ve AoC’d rust and really liked it but async seems a bit much.
Not the other poster but I moved from Go to Rust and the main packages I use for web services are axum, askama, serde and sqlx. Tokio and the futures crate are fleshed out enough now that I rarely run into async issues.
That's pretty much where I'm at, plus a few basic packages for auth, caching, job/queue stuff. I can't remember the last time I had to care about async, but it does occasionally come up when dealing with things like background tasks.

I'm not totally happy with sqlx and the logging situation, but most issues that come up are the "solve once and never worry about it again" type.

I have to agree, despite using it a lot, async is the worst part of Rust.

If I had to do some of my projects over again, I'd probably just stick with synchronous Rust and thread pools.

The concept of async isn't that bad, but it's implementation in Rust feels rushed and incomplete.

For a language that puts so much emphasis on compile time checks to avoid runtime footguns, it's way too easy to clog the async runtime with blocking calls and not realize it.

If he was OK with python performance limitations the rust without async is more then enough
Code is usually ran many more times than it is written. It's usually worth spending a bit of extra time to do something the right way the first time when you can avoid having to rewrite it under pressure only after costs have ballooned. This is proven time and time again, especially in places where inefficient code can be so easily identified upfront.
Not all code is run high enough times for that trade off to be always justified.

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

> Not all code is run high enough times for that trade off to be always justified

If you're running a web server, it definitely is. Compute and memory literally translate into money.

Not if you don't have users, that is the point.

Vast majority of applications both in enterprise and consumers space that get built do not close to even say 10k monthly active users.

You can stick several of those apps in crappy PHP or NodeJS code on to the cheapest $50 / month VPS and nobody will know the difference ,or do it even cheaper on run them on serverless stacks such as firebase, lambda etc.

There is a threshold for usage of code under which the significant cost driver is developer time and skill levels needed. 95%+ professional developers never write code that will exceed this threshold.

This economics is what drives

- So much poorly written plugins for CMSes or apps in easy to start languages like PHP or nodeJS,

- No code solutions as diverse as Retool or Shopify get so much revenue and valuations

- Copilot style AI assistance would have a market even when they were not good enough for skilled developers.

This economics works for startups too, all of here use the cloud, and it will keep making sense both technically and economically until we hit perhaps 10s if not 100s of millions of users. We don't care about somebody else DC with network mounted disk with shitty I/O performance or be bothered about paying for metered bandwidth or the language we use.

There are only few pieces of code that truly cross the threshold where cost of performance of infra(RAM, Disk, Memory, Bandwidth, CPU etc) costs are much greater than cost of developer time so it makes sense to optimize it.

Facebook fiddled with Hack run time before optimizing out their PHP stack, Twitter famously rewrote out their ruby stack after they kept hitting limits. Products have to only worry about this problem IF they scale, most don't.

That's a fair point, but I think the fallacy here is that choosing a less-performant stack means it's easier/faster to deliver features. I don't think that's necessarily true.

When Facebook started PHP was a good choice. These days though you'd probably be better off going for Springboot or .NET. These are more performant and much more "batteries included". I would say the same thing goes for Node.

Node.js is... usable. It's certainly not nice to write JS on the backend and you need a LOT of libraries to make it work. Then the problem is they don't all interop perfectly together and you won't have great tooling. I think most people, even startups, would be better off going with a backend framework with good tooling. If you're able to do a bunch of codegen + you have all the batteries, I would imagine developer velocity would be faster.

Caring about efficiency and performance doesn't have to mean spending all your time on it until you've exhausted every possible avenue. Sometimes using the right tools and development stack is enough to make massive gains.

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.

Exactly. Nobody is saying to min-max from the start - just be a bit more thoughtful and use the right tools for the job in general.
Yea but we also write the same software over and over and over and over again. Perhaps slower, more methodical development might enable more software to be written fewer times. (Does not apply to commercially licensed software or services obviously, which is straight waste.)
This is a decent point, but in many cases writing software over again can be a great thing, even in replaceing some very well established software.

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.

> Perhaps slower, more methodical development might enable more software to be written fewer times

I don't see why. People will just discover they rewrote something slower.

Tempted to say it’s more the learning the language that takes longer than the writing it part.

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

Agreed. When a VC backed company is in hyper-growth, and barely has resources to scale up their shaky MVP tech stack so they can support 100+ million users, I doubt anyone thinks its reasonable to give the engineers 6 months to stop and learn Rust just to rewrite already working systems.

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.

That's because you're churning temporary memory. JS can't free it until garbage collection runs. Rust is able to do a lifetime analysis, and knows it can free it immediately.

The same will happen on any function where you're calling functions over and over again that create transient data which later gets discarded.

fwiw, Bun/webkit is much better in mem use if your code is written in a way that avoids creating new strings. it won't be a 100x improvement, but 5x is attainable.
> If everybody cared about optimizing for efficiency and performance

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.

It's a little more nuanced than that of course, a big reason why the memory usage is so high is because Node.JS needs more of it to take advantage of a large multicore machine for compute-intensive tasks.

> 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.

Reducing memory footprint is a big deal for using a VPS as well. Memory is still quite expensive when using cloud computing services.
True that. Having to carefully balance responsiveness and memory usage/OOM risk when setting up PHP-FPM pools definitely makes me grateful when deploying Go and Rust software in production environments.
While I agree with pretty much all you wrote, I'd like to point out that e-mail, out of all the services one could conceivably self-host, is quite resilient to temporary outages. You just need to have another backup mail server somewhere (maybe another self-hosting friend or in a datacenter), and set up your DNS MX records accordingly. The incoming mail will be held there until you are back online, and then forwarded to your primary mail server. Everything transparent to the outside word, no mail gets lost, no errors shown to any outside sender.
> Imagine if all of your e-mails bounced while the power was out.

Retry for a while until the destination becomes reachable again. That's how email was originally designed.

>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.

Another probably even bigger reason people don't self-host email specifically is that practically all email coming from a residential IP is spam from botnets, so email providers routinely block residential IPs.
Yeah, exactly this. The natural disaster in North Carolina is a great example of how I envision this going very badly. When you self-host at home, you just can't have the same kind of redundancy that data centers have.

I don't think it's an obstacle that's absolutely insurmountable, but it feels like something where we would need to organize the entire Internet around solving problems like these. My personal preference would be to have devices act more independently. e.g. It's possible to sync your KeepassXC with SyncThing at which point any node is equal and thus only if you lose all of your devices simultaneously (e.g. including your mobile computer(s)) are you at risk of any serious trouble. (And it's easy to add new devices to back things up if you are especially worried about that.) I would like it if that sort of functionality could be generalized and integrated into software.

For something like e-mail, the only way I can envision this working is if any of your devices could act as a destination in the event of a serious outage. I suspect this would be possible to accomplish to some degree today, but it is probably made a lot harder by two independent problems (IPv4 exhaustion/not having directly routable IPs on devices, mobile devices "roaming" through different IP addresses) which force you to rely on some centralized infrastructure anyways (e.g. something like Tailscale Funnels.)

I for one welcome whoever wants to take on the challenge of making it possible to do reliable, durable self-hosting of all of my services without the pain. I would be an early adopter without question.

There are flags you can set to tune memory usage (notably V8's --max-old-space-size for Node and the --smol flag for Bun). And of course in advanced scenarios you can avoid holding strong references to objects with weak maps, weak sets, and weak refs.
Im ok if it isnt popular. It will keep compute costs lower for those using it as the norm is excessive usage