Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
gs_shmem.F90
Go to the documentation of this file.
1! Copyright (c) 2026, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32!
35 use num_types, only : rp, c_rp, i8
36 use gs_comm, only : gs_comm_t, gs_vec_nc
38 use stack, only : stack_i4_t
39 use comm, only : neko_comm, pe_rank, pe_size
40 use mpi_f08, only : mpi_allreduce, mpi_alltoall, mpi_integer, mpi_max
41 use utils, only : neko_error
42#ifdef HAVE_OPENSHMEM
48#endif
49 use, intrinsic :: iso_c_binding, only : c_ptr, c_null_ptr, c_loc, &
50 c_f_pointer, c_associated, c_sizeof, c_size_t, c_int64_t, c_int
51 implicit none
52 private
53
60 logical :: gs_shmem_thread_multiple = .false.
61
65#ifdef HAVE_OPENSHMEM
66 logical, parameter, public :: gs_shmem_avail = .true.
67#else
68 logical, parameter, public :: gs_shmem_avail = .false.
69#endif
70
74 integer, allocatable :: ndofs(:)
76 integer, allocatable :: offset(:)
79 integer, allocatable :: remote_offset(:)
81 integer :: total = 0
83 integer :: max_total = 0
85 type(c_ptr) :: buf_ptr = c_null_ptr
86 contains
87 procedure, pass(this) :: init => gs_shmem_buf_init
88 procedure, pass(this) :: free => gs_shmem_buf_free
89 end type gs_shmem_buf_t
90
116 type, public, extends(gs_comm_t) :: gs_shmem_t
117 type(gs_shmem_buf_t) :: send_buf
118 type(gs_shmem_buf_t) :: recv_buf
119 ! Symmetric data-arrival signals; data_signals[r] is set to `iter`
120 ! by PE r via shmem_putmem_signal_nbi when it has put data into
121 ! our recv buffer. Sized to pe_size on every PE.
122 type(c_ptr) :: data_signals_ptr = c_null_ptr
123 ! Symmetric ack signals; ack_signals[r] is set to `iter` by PE r
124 ! via shmem_uint64_atomic_set after it has consumed our most
125 ! recent put. Sized to pe_size on every PE.
126 type(c_ptr) :: ack_signals_ptr = c_null_ptr
127 ! Monotonically increasing iteration counter; sender writes this
128 ! value into the receiver's data signal slot via SHMEM_SIGNAL_SET,
129 ! and the receiver writes the same value into the sender's ack
130 ! slot after consumption.
131 integer(kind=i8) :: iter = 0
132 contains
133 procedure, pass(this) :: init => gs_shmem_init
134 procedure, pass(this) :: free => gs_shmem_free
135 procedure, pass(this) :: nbsend => gs_shmem_nbsend
136 procedure, pass(this) :: nbrecv => gs_shmem_nbrecv
137 procedure, pass(this) :: nbwait => gs_shmem_nbwait
138 procedure, pass(this) :: nbsend_vec => gs_shmem_nbsend_vec
139 procedure, pass(this) :: nbrecv_vec => gs_shmem_nbrecv_vec
140 procedure, pass(this) :: nbwait_vec => gs_shmem_nbwait_vec
141 end type gs_shmem_t
142
143contains
144
149 subroutine gs_shmem_buf_init(this, pe_order, dof_stack)
150 class(gs_shmem_buf_t), intent(inout) :: this
151 integer, intent(in) :: pe_order(:)
152 type(stack_i4_t), intent(inout) :: dof_stack(0:)
153 integer :: i, ierr, n
154 integer(c_size_t) :: sz
155 real(c_rp) :: rp_dummy
156#ifndef HAVE_OPENSHMEM
157 call neko_error('Neko was not built with OpenSHMEM support')
158#else
159
160 n = size(pe_order)
161
162 allocate(this%ndofs(n))
163 allocate(this%offset(n))
164 allocate(this%remote_offset(n))
165
166 do i = 1, n
167 this%remote_offset(i) = -1
168 end do
169
170 this%total = 0
171 do i = 1, n
172 this%ndofs(i) = dof_stack(pe_order(i))%size()
173 this%offset(i) = this%total
174 this%total = this%total + this%ndofs(i)
175 end do
176
177 ! OpenSHMEM symmetric memory must be allocated with the same size on
178 ! every PE.
179 call mpi_allreduce(this%total, this%max_total, 1, mpi_integer, mpi_max, &
180 neko_comm, ierr)
181
182 ! Sized for up to GS_VEC_NC components so the fused vector path can
183 ! reuse the same symmetric buffer; the scalar path uses the first
184 ! max_total elements.
185 sz = c_sizeof(rp_dummy) * int(max(gs_vec_nc*this%max_total, 1), c_size_t)
186 this%buf_ptr = shmem_malloc(sz)
187 if (.not. c_associated(this%buf_ptr)) then
188 call neko_error('shmem_malloc failed for gs_shmem buffer')
189 end if
190#endif
191 end subroutine gs_shmem_buf_init
192
194 subroutine gs_shmem_buf_free(this)
195 class(gs_shmem_buf_t), intent(inout) :: this
196
197 if (allocated(this%ndofs)) deallocate(this%ndofs)
198 if (allocated(this%offset)) deallocate(this%offset)
199 if (allocated(this%remote_offset)) deallocate(this%remote_offset)
200
201#ifdef HAVE_OPENSHMEM
202 if (c_associated(this%buf_ptr)) then
203 ! shmem_free is collective; gs_shmem_free issues a barrier before
204 ! tearing down buffers.
205 call shmem_free(this%buf_ptr)
206 end if
207#endif
208 this%buf_ptr = c_null_ptr
209 this%total = 0
210 this%max_total = 0
211
212 end subroutine gs_shmem_buf_free
213
215 subroutine gs_shmem_init(this, send_pe, recv_pe)
216 class(gs_shmem_t), intent(inout) :: this
217 type(stack_i4_t), intent(inout) :: send_pe
218 type(stack_i4_t), intent(inout) :: recv_pe
219 integer :: i, ierr
220 integer(c_size_t) :: i64_size
221 integer(c_int64_t) :: i64_dummy
222 integer, allocatable :: local_offsets(:), remote_offsets(:)
223#ifdef HAVE_OPENSHMEM
224 integer(c_int) :: thread_level
225#endif
226#ifndef HAVE_OPENSHMEM
227 call neko_error('Neko was not built with OpenSHMEM support')
228#else
229
230 ! No self-free here: gs_schedule has just filled send_dof/recv_dof and
231 ! gs_shmem_free would deallocate them again, leaving the buffer setup
232 ! below reading from a dangling descriptor. Like every other comm
233 ! backend, init assumes a freshly allocated object.
234 call this%init_order(send_pe, recv_pe)
235
236 ! Decide once whether the OpenSHMEM library tolerates concurrent calls
237 ! from several OpenMP threads; the send path branches on it.
238 call shmem_query_thread(thread_level)
240
241 call this%send_buf%init(this%send_pe, this%send_dof)
242 call this%recv_buf%init(this%recv_pe, this%recv_dof)
243
244 ! Allocate the per-rank symmetric signal arrays. Size pe_size on
245 ! every PE -> no Allreduce needed and no slot handshake required;
246 ! every PE writes/reads at offset = remote PE's rank.
247 i64_size = c_sizeof(i64_dummy)
248 this%data_signals_ptr = shmem_calloc(int(pe_size, c_size_t), i64_size)
249 if (.not. c_associated(this%data_signals_ptr)) then
250 call neko_error('shmem_calloc failed for gs_shmem data signals')
251 end if
252 this%ack_signals_ptr = shmem_calloc(int(pe_size, c_size_t), i64_size)
253 if (.not. c_associated(this%ack_signals_ptr)) then
254 call neko_error('shmem_calloc failed for gs_shmem ack signals')
255 end if
256
257 ! Offset exchange via Alltoall. send_pe and recv_pe are built in
258 ! gs_schedule with a shifted-modulo neighbor pattern; pairwise
259 ! Isend/Irecv keyed by neighbor rank turned out to deadlock at
260 ! certain PE counts. Alltoall is a single collective call that
261 ! cannot mismatch, and the payload is just pe_size ints per PE.
262 allocate(local_offsets(0:pe_size - 1))
263 allocate(remote_offsets(0:pe_size - 1))
264 local_offsets = -1
265 do i = 1, size(this%recv_pe)
266 local_offsets(this%recv_pe(i)) = this%recv_buf%offset(i)
267 end do
268 call mpi_alltoall(local_offsets, 1, mpi_integer, &
269 remote_offsets, 1, mpi_integer, neko_comm, ierr)
270 do i = 1, size(this%send_pe)
271 this%send_buf%remote_offset(i) = remote_offsets(this%send_pe(i))
272 end do
273 deallocate(local_offsets)
274 deallocate(remote_offsets)
275
276 this%iter = 0
277 this%vec_supported = .true.
278 ! The vector slabs are part of the symmetric/registered allocation
279 ! made above, which every rank has to take part in, so they cannot
280 ! be deferred to the first fused exchange: a rank with no shared
281 ! dofs never reaches it. See gs_comm_t%vec_ready.
282 this%vec_ready = .true.
283
284 ! Ensure all PEs have completed symmetric allocation before any
285 ! one-sided communication is issued.
286 call shmem_barrier_all()
287#endif
288 end subroutine gs_shmem_init
289
291 subroutine gs_shmem_free(this)
292 class(gs_shmem_t), intent(inout) :: this
293
294#ifdef HAVE_OPENSHMEM
295 ! shmem_free is collective; synchronize first to ensure no in-flight
296 ! puts target the buffers we are about to release.
297 call shmem_barrier_all()
298
299 if (c_associated(this%data_signals_ptr)) then
300 call shmem_free(this%data_signals_ptr)
301 end if
302 if (c_associated(this%ack_signals_ptr)) then
303 call shmem_free(this%ack_signals_ptr)
304 end if
305 this%data_signals_ptr = c_null_ptr
306 this%ack_signals_ptr = c_null_ptr
307 this%iter = 0
308#endif
309
310 call this%send_buf%free()
311 call this%recv_buf%free()
312
313 call this%free_order()
314 call this%free_dofs()
315
316 end subroutine gs_shmem_free
317
323 subroutine gs_shmem_nbsend(this, u, n, tag, deps, strm)
324 class(gs_shmem_t), intent(inout) :: this
325 integer, intent(in) :: n
326 real(kind=rp), dimension(n), intent(inout) :: u
327 integer, intent(in) :: tag
328 type(c_ptr), intent(inout) :: deps
329 type(c_ptr), intent(inout) :: strm
330 integer :: i, j, dst, base, ndst
331 integer(c_size_t) :: nbytes
332 integer , pointer :: sp(:)
333 real(kind=rp), pointer :: send_data(:), recv_data(:)
334 integer(c_int64_t), pointer :: data_signals(:), ack_signals(:)
335 real(c_rp) :: rp_dummy
336#ifdef HAVE_OPENSHMEM
337
338 ! Entered from inside the gs_op_vector OpenMP parallel region. Every
339 ! thread binds its own pointers to the symmetric buffers (the c_f_pointer
340 ! targets are private locals), but the iteration counter is shared state
341 ! and must be bumped exactly once per round.
342 !$omp master
343 ! Each gs op gets a fresh signal value so receivers can distinguish
344 ! puts from one call vs. the next.
345 this%iter = this%iter + 1
346 !$omp end master
347
348 call c_f_pointer(this%send_buf%buf_ptr, send_data, &
349 [max(this%send_buf%max_total, 1)])
350 call c_f_pointer(this%recv_buf%buf_ptr, recv_data, &
351 [max(this%recv_buf%max_total, 1)])
352 call c_f_pointer(this%data_signals_ptr, data_signals, [pe_size])
353 call c_f_pointer(this%ack_signals_ptr, ack_signals, [pe_size])
354
355 ! Publish the new iter before anyone reads it below.
356 !$omp barrier
357
359 ! Each thread drives the whole protocol for its share of the peers,
360 ! keeping the per-peer wait/pack/put order of the serial code.
361 !$omp do
362 do i = 1, size(this%send_pe)
363 dst = this%send_pe(i)
364
365 ! Wait for dst to have consumed our previous round's data before
366 ! we overwrite their recv buffer. ack_signals[dst] is calloc'd
367 ! to 0 and dst writes the iter value (via atomic_set) after
368 ! consumption, so for iter == 1 the wait succeeds immediately.
369 call shmem_uint64_wait_until(c_loc(ack_signals(dst + 1)), &
370 shmem_cmp_ge, this%iter - 1_8)
371
372 sp => this%send_dof(dst)%array()
373 base = this%send_buf%offset(i)
374 ndst = this%send_buf%ndofs(i)
375 do concurrent(j = 1:ndst)
376 send_data(base + j) = u(sp(j))
377 end do
378
379 nbytes = int(ndst, c_size_t) * c_sizeof(rp_dummy)
380 ! Put data + signal dst's data_signals[my_rank].
382 c_loc(recv_data(this%send_buf%remote_offset(i) + 1)), &
383 c_loc(send_data(base + 1)), &
384 nbytes, &
385 c_loc(data_signals(pe_rank + 1)), &
386 this%iter, shmem_signal_set, dst)
387 end do
388 !$omp end do
389 else
390 ! Funnelled: every SHMEM call is left to the master and only the pack
391 ! is work-shared, one peer at a time. The per-peer wait/pack/put order
392 ! of the serial code is kept -- the ack is what makes a slab safe to
393 ! repack as well, since it means the previous round's non-blocking put
394 ! has been consumed and no longer reads our send buffer. The barrier
395 ! after the ack wait releases the team into the pack, and the implicit
396 ! barrier at end do completes the slab before the master puts it.
397 do i = 1, size(this%send_pe)
398 dst = this%send_pe(i)
399 base = this%send_buf%offset(i)
400 ndst = this%send_buf%ndofs(i)
401
402 !$omp master
403 call shmem_uint64_wait_until(c_loc(ack_signals(dst + 1)), &
404 shmem_cmp_ge, this%iter - 1_8)
405 !$omp end master
406 !$omp barrier
407
408 sp => this%send_dof(dst)%array()
409 !OCL NORECURRENCE, NOVREC, NOALIAS
410 !DIR$ CONCURRENT
411 !DIR$ IVDEP
412 !GCC$ ivdep
413 !NEC$ IVDEP
414 !$omp do
415 do j = 1, ndst
416 send_data(base + j) = u(sp(j))
417 end do
418 !$omp end do
419
420 !$omp master
421 nbytes = int(ndst, c_size_t) * c_sizeof(rp_dummy)
423 c_loc(recv_data(this%send_buf%remote_offset(i) + 1)), &
424 c_loc(send_data(base + 1)), &
425 nbytes, &
426 c_loc(data_signals(pe_rank + 1)), &
427 this%iter, shmem_signal_set, dst)
428 !$omp end master
429 end do
430 !$omp barrier
431 end if
432#endif
433 end subroutine gs_shmem_nbsend
434
436 subroutine gs_shmem_nbrecv(this, tag)
437 class(gs_shmem_t), intent(inout) :: this
438 integer, intent(in) :: tag
439
440 end subroutine gs_shmem_nbrecv
441
445 subroutine gs_shmem_nbwait(this, u, n, op, strm)
446 class(gs_shmem_t), intent(inout) :: this
447 integer, intent(in) :: n
448 real(kind=rp), dimension(n), intent(inout) :: u
449 type(c_ptr), intent(inout) :: strm
450 integer :: op
451 integer :: i, j, src, base, nsrc
452 integer(c_int64_t) :: dummy
453 integer , pointer :: sp(:)
454 real(kind=rp), pointer :: recv_data(:)
455 integer(c_int64_t), pointer :: data_signals(:), ack_signals(:)
456#ifdef HAVE_OPENSHMEM
457
458 call c_f_pointer(this%recv_buf%buf_ptr, recv_data, &
459 [max(this%recv_buf%max_total, 1)])
460 call c_f_pointer(this%data_signals_ptr, data_signals, [pe_size])
461 call c_f_pointer(this%ack_signals_ptr, ack_signals, [pe_size])
462
463 ! The loop over peers stays serial even when the library is thread
464 ! multiple: a dof shared by 3+ ranks appears in several recv_dof lists,
465 ! so reducing two slabs concurrently would race on that dof. The master
466 ! awaits and acks each slab in turn and the team reduces it, taking the
467 ! parallelism within the slab instead.
468 do i = 1, size(this%recv_pe)
469 src = this%recv_pe(i)
470 base = this%recv_buf%offset(i)
471 nsrc = this%recv_buf%ndofs(i)
472
473 ! Wait for data from src; data_signals[src] is set by src's put.
474 !$omp master
475 dummy = shmem_signal_wait_until(c_loc(data_signals(src + 1)), &
476 shmem_cmp_ge, this%iter)
477 !$omp end master
478 !$omp barrier
479
480 sp => this%recv_dof(src)%array()
481 select case (op)
482 case (gs_op_add)
483 !OCL NORECURRENCE, NOVREC, NOALIAS
484 !DIR$ CONCURRENT
485 !DIR$ IVDEP
486 !GCC$ ivdep
487 !NEC$ IVDEP
488 !$omp do
489 do j = 1, nsrc
490 u(sp(j)) = u(sp(j)) + recv_data(base + j)
491 end do
492 !$omp end do
493 case (gs_op_mul)
494 !OCL NORECURRENCE, NOVREC, NOALIAS
495 !DIR$ CONCURRENT
496 !DIR$ IVDEP
497 !GCC$ ivdep
498 !NEC$ IVDEP
499 !$omp do
500 do j = 1, nsrc
501 u(sp(j)) = u(sp(j)) * recv_data(base + j)
502 end do
503 !$omp end do
504 case (gs_op_min)
505 !OCL NORECURRENCE, NOVREC, NOALIAS
506 !DIR$ CONCURRENT
507 !DIR$ IVDEP
508 !GCC$ ivdep
509 !NEC$ IVDEP
510 !$omp do
511 do j = 1, nsrc
512 u(sp(j)) = min(u(sp(j)), recv_data(base + j))
513 end do
514 !$omp end do
515 case (gs_op_max)
516 !OCL NORECURRENCE, NOVREC, NOALIAS
517 !DIR$ CONCURRENT
518 !DIR$ IVDEP
519 !GCC$ ivdep
520 !NEC$ IVDEP
521 !$omp do
522 do j = 1, nsrc
523 u(sp(j)) = max(u(sp(j)), recv_data(base + j))
524 end do
525 !$omp end do
526 case default
527 call neko_error("Unknown operation in gs_nbwait_shmem")
528 end select
529
530 ! Tell src we are done with this round's buffer so they may
531 ! overwrite it on the next round. We set our own rank's slot
532 ! in src's ack_signals; src waits there on the next nbsend. The
533 ! implicit barrier at the end do above guarantees the whole team is
534 ! finished with the slab before it is released.
535 !$omp master
537 c_loc(ack_signals(pe_rank + 1)), &
538 this%iter, src)
539 !$omp end master
540 end do
541#endif
542 end subroutine gs_shmem_nbwait
543
548 subroutine gs_shmem_nbsend_vec(this, u, n, nc, tag, deps, strm)
549 class(gs_shmem_t), intent(inout) :: this
550 integer, intent(in) :: n, nc
551 real(kind=rp), dimension(nc*n), intent(inout) :: u
552 integer, intent(in) :: tag
553 type(c_ptr), intent(inout) :: deps
554 type(c_ptr), intent(inout) :: strm
555 integer :: i, j, c, dst, base, ndst
556 integer(c_size_t) :: nbytes
557 integer, pointer :: sp(:)
558 real(kind=rp), pointer :: send_data(:), recv_data(:)
559 integer(c_int64_t), pointer :: data_signals(:), ack_signals(:)
560 real(c_rp) :: rp_dummy
561#ifdef HAVE_OPENSHMEM
562
563 !$omp master
564 this%iter = this%iter + 1
565 !$omp end master
566
567 call c_f_pointer(this%send_buf%buf_ptr, send_data, &
568 [max(nc*this%send_buf%max_total, 1)])
569 call c_f_pointer(this%recv_buf%buf_ptr, recv_data, &
570 [max(nc*this%recv_buf%max_total, 1)])
571 call c_f_pointer(this%data_signals_ptr, data_signals, [pe_size])
572 call c_f_pointer(this%ack_signals_ptr, ack_signals, [pe_size])
573
574 !$omp barrier
575
576 ! Same threading split as the scalar path; see gs_shmem_nbsend.
578 !$omp do
579 do i = 1, size(this%send_pe)
580 dst = this%send_pe(i)
581
582 call shmem_uint64_wait_until(c_loc(ack_signals(dst + 1)), &
583 shmem_cmp_ge, this%iter - 1_8)
584
585 sp => this%send_dof(dst)%array()
586 base = this%send_buf%offset(i)
587 ndst = this%send_buf%ndofs(i)
588 do c = 1, nc
589 do concurrent(j = 1:ndst)
590 send_data(nc*base + (c-1)*ndst + j) = u((c-1)*n + sp(j))
591 end do
592 end do
593
594 nbytes = int(nc*ndst, c_size_t) * c_sizeof(rp_dummy)
596 c_loc(recv_data(nc*this%send_buf%remote_offset(i) + 1)), &
597 c_loc(send_data(nc*base + 1)), &
598 nbytes, &
599 c_loc(data_signals(pe_rank + 1)), &
600 this%iter, shmem_signal_set, dst)
601 end do
602 !$omp end do
603 else
604 do i = 1, size(this%send_pe)
605 dst = this%send_pe(i)
606 base = this%send_buf%offset(i)
607 ndst = this%send_buf%ndofs(i)
608
609 !$omp master
610 call shmem_uint64_wait_until(c_loc(ack_signals(dst + 1)), &
611 shmem_cmp_ge, this%iter - 1_8)
612 !$omp end master
613 !$omp barrier
614
615 sp => this%send_dof(dst)%array()
616 !$omp do
617 do j = 1, ndst
618 do c = 1, nc
619 send_data(nc*base + (c-1)*ndst + j) = u((c-1)*n + sp(j))
620 end do
621 end do
622 !$omp end do
623
624 !$omp master
625 nbytes = int(nc*ndst, c_size_t) * c_sizeof(rp_dummy)
627 c_loc(recv_data(nc*this%send_buf%remote_offset(i) + 1)), &
628 c_loc(send_data(nc*base + 1)), &
629 nbytes, &
630 c_loc(data_signals(pe_rank + 1)), &
631 this%iter, shmem_signal_set, dst)
632 !$omp end master
633 end do
634 !$omp barrier
635 end if
636#endif
637 end subroutine gs_shmem_nbsend_vec
638
640 subroutine gs_shmem_nbrecv_vec(this, tag, nc)
641 class(gs_shmem_t), intent(inout) :: this
642 integer, intent(in) :: tag, nc
643 end subroutine gs_shmem_nbrecv_vec
644
647 subroutine gs_shmem_nbwait_vec(this, u, n, nc, op, strm)
648 class(gs_shmem_t), intent(inout) :: this
649 integer, intent(in) :: n, nc
650 real(kind=rp), dimension(nc*n), intent(inout) :: u
651 type(c_ptr), intent(inout) :: strm
652 integer :: op
653 integer :: i, j, c, src, base, nsrc
654 integer(c_int64_t) :: dummy
655 integer, pointer :: sp(:)
656 real(kind=rp), pointer :: recv_data(:)
657 integer(c_int64_t), pointer :: data_signals(:), ack_signals(:)
658#ifdef HAVE_OPENSHMEM
659
660 call c_f_pointer(this%recv_buf%buf_ptr, recv_data, &
661 [max(nc*this%recv_buf%max_total, 1)])
662 call c_f_pointer(this%data_signals_ptr, data_signals, [pe_size])
663 call c_f_pointer(this%ack_signals_ptr, ack_signals, [pe_size])
664
665 ! Serial over peers (a dof shared by 3+ ranks appears in several recv
666 ! lists); parallelism is taken within each slab. See gs_shmem_nbwait.
667 do i = 1, size(this%recv_pe)
668 src = this%recv_pe(i)
669 base = this%recv_buf%offset(i)
670 nsrc = this%recv_buf%ndofs(i)
671
672 !$omp master
673 dummy = shmem_signal_wait_until(c_loc(data_signals(src + 1)), &
674 shmem_cmp_ge, this%iter)
675 !$omp end master
676 !$omp barrier
677
678 sp => this%recv_dof(src)%array()
679 select case (op)
680 case (gs_op_add)
681 !$omp do
682 do j = 1, nsrc
683 do c = 1, nc
684 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) + &
685 recv_data(nc*base + (c-1)*nsrc + j)
686 end do
687 end do
688 !$omp end do
689 case (gs_op_mul)
690 !$omp do
691 do j = 1, nsrc
692 do c = 1, nc
693 u((c-1)*n + sp(j)) = u((c-1)*n + sp(j)) * &
694 recv_data(nc*base + (c-1)*nsrc + j)
695 end do
696 end do
697 !$omp end do
698 case (gs_op_min)
699 !$omp do
700 do j = 1, nsrc
701 do c = 1, nc
702 u((c-1)*n + sp(j)) = min(u((c-1)*n + sp(j)), &
703 recv_data(nc*base + (c-1)*nsrc + j))
704 end do
705 end do
706 !$omp end do
707 case (gs_op_max)
708 !$omp do
709 do j = 1, nsrc
710 do c = 1, nc
711 u((c-1)*n + sp(j)) = max(u((c-1)*n + sp(j)), &
712 recv_data(nc*base + (c-1)*nsrc + j))
713 end do
714 end do
715 !$omp end do
716 case default
717 call neko_error("Unknown operation in gs_shmem_nbwait_vec")
718 end select
719
720 !$omp master
722 c_loc(ack_signals(pe_rank + 1)), &
723 this%iter, src)
724 !$omp end master
725 end do
726#endif
727 end subroutine gs_shmem_nbwait_vec
728
729end module gs_shmem
Definition comm.F90:1
integer, public pe_size
MPI size of communicator.
Definition comm.F90:62
integer, public pe_rank
MPI rank.
Definition comm.F90:59
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
Defines a gather-scatter communication method.
Definition gs_comm.f90:34
integer, parameter, public gs_vec_nc
Maximum number of components handled by the fused vector (multi-component) halo exchange used by gs_o...
Definition gs_comm.f90:50
Defines Gather-scatter operations.
Definition gs_ops.f90:34
integer, parameter, public gs_op_add
Definition gs_ops.f90:36
integer, parameter, public gs_op_max
Definition gs_ops.f90:36
integer, parameter, public gs_op_min
Definition gs_ops.f90:36
integer, parameter, public gs_op_mul
Definition gs_ops.f90:36
Defines OpenSHMEM gather-scatter communication.
Definition gs_shmem.F90:34
subroutine gs_shmem_buf_init(this, pe_order, dof_stack)
Allocate symmetric memory and per-neighbor bookkeeping for one direction of communication.
Definition gs_shmem.F90:150
subroutine gs_shmem_nbrecv_vec(this, tag, nc)
No-op: receives are completed via remote put-with-signal.
Definition gs_shmem.F90:641
logical, parameter, public gs_shmem_avail
Whether a native OpenSHMEM library was built into this Neko (–with-openshmem). Lets callers (e....
Definition gs_shmem.F90:68
subroutine gs_shmem_init(this, send_pe, recv_pe)
Initialise OpenSHMEM based communication method.
Definition gs_shmem.F90:216
subroutine gs_shmem_nbwait_vec(this, u, n, nc, op, strm)
Fused nc-component wait/reduce: per peer, wait on the data signal and reduce nc component blocks into...
Definition gs_shmem.F90:648
subroutine gs_shmem_nbsend(this, u, n, tag, deps, strm)
Pack the gathered shared dofs into the symmetric send buffer and issue non-blocking puts with signali...
Definition gs_shmem.F90:324
subroutine gs_shmem_buf_free(this)
Release symmetric memory and bookkeeping.
Definition gs_shmem.F90:195
subroutine gs_shmem_nbsend_vec(this, u, n, nc, tag, deps, strm)
Fused nc-component send: pack nc contiguous component blocks per peer slab and put nc*ndofs reals wit...
Definition gs_shmem.F90:549
subroutine gs_shmem_free(this)
Deallocate OpenSHMEM based communication method.
Definition gs_shmem.F90:292
subroutine gs_shmem_nbrecv(this, tag)
No-op: receives are completed via remote put-with-signal.
Definition gs_shmem.F90:437
logical gs_shmem_thread_multiple
True when the OpenSHMEM library provides SHMEM_THREAD_MULTIPLE. The send path then lets each OpenMP t...
Definition gs_shmem.F90:60
subroutine gs_shmem_nbwait(this, u, n, op, strm)
Wait per-neighbor for the signal indicating that data has landed, apply the gather-scatter operation ...
Definition gs_shmem.F90:446
integer, parameter, public i8
Definition num_types.f90:7
integer, parameter, public sp
Definition num_types.f90:8
integer, parameter, public c_rp
Definition num_types.f90:15
integer, parameter, public rp
Global precision used in computations.
Definition num_types.f90:14
Fortran bindings to SHMEM's C API.
Definition shmem.F90:34
@ shmem_signal_set
Definition shmem.F90:79
@ shmem_thread_multiple
Definition shmem.F90:57
@ shmem_cmp_ge
Definition shmem.F90:50
Implements a dynamic stack ADT.
Definition stack.f90:49
Utilities.
Definition utils.f90:35
Gather-scatter communication method.
Definition gs_comm.f90:53
Symmetric buffer for one direction of OpenSHMEM communication.
Definition gs_shmem.F90:72
Gather-scatter communication using OpenSHMEM one-sided puts with per-rank signaling for completion (O...
Definition gs_shmem.F90:116
Integer based stack.
Definition stack.f90:77
#define max(a, b)
Definition tensor.cu:40