Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loop { thread::yield_now(); } causes 100% CPU usage #46774

Closed
pzmarzly opened this issue Dec 16, 2017 · 11 comments
Closed

loop { thread::yield_now(); } causes 100% CPU usage #46774

pzmarzly opened this issue Dec 16, 2017 · 11 comments
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools C-enhancement Category: An issue proposing an enhancement or a PR with one. E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. P-medium Medium priority T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@pzmarzly
Copy link

I may be wrong, but I thought thread::yield_now() should work like thread::sleep_ms(1) (yes, I known there's preferred Duration struct). This may be caused by Linux kernel and not Rust's fault, but it leads to 100% CPU usage and PC heating up.

I tried this code:

use std::thread;
fn main() {
    for _ in 1..4 {
        thread::spawn(||  loop { thread::yield_now(); });
    }
    loop { thread::yield_now(); }
}

And ended up with 100% usage on 4-core CPU.

Meanwhile replacing thread::yield_now with thread::sleep_ms(1) drops usage to 0%. Also, following code using sync::mpsc (that according to documentation of yield_now() uses yield_now()) also (thankfully) uses 0% of CPU time:

use std::thread;
use std::sync::mpsc::channel;
fn main() {
    for _ in 1..4 {
        thread::spawn(|| {
            let (tx, rx) = channel();
            tx.send(1).unwrap();
            loop { rx.recv().unwrap(); }
        });
    }
    let (tx, rx) = channel();
    tx.send(1).unwrap();
    loop { rx.recv().unwrap(); }
}

rustc --version --verbose:

rustc 1.24.0-nightly (f8af59d95 2017-12-13)
binary: rustc
commit-hash: f8af59d95225d122e38bb5dceb1027844d4ce170
commit-date: 2017-12-13
host: x86_64-unknown-linux-gnu
release: 1.24.0-nightly
LLVM version: 4.0

uname -a: Linux pawel-pc 4.12.14-1-MANJARO #1 SMP PREEMPT Wed Sep 20 10:51:00 UTC 2017 x86_64 GNU/Linux

sudo cpupower frequency-info:

analyzing CPU 0:
  driver: intel_pstate
  CPUs which run at the same hardware frequency: 0
  CPUs which need to have their frequency coordinated by software: 0
  maximum transition latency:  Cannot determine or is not supported.
  hardware limits: 800 MHz - 3.20 GHz
  available cpufreq governors: performance powersave
  current policy: frequency should be within 800 MHz and 3.20 GHz.
                  The governor "powersave" may decide which speed to use
                  within this range.
  current CPU frequency: 1.40 GHz (asserted by call to hardware)
  boost state support:
    Supported: yes
    Active: yes

I tried both sudo cpupower frequency-set -g powersave and sudo cpupower frequency-set -g performance. I was running all te code with cargo run --release.

@leoyvens
Copy link
Contributor

From the manpage of the corresponding syscall on Linux:

If the calling thread is the only thread in the highest priority list at that time, it will continue to run after a call to sched_yield().

Perhaps that is the case. Anyways this behaviour is 100% determined by the OS. That documentation about channels looks misleading, which is a relevant docs issue.

@pietroalbini pietroalbini added I-slow Issue: Problems and improvements with respect to performance of generated code. O-linux Operating system: Linux C-enhancement Category: An issue proposing an enhancement or a PR with one. labels Jan 30, 2018
@sfackler sfackler added A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools and removed C-enhancement Category: An issue proposing an enhancement or a PR with one. I-slow Issue: Problems and improvements with respect to performance of generated code. O-linux Operating system: Linux labels Jan 30, 2018
@sfackler
Copy link
Member

This is a docs issue, not a perf issue.

@gsollazzo gsollazzo added the T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. label Feb 1, 2018
@Manishearth Manishearth removed the T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. label Feb 8, 2018
@Manishearth
Copy link
Member

@gsollazzo please don't use the A-rustdoc label for doc bugs (I've had to untag a few). That label is specifically for issues about the rustdoc tool, not about issues with the contents of the stdlib docs.

@gsollazzo
Copy link
Member

@Manishearth Ok, thanks! Will do :)

@XAMPPRocky XAMPPRocky added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Mar 26, 2018
@GuillaumeGomez GuillaumeGomez added the E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. label May 23, 2018
@steveklabnik steveklabnik added E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. and removed E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. labels May 29, 2018
@steveklabnik steveklabnik added the P-medium Medium priority label Dec 27, 2018
@the8472
Copy link
Member

the8472 commented Jun 29, 2019

Depending on your goals (latency, power consumption, progress opportunities for other threads) poll+busy-wait loops may require a mix of pause(x86)/yield(arm) instructions, yielding to the scheduler and sleeps. possibly with backoffs for the pause and sleep intervals.

yield_now() is just a low-level building block you would use in more sophisticated spin loops.

@DevQps
Copy link
Contributor

DevQps commented Aug 9, 2019

@steveklabnik Since this is behavior is platform dependent, do we actually want to document it? As @the8472 mentioned yield_now is somewhat of a low-level primitive. I wonder if we should close this issue or not. What do you think about this?

@the8472
Copy link
Member

the8472 commented Aug 10, 2019

Maybe the documentation needs to be updated. E.g. it says channels use yield to give up the thread, but that's not really true (at least skimming the implementation) yield is only used for backoff in some cases while thread::park is used to actually deschedule the thread.

Additionally it would be good to mention that it's only a hint to the scheduler. Or link to the underlying OS primitive as an implementation note so people can learn about OS-specific behavior.

@steveklabnik
Copy link
Member

Yes, I think a clarification would be best, along the lines of what @the8472 is talking about.

@crlf0710
Copy link
Member

Should there be a function that works just likethread::sleep_ms(1)? This code doesn't capture the intention well.

@the8472
Copy link
Member

the8472 commented Aug 15, 2019

I don't think such a thing belongs in the standard library because naive polling often is an anti-pattern. It prevents CPUs from entering power-save states and offers poor latency and scalability properties.

And in the cases where other alternatives are not available it one usually uses a more sophisticated approach involving loops over noops, pause instructions, yields and sleeps with backoffs. Something like that might belong in a library since it needs to be tuned depending on platform and what one is waiting on.

dctucker added a commit to dctucker/midibus that referenced this issue Sep 16, 2019
There's an issue with thread::yield_now() that causes it to eat CPU
cycles when used in a loop. Recommended to use sleep(1ms) instead

rust-lang/rust#46774
@jonas-schievink jonas-schievink added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Mar 6, 2020
@godmar
Copy link
Contributor

godmar commented Aug 4, 2021

This may be fixed by PR 86916.
See https://doc.rust-lang.org/beta/std/thread/fn.yield_now.html

@pzmarzly pzmarzly closed this as completed Aug 4, 2021
dctucker added a commit to dctucker/midibus that referenced this issue Mar 8, 2022
There's an issue with thread::yield_now() that causes it to eat CPU
cycles when used in a loop. Recommended to use sleep(1ms) instead

rust-lang/rust#46774
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools C-enhancement Category: An issue proposing an enhancement or a PR with one. E-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate. P-medium Medium priority T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests