Optimize `loop {}`

loop {} (and similar loops like while true { a = a + 0; } should not use all the power of a CPU.

This is an optimization.

A drawback of my proposition is that it sometimes may make locating halted process harder, but this is a weak argument against my feature proposal.

1 Like

What would you have it do instead?

1 Like

It should pause the current thread (and handle as nothing its possible awake requests).

Did the bug in LLVM, where it completely removes empty infinite loops, because that is a valid optimization for C++, even though Rust is not C++, ever get fixed? Because that needs to get fixed first.

I can't think of any reason why we shouldn't start emitting a CPU-level "this thread has nothing useful to do" instruction into the body of all empty infinite loops, e.g. for x86

.infloop:
        pause
        jmp infloop

instead of just

.infloop:
        jmp infloop

This would best be done by the compiler's back end, after all loop optimizations are complete, and is therefore a feature request for LLVM, Cranelift, etc. rather than for rustc.

Emitting a "park this thread" system call is harder, not because it's any harder to make the compiler do it (for example, on any x86-based Unix you would just replace the pause instruction above with call pause) but because introducing system calls where none exist in the source code is fraught with implications. Someone's got to actually provide the stub for the pause system call; normally this is the C library but in no-std configurations you can't assume that exists. People want to be able to enumerate all the system calls that might be invoked by a particular program by static analysis of its source code; this is an additional wrinkle for that analysis. Etc.

5 Likes

Yes, that was rust#28728 and llvm#1337, finally fixed by LLVM 12's explicit mustprogress attribute, which we avoid for Rust.

I agree, and I don't expect we'll ever do this implicitly. Maybe there could be some kind of "loop handler" akin to the existing panic handler, where you could choose to call an OS pause, but I think that would be a lot of effort for something that really shouldn't happen. While infinite loops definitely need to be safe, they don't need to be optimized IMO.

6 Likes

Can you say more about why you're writing such a loop in the first place?

Note that without an OS we can't really do any better than spin_loop in core::hint - Rust, which may reduce the CPU power usage by a bit but still keeps it at 100% usage.

All the other options -- yield_now in std::thread - Rust or sleep_ms in std::thread - Rust or park in std::thread - Rust -- need an OS and thus are generally not something that we'd introduce automatically.

(And at least last I checked, on at least one OS even yield_now doesn't drop a loop below 100% usage on an otherwise-idle CPU.)

8 Likes

Code size would be the most notable reason. If you expect to run the infinite loop, it makes sense to have a pause in it. If you're using the infinite loop as a placeholder, you might not want the extra two bytes.

More importantly, it's not obvious that we should do that by default for everyone, rather than encouraging people to use spin_loop in std::hint - Rust when they want it.

4 Likes

In particular, a loop that isn't empty still might need a spin loop hint. It would be bad if adding useful work to a spin loop implicitly made it less energy efficient.

Clippy already has an empty_loop lint, but its suggestions don't include spin_loop() (or thread::park()).

10 Likes

If you still have placeholder code in your app, you probably are not at a stage where 2 bytes matters a lot to you?

3 Likes

Sometimes you are forced to define a panic handler or have some other code you know for sure is unreachable but where rustc doesn't know about this. loop {} is a useful placeholder in that case. Especially as std::process::abort() can't be used on no_std, and core::intrinsics::abort() isn't exposed as a stable function.

5 Likes

Exactly. And unreachable_unchecked!() isn't always worth the extra stress of thinking about that case.

2 Likes

And if it does happen, the hanged thread can be easier to debug than if it had aborted the process. Assuming it's the developer running it. One could even recover the process from that state in some cases.

1 Like

Why not?

Well you can always get an abort by double-panicking.

2 Likes

There is an ongoing effort about it abort in core · Issue #442 · rust-lang/libs-team · GitHub

7 Likes

I don't understand why this is appealing when there are options in the standard library with much clearer, explicit, semantics. If you want something they can't provide, then I think you should be asking for a new standard library function.

4 Likes