News for hackers. Stuff that matters.
Re:Changing the Rules of Rust by hedora on Sunday September 17, 2023 @01:39PM (#37547531)
The lack of a Leak-style trait is so painful for me that I’d strongly consider switching to a different language that was basically just rust + Leak. (Adding move and removing pin would be nice to have too.)

The problem with not having Leak is that it forces all futures to be ‘static, which effectively disables the borrow checker whenever you cross an async spawn boundary.

Arc is the standard solution, but, for multi-threaded code, it can be expensive (though also more predictable, in fairness) than having a GC.

The only other solution (which I prefer) is to use an unsafe version of spawn that assumes the future it returns will not be leaked.

This certainly breaks the rust promise of supporting safe, zero cost abstractions.

Re:Changing the Rules of Rust by sabageti on Sunday September 17, 2023 @02:14PM (#37547844)
I have seen many comments like this, and I would like to understand better I feel like I'm missing something. In my daily use case(web api) of async rust we use rarely spawn. What are the use case of intensive spawn usage ?

And there is tokio::spawn_local() that doesn't require Send

Re:Changing the Rules of Rust by scottlamb on Sunday September 17, 2023 @02:44PM (#37548123)
> Arc is the standard solution, but, for multi-threaded code, it can be expensive (though also more predictable, in fairness) than having a GC.

Do you have measurements to back that up? I would expect it to be a lot cheaper than GC because GCed language such as Java/Go/etc. GC every heap allocation (and Java in particular doesn't have unboxed objects, so there are a lot of heap allocations!) where Arc<RequestState> would bundle a bunch of things together.

That said, I'm not in love with the Arc solution either. It's not just the performance impact. The programming model of scoped concurrency is just much more pleasant. Giving up that and then also having to deal with putting everything into an Arc, and likely also then wrapping it in a MutexLock, even when it should really be one thread at a time dealing with the value in question, just doesn't do anything good for readability and writability of code...

Re:Changing the Rules of Rust by charcircuit on Sunday September 17, 2023 @04:59PM (#37549243)
>because GCed language such as Java/Go/etc. GC every heap allocation

GC is not run on every allocation.

Re:Changing the Rules of Rust by o11c on Sunday September 17, 2023 @05:19PM (#37549442)
They don't run GC, but they subject the extra pointers to GC, whereas good RC languages generally support values. Doing nothing is usually cheaper than doing something.

This is more a Java problem than a GC problem; C# supports values just fine, and some heroic JVM-targetting languages also manage it.

Re:Changing the Rules of Rust by manwe150 on Sunday September 17, 2023 @11:48PM (#37552021)
But then mark and sweep also doesn’t have to free the memory (it just does a bulk sweep occasionally), and RC languages do need to free memory used to hold the values later (they have to run expensive compaction and locking steps). So definitely more a Java problem with poor allocation hoisting and reuse than a difference between the RC and mark and sweep implementations of GC.
Re:Changing the Rules of Rust by scottlamb on Monday September 18, 2023 @11:27AM (#37557534)
Rust isn't an "RC" language though like Java is a "GC" language. There's a lot of flexibility for how you handle allocations. It has actual unboxed/value structs; it has Box (heap allocation without reference counting); it has Rc (single-thread/no-atomic reference counting); it has Arc (atomic reference counting); it has epoch gc libraries. And, regarding this phrase:

> RC languages do need to free memory used to hold the values later

It also has arena allocators, so you can group allocations that will be freed at a similar time. You don't need to walk through these to find them all and individually free them. Anything with a Drop impl (destructor) gets thrown on a list for that to be taken care of; then the arena's relatively few, large blocks get freed all at once.

I've used this in C++ to squeeze the last bit of performance out of a high-request-rate server. I haven't done it in Rust, but it's the same idea.

btw, little confused by this:

> [RC languages?] have to run expensive compaction and locking steps

Was this meant to refer to GC languages? I've never seen one that does compaction (we're talking about the same thing? moving all the memory allocations around to reduce fragmentation?) That's more of a GC language thing that e.g. Java does; it would require the language/runtime to be able to precisely spot pointers and be able to throw in a trap when one it's changing is dereferenced or to entirely stop the world first.

Re:Changing the Rules of Rust by manwe150 on Wednesday September 27, 2023 @09:30PM (#37684049)
Yes, rust is a great language. I love what it does. It also has memory leaks, because it is missing the cleanup part of the GC so it can’t deal with reference counter cycles.

Well, there is boehm if you do want to take on that complexity. But no, I am referring to the book keeping operations that are done internal to the malloc and free calls. They are largely hidden from you as the user of the API, but you still have to pay for them in runtime.

I thought rust didn’t have any safe arena allocators, but that some people make them by tricking the system with indexes instead and such, though word was they might come soon https://news.ycombinator.com/item?id=33403324

Re:Changing the Rules of Rust by scottlamb on Sunday September 17, 2023 @05:21PM (#37549460)
Are you interpreting "GC every heap allocation" as "each heap allocation causes an entire cycle of GC"? That's...not what that phrase means.
Re:Changing the Rules of Rust by charcircuit on Sunday September 17, 2023 @05:25PM (#37549503)
Yes I am. Java's GC can stop the world at any safepoint. It isn't limited to when objects are allocated and it doesn't do it every time.
Re:Changing the Rules of Rust by scottlamb on Monday September 18, 2023 @11:44AM (#37557782)
I'm aware. A cycle per allocation would be absurdly expensive. "GC every heap allocation" actually means that every heap allocation is subject to garbage collection and has to be traversed on every cycle in which it's reachable. If the GC algorithm uses compaction and/or generations, it gets moved around too. That all has a cost.
Re:Changing the Rules of Rust by yencabulator on Sunday September 17, 2023 @08:37PM (#37550864)
The reason Arc hurts is that it's global and atomic. Multiple cores competing to inc/dec a shared reference count is pretty much a worst case scenario for modern CPUs.

Under modern garbage collection, there's very little need to coordinate across cores, just some barriers every now and then to mark things safe.

Re:Changing the Rules of Rust by scottlamb on Sunday September 17, 2023 @10:26PM (#37551511)
True, but impact depends on both total core count you're using in the process and what you're doing with Arc. I'm imagining Arc<RequestState> with low concurrency on each RequestState so impact of cache line bouncing should be negligible.

If you're talking about Arc<GlobalStuff>, I'd probably use Box::leak instead. There's also a few crates that do epoch gc. I haven't really written super high request rate multicore stuff in Rust, but I have in C++, and there for global-ish occasionally updated config stuff, I used a epoch gc implementation on top of Linux rseq to totally eliminate the effect you're describing.

Re:Changing the Rules of Rust by yencabulator on Sunday September 17, 2023 @10:29PM (#37551535)
The classic case where Arc hurts and GC really shines is Arc<Config>.

Arc makes otherwise read-only activity write.

Re:Changing the Rules of Rust by pkolaczk on Monday September 18, 2023 @01:33AM (#37552564)
How many independent things do you need to access the config? A dozen? Then there is a dozen calls to inc the RC at startup, but there is no later cost to read from object behind Arc. So you may read your config a million times and Arc changes nothing. While GC will have to scan the Config structure every time a full GC is needed, so there is an added cost.
Re:Changing the Rules of Rust by yencabulator on Monday September 18, 2023 @10:05AM (#37556290)
In the trouble scenario, config may change at any time.
Re:Changing the Rules of Rust by scottlamb on Monday September 18, 2023 @11:19AM (#37557392)
The C++ server I mentioned had an "experiment config" that would be used to roll out changes (user-visible features, backend code path changes for data store migrations, etc.) incrementally, and it picked up config changes without a restart. Each request needed to grab the current experiment config once and hold onto it for a consistent view. This server reserved ~16 cores and had pretty high request rates, so Arc<Config> would indeed hit the sort of problem yencabulator is describing. And I imagine it'd get pretty bad if each server crossed NUMA node boundaries (although I recommend avoiding that if you can regardless).

In this case, the Linux rseq-based epoch GC worked perfectly. It is technically a form of garbage collection, but it's not all-or-nothing like Java-style or Bohm GC; it's just a library you use for a few highly used, infrequently updated data structures.

Re:Changing the Rules of Rust by insanitybit on Monday September 18, 2023 @03:14PM (#37560639)
I'm not really understanding why you need to clone the Arc so many times. At most it seems that you'd do so once per request.
Re:Changing the Rules of Rust by scottlamb on Monday September 18, 2023 @04:30PM (#37561570)
"So many times" = one increment + one decrement per request = 100k/sec maybe, bouncing cache lines across 16 cores. This is suboptimal but not world-ending.
Re:Changing the Rules of Rust by insanitybit on Monday September 18, 2023 @06:35PM (#37563074)
Definitely suboptimal, no question.
Re:Changing the Rules of Rust by jenadine on Monday September 18, 2023 @01:10AM (#37552454)
It's probably too complicated to explain in a HN comment, but I don't understand why not having Leak forces futures to be 'static.
Re:Changing the Rules of Rust by pornel on Monday September 18, 2023 @06:48AM (#37554518)
There's a desire to have futures that run on other threads, but can borrow from temporary locations like stack of their caller.

This can't be implemented robustly, because there's no way to enforce the outer scope (the one lending its stack) won't return while the futures are still running. It needs a blocking mechanism that does "wait, your async code hasn't finished yet!", but implementing that via guards and destructors can be bypassed by leaking them.

https://without.boats/blog/the-scoped-task-trilemma/

----

However, running futures on the same thread, with a simple plain .await, does allow them to borrow from the caller's stack safely without restriction. So the first poster in this thread may be doing something unusual, perhaps just over-using multi-threaded spawn() where join_all or streams would suffice.

Re:Changing the Rules of Rust by Matthias247 on Monday September 18, 2023 @02:27AM (#37552909)
I assume you mean "spawned futures" - because regular Futures obviously don't have to be 'static. You can run as many of them as you want on a single task by using Future combinators like join/select.

If this is mostly about scoped async task and the ability to reference data from the parent task, then there also had been a different proposal being discussed which provides the same guarantee: A new Future type (e.g. CompletionFuture), which by represents an async function that needs to be driven to completion. See https://github.com/Matthias247/rfcs/pull/1 for details. However since this propsal is already 3 years old and hasn't gotten and interest by the Rust async WG, it's probably also not going forward.

Re:Changing the Rules of Rust by insanitybit on Monday September 18, 2023 @02:27PM (#37560028)
I am very curious to hear about why `spawn_local` does not work, or why cloning/moving values around doesn't work? Or channels?