Skip to content

Reduce barrier/collective execution floor - #1240

Open
avinciguerra07 wants to merge 2 commits into
Sandia-OpenSHMEM:mainfrom
avinciguerra07:avinciguerra/barrier-psync-opt
Open

avinciguerra07 wants to merge 2 commits into
Sandia-OpenSHMEM:mainfrom
avinciguerra07:avinciguerra/barrier-psync-opt

Conversation

@avinciguerra07

Copy link
Copy Markdown

Shrink the shmem_barrier_all/collective critical path, where the internal pSync reset was issued as a put-to-self over the fabric plus a wait for its own NIC writeback on every PE every collective.

  • collectives.c: replace the self-loopback pSync clear (put_scalar to shmem_internal_my_pe + SHMEM_WAIT_UNTIL == 0) with a local store plus an acquire-release fence at all 22 sync/broadcast/reduce/scan/fcollect sites. Each clear is preceded by a fully-draining counting wait, so the incoming call-ins are already drained and a local reset before the next reuse is race-free; remote ack/signal puts are unchanged.

  • transport_ofi.h: skip the progress-lock trylock/unlock in shmem_transport_probe() when running SHMEM_THREAD_SINGLE, and read the put error counter in shmem_transport_quiet() only when completions have stalled (success < cnt) instead of every spin, keeping error detection intact.

Barrier microbenchmark (osu_oshm_barrier) shows a ~15-40 percent reduction of the pure barrier.

Shrink the shmem_barrier_all/collective critical path on OFI manual-progress
builds that have no on-node (XPMEM/CMA) shortcut, where the internal pSync
reset was issued as a put-to-self over the fabric plus a wait for its own NIC
writeback on every PE every collective.

- collectives.c: replace the self-loopback pSync clear (put_scalar to
  shmem_internal_my_pe + SHMEM_WAIT_UNTIL == 0) with a local store plus an
  acquire-release fence at all 22 sync/broadcast/reduce/scan/fcollect sites.
  Each clear is preceded by a fully-draining counting wait, so the incoming
  call-ins are already drained and a local reset before the next reuse is
  race-free; remote ack/signal puts are unchanged.

- transport_ofi.h: skip the progress-lock trylock/unlock in
  shmem_transport_probe() when running SHMEM_THREAD_SINGLE, and read the put
  error counter in shmem_transport_quiet() only when completions have stalled
  (success < cnt) instead of every spin, keeping error detection intact.

Barrier microbenchmark (osu_oshm_barrier, 2 CN5000 nodes, OFI/opx) shows a
~15-40 percent reduction of the pure barrier floor at low-mid PE counts;
gauss-legendre pi-benchmark output is bit-identical (total_hex_digits and
spigot checks unchanged).

Signed-off-by: Armando Vinciguerra <armando.vinciguerra@cornelisnetworks.com>
@lstewart

lstewart commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

This changes a put to self with a CPU store. I do not understand why this should work. If other PEs modify the same location using atomics and the atomicity zone is using NIC atomics that might be cached in the NIC, then a CPU atomic will not work correctly. If the only remote operations are ordinary puts, then it could work.

@avinciguerra07

Copy link
Copy Markdown
Author

This changes a put to self with a CPU store. I do not understand why this should work. If other PEs modify the same location using atomics and the atomicity zone is using NIC atomics that might be cached in the NIC, then a CPU atomic will not work correctly. If the only remote operations are ordinary puts, then it could work.

I was working on the assumption that the barrier makes the slot quiescent at clear time: the SHMEM_WAIT_UNTIL(pSync == N) above the clear has already drained all N call-ins for this round, and barrier semantics forbid any PE from issuing the next round's call-in until every PE has left this barrier (each waits for its ack). So between "count satisfied" and "next round's first atomic," no atomic is in flight against the cell, nothing races the store.

But if I understand what you are saying, a NIC-atomic provider, "drained all call-ins" (completion-visible) does not guarantee the NIC has written the value back to host memory?

This seems to work for ofi, but I might be breaking some other transport. Would it be alright if I gated it with something like this:

static inline void
shmem_internal_sync_clear(long *pSync)
{
if (shmem_transport_ofi_host_atomics) {
*pSync = 0;
shmem_internal_membar_acq_rel();
} else {
long zero = 0;
shmem_internal_put_scalar(SHMEM_CTX_DEFAULT, pSync, &zero,
sizeof(zero), shmem_internal_my_pe);
SHMEM_WAIT_UNTIL(pSync, SHMEM_CMP_EQ, 0);
}
}

Or something more conservative like an env variable and we have to manually enable it at config: --enable-host-atomic-psync-clear?

@lstewart

lstewart commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

That is indeed the issue I am worried about, you can't mix shmem atomics with loads, stores, or atomics in a different atomicity domain.
However, maybe there is another way? Rather than reset the psync to zero, why not just remember its starting value and do the WAIT_FOR with starting_value + num_pes -1 ?
Also, if there IS a problem mixing atomics and non-atomics, then it was already wrong to use shmem_put_internal anyway.

The first idea only works if the psync is only used for barriers with the same arguments...
It looks like you could do this hack only for the non-active-set versions, which use internal dedicated psync and you can make your own rules.

I'd argue the performance of the active set versions is uninteresting, and the spec is broken anyway, since it specifies initializing the user supplied psync with load/store and then uses them with atomics.

@lstewart

lstewart commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

Well the exact wording is
OpenSHMEM atomic operations do not guarantee exclusivity in the following scenarios, all of which result in unde-
fined behavior.

  1. When concurrent accesses to the same location are performed using OpenSHMEM atomic operations using
    communication contexts in different atomicity domains.
  2. When concurrent accesses to the same location are performed using OpenSHMEM atomic operations using
    different datatypes.
  3. When atomic and non-atomic OpenSHMEM operations are used to access the same location concurrently.
  4. When OpenSHMEM atomic operations and non-OpenSHMEM operations (e.g., load and store operations) are
    used to access the same location concurrently.

Which at least suggests that you can alternate atomic and non-atomic accesses provided you can prove the accesses are not concurrent, which I think means potentially occurring in different orders

In the case of barrier, the WAIT guarantees that clearing the psync doesn't occur until after all atomic updates are visible, and doing the clear before the downstream notifications guarantees that no future atomics will occur until the clear is done

So I guess the change is OK.

@lstewart

lstewart commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

This also suggests that NIC atomics are not allowed to cache, which I find surprising. I wonder if that is true.

@avinciguerra07

Copy link
Copy Markdown
Author

Rather than reset the psync to zero, why not just remember its starting value and do the WAIT_FOR with starting_value + num_pes -1 ?

I like this idea much better than mine. I will test it a little bit and if all goes well you should see a push with this implementation soon.

The internal world barrier (shmem_internal_sync_tree over the dedicated
shmem_internal_barrier_all_psync / shmem_internal_sync_all_psync arrays)
runs over a constant tree topology with a fixed PE count, and its pSync
slot is only ever incremented via atomic SUM call-ins.  Rather than reset
the slot to zero after every barrier -- which the prior change did with a
local CPU store -- remember a running base and wait for base + delta, then
advance the base.  The slot is never written locally at all, so this is
correct on every transport, including ones that apply AMOs in NIC-cached
memory (addresses the review concern about mixing a CPU store with
NIC-side atomics).

The accumulate-rebase only applies to the two dedicated internal world
pSync arrays where topology and PE count are constant; every other pSync
(active-set/user-supplied, sub-world teams, sync_linear, dissem, and the
broadcast/reduce/scan/collect handshakes) keeps the local-store clear.

Validated correct with gauss-legendre -b30 at 64 PE and 256 PE on CN5000
(total_hex_digits=268435376, spigot rc=0, no NACKs); no 2-node barrier
regression.  The execution-floor benefit is expected to compound at large
node/PE counts and remains to be confirmed at full scale.

Signed-off-by: Armando Vinciguerra <armando.vinciguerra@cornelisnetworks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants