Skip to content

Tracking Issue for #![feature(available_parallelism)] #74479

Closed

Description

@yoshuawuyts

The feature gate for the issue is #![feature(available_parallelism)].

This is a tracking issue for std::thread::available_parallelism; a portable API to determine how many threads to spawn in order to ensure a program can make use of all available parallelism available on a machine.

Public API

#![feature(available_parallelism)]
use std::thread;

fn main() -> std::io::Result<()> {
    let count = thread::available_parallelism()?.get();
    assert!(count >= 1);
    Ok(())
}

Tasks

  • Resolve discussion on naming.
  • Resolve discussion on function signature.
  • Resolve discussion on terminology.
  • Add support for externally-set limits on Linux (ref).
  • Add support for externally-set limits on Windows (ref).
  • Update documentation to remove mentions of "hardware" (ref).
  • Smooth out the docs example.

Notes on Platform-specific behavior

available_parallelism is (for now) only considered a hint. The following platform limitations exist:

Implementation history

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Jul 18, 2020
  2. added
    B-unstableBlocker: Implemented in the nightly compiler and unstable.
    T-libs-api[DEPRECATED; DO NOT USE]
    on Jul 18, 2020
  3. added
    Libs-SmallLibs issues that are considered "small" or self-contained
    I-libs-radarLibs issues that are tracked on the team's radar.
    on Jul 29, 2020
  4. changed the title [-]Tracking Issue for #![feature(hardware_threads)][/-] [+]Tracking Issue for #![feature(available_concurrency)][/+] on Sep 15, 2020
  5. yoshuawuyts commented on Sep 16, 2020

    @yoshuawuyts
    MemberAuthor

    A fix for the linux undercounting exists in num_cpus which is license-compatible with the compiler. I believe the fix boils down to including this conditional for the linux detection code:

    if unsafe { libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut set) } == 0 {
        let mut count: u32 = 0;
        for i in 0..libc::CPU_SETSIZE as usize {
            if unsafe { libc::CPU_ISSET(i, &set) } {
                count += 1
            }
        }
        count as usize
    } else {
        // `libc::_SC_NPROCESSORS_ONLN` code we're currently using
    }

    cc/ @luser

  6. the8472 commented on Sep 16, 2020

    @the8472
    Member

    @yoshuawuyts that would be insufficient due to cgroup CPU quotas which gets your entire cgroup frozen until the next time slice when the quota is used up. docker and other containerization services makes heavy use of that for resource allocation.
    One can and other runtimes (e.g. java) do calculate the equivalent of available full-time cpu-cores per time slice from the quotas.

    see https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt

  7. added a commit that references this issue on Oct 15, 2020
  8. added a commit that references this issue on Oct 18, 2020
  9. strohel commented on Oct 27, 2020

    @strohel

    I would suggest that taking into account Linux a) cpusets (#74479 (comment)) and b) cgroup cpu bandwidth limits (#74479 (comment)) are considered blockers of stabilization of available_concurrency.

    Rationale: the change b) in num_cpus was a source of significant CPU efficiency gains and potential memory savings in cloud-based environments. If available_concurrency is stabilized, then it is expected people will migrate to it from num_cpus. That would currently cause a regression.

    This is complicated by the fact that associating "Kubernetes CPU limits" with "cgroups" and https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt is non-trivial as there are abstraction layers between them. (so people may not be aware of this even in the presence of documentation on available_concurrency)

    The fix can also be to include some num_cpus code, but the cgroups part is more substantial than the cpusets one.

  10. yoshuawuyts commented on Oct 27, 2020

    @yoshuawuyts
    MemberAuthor

    @strohel I definitely agree we should support cpusets before stabilizing: we may currently over-report concurrency in containers which is something that should be fixed.

    But I'm less sure about cgroup cpu bandwidth limits. If I'm understanding #74479 (comment) this would cause the reported number of CPUs to fluctuate during the runtime of the program. I believe this may cause issues when used in examples like the following:

    use std::thread;
    
    fn main() -> io::Result<()> {
        // perform init work here (connect to db, warm caches, etc.)
    
        let max_threads = thread::available_concurrency().map(|n| n.get()).unwrap_or(1);
        let pool = ThreadPool::init(max_threads);
    
        // use pool for the duration of the program
    }

    If the boot sequence of a program is CPU-intensive, by the time the thread pool is initialized the CPU quota may have been expended. Which means that the thread pool may set a limit lower than the max supported concurrency for the program, causing an overall under-utilization of resources.

    It would seem that the right use of a bandwidth-aware API would be to periodically update the concurrency. Not to statically initialize it once since that risks under-utilization if the quota is at a low. Did I interpret this accurately?

    edit: On closer reading I think I may have indeed misinterpreted. The quota value is statically set, and not constantly updated during a program's runtime. Which means it can indeed be used to calculate the max concurrency and does not need to be updated in a loop. If this is indeed the case, I agree we should account for this.

  11. strohel commented on Oct 27, 2020

    @strohel

    @yoshuawuyts I see you've just edited the comment; the clarification is correct.

    It is true the one can change CPU quota of a running program, but the same AFAIK holds about cpu sets. Linux even supports runtime physical CPU hotplugging. That's a non-issue, the API can do nothing about it (beyond mentioning in the docs). Caching of the value is problematic for this reason.

  12. the8472 commented on Oct 27, 2020

    @the8472
    Member

    If the boot sequence of a program is CPU-intensive, by the time the thread pool is initialized the CPU quota may have been expended. Which means that the thread pool may set a limit lower than the max supported concurrency for the program, causing an overall under-utilization of resources.

    Perhaps my post was not clear. There is a quota-configuration which you can read from sysfs (as quota per interval and interval length) and the runtime depletion of the quota. The depletion is dynamic, but the configuration does not change unless someone explicitly reconfigures them. available_concurrency() would be based on the configuration, not the remaining quota available in the current time slice.

  13. 51 remaining items

  14. added 2 commits that reference this issue on Mar 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-concurrencyArea: ConcurrencyB-unstableBlocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCI-libs-radarLibs issues that are tracked on the team's radar.Libs-SmallLibs issues that are considered "small" or self-containedT-libs-api[DEPRECATED; DO NOT USE]disposition-mergeThis issue / PR is in PFCP or FCP with a disposition to merge it.finished-final-comment-periodThe final comment period is finished for this PR / Issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions