Lowers n to the number of processors this process's CPU affinity actually allows, and says
so once per process when that clamp bites.
This is the ONE place the affinity clamp lives. Four resolvers reach it -- the sort's
resolve_thread_count, pf_sort_threads and the bulk random draws through
parquet_auto_thread_count, prefetch_thread_count (src/parquet_tables_read.f90) and
parquet_string_threads (src/parquet_strings.f90). It is here rather than in any of them
because three of the four live in tiers that cannot see each other, and a second copy of a
rule like this is how two subsystems come to disagree about the same machine.
omp_get_max_threads is what the environment ASKED for; omp_get_num_procs is what the
affinity mask allows. They differ whenever the initial thread was bound before main --
OMP_PROC_BIND with OMP_PLACES=cores binds it to one core, after which a team asked for 64
lands on however few processors the mask holds and time-shares them, which is slower than not
threading at all. Clamping is therefore a performance decision, never a correctness one: the
answer is identical at every thread count.
The warning is the whole point, because nothing else reveals this. No call fails, no
result changes, and the only symptom is wall-clock. area names the subsystem that noticed
so the reader knows which work was affected; the fix it recommends is the same either way.
Every resolver passes an area; there is no silent variant, deliberately. The obvious
refinement -- let a QUERY such as pf_sort_threads() or parquet_string_threads() clamp
without printing, and leave the warning to the operation that follows -- was tried and is
wrong here, because pf_sort_threads() is exactly how the sort's own AUTOMATIC path resolves
its count. A silent query therefore hands resolve_thread_count an already-clamped number,
whose own clamp is then a no-op, and the warning becomes unreachable for the one job it
exists to catch: OMP_NUM_THREADS=64 under OMP_PLACES=cores, no explicit threads=, and
nothing said. Warning from the shared clamp covers both paths and every subsystem at once.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | n |
threads resolved before the clamp. |
||
| character(len=*), | intent(in) | :: | area |
subsystem this count belongs to, for the message. |
integer function parquet_clamp_to_affinity(n, area) result(m) #ifdef _OPENMP use omp_lib, only: omp_get_num_procs #endif integer, intent(in) :: n !! threads resolved before the clamp. character(len=*), intent(in) :: area !! subsystem this count belongs to, for the message. character(len=32) :: got, asked integer(int64) :: seen !! the claim count this call observed; 0 means this call won it. integer :: procs !! processors the mask allows; 0 when this build has no OpenMP. ! m = max(1, n) procs = 0 #ifdef _OPENMP procs = omp_get_num_procs() #endif if (dbg_affinity_procs > 0) procs = dbg_affinity_procs if (procs < 1) return if (m <= procs) return m = max(1, procs) ! Fast path first: in a process whose affinity really is clamped every resolver reaches here ! on every operation, so this must cost one load once the line has been said. `atomic read` ! is a plain load on every real target. !$omp atomic read seen = affinity_clamp_claims if (seen /= 0_int64) return ! Suppression is checked BEFORE the claim, deliberately: a run that silenced its output must ! not consume the one warning, so a later call with output enabled still receives it. ! ! **`parquet_output_is_suppressed` rather than `parquet_emit_warning`'s own threshold**, so ! this one message goes quiet at verbosity `"silent"` while an ordinary warning survives to ! `"errors_only"`. That is deliberate and is what `doc/pages/operating/performance.md` ! documents: this is advice about the caller's ENVIRONMENT, not a report of anything the ! library found wrong, so it belongs with the output a `"silent"` run is asking to be spared. if (parquet_output_is_suppressed()) return !$omp atomic capture seen = affinity_clamp_claims affinity_clamp_claims = affinity_clamp_claims + 1_int64 !$omp end atomic if (seen /= 0_int64) return write (got, '(i0)') m ! **The count REQUESTED, not `omp_get_max_threads()`.** The clamp fires for an explicit ! `threads=` too, and reporting the environment's ICV there names a number the caller never ! asked for -- `threads=100` on a machine offering 8 used to print "although 8 were ! requested". `n` is what the resolver actually settled on before this clamp touched it, so ! it is right on both paths: the caller's own number when they named one, and the ! environment's (already narrowed by any cap) when they did not. write (asked, '(i0)') n call parquet_emit_warning(trim(area) // " is limited to " // trim(got) // & " thread(s) because this process's CPU affinity allows no more, although " // & trim(asked) // " were requested. This usually means OMP_PROC_BIND is set with " // & "OMP_PLACES=cores; OMP_PLACES=sockets avoids it.") end function parquet_clamp_to_affinity