pub struct Ring { /* private fields */ }Expand description
Sparse directed message rings packed into collective RMA windows.
Safe to share between threads on the same process.
Lifetime is the underlying MPI window
To drop the ring use Self::close (collective) or let the destructor run symmetrically on every rank.
Implementations§
Source§impl Ring
impl Ring
Sourcepub fn safe<C: Communicator + ?Sized>(
comm: &C,
rings: &[(Rank, Rank, usize, usize)],
) -> Result<Self, Error>
pub fn safe<C: Communicator + ?Sized>( comm: &C, rings: &[(Rank, Rank, usize, usize)], ) -> Result<Self, Error>
Construct a safe (overwrite-gated) ring. Collective over comm.
rings names the active directed lanes, one (source, destination, depth, capacity) tuple per lane: depth is the number of fixed slots
the lane holds, capacity the payload bytes one slot fits. Every rank
passes the same list.
Each source lane gets a cumulative-acknowledgement counter at the source
The senders spin on yield_now until the receiver has acked
enough earlier messages to make room in the slot ring. Requires a
unified window memory model otherwise contruction fails
§Errors
Error::Intercommunicatorifcommis an intercommunicator.Error::Ringfor configuration disagreement, invalid or repeated lanes, or zero depth or capacity.Error::Windowif the window uses a separate memory model, which cannot support the local polling the ring relies on.- Plus whatever
CommunicatorRmaExt::allocate_windowreturns for the slot and counter windows.
Examples found in repository?
20fn run(world: &SimpleCommunicator, safe: bool) {
21 let rank = world.rank();
22 let ring = if safe {
23 Ring::safe(world, &lanes(2, 8)).unwrap()
24 } else {
25 Ring::raw(world, &lanes(2, 8)).unwrap()
26 };
27
28 if rank < 2 {
29 ring.send(2, &[rank as u8, 1]).unwrap();
30 }
31 world.barrier();
32
33 if rank == 2 {
34 let messages = ring.poll().unwrap();
35 assert_eq!(messages.len(), 2);
36 assert_eq!(messages[0].origin, 0);
37 assert_eq!(messages[0].data, vec![0, 1]);
38 assert_eq!(messages[1].origin, 1);
39 assert_eq!(messages[1].data, vec![1, 1]);
40 ring.ack(0, 1).unwrap();
41 ring.ack(1, 1).unwrap();
42 ring.send(0, &[2, 1]).unwrap();
43 ring.send(1, &[2, 1]).unwrap();
44 }
45 world.barrier();
46
47 if rank < 2 {
48 let messages = ring.poll().unwrap();
49 assert_eq!(
50 messages,
51 vec![Message {
52 origin: 2,
53 sequence: 1,
54 data: vec![2, 1]
55 }]
56 );
57 ring.ack(2, 1).unwrap();
58 }
59 world.barrier();
60
61 ring.close().unwrap();
62}More examples
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146 if rank == 0 {
147 assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148 }
149 world.barrier();
150 if rank == 1 {
151 let messages = ring.poll().unwrap();
152 assert_eq!(messages.len(), 1);
153 assert_eq!(messages[0].origin, 0);
154 assert_eq!(messages[0].data, vec![7]);
155 ring.ack(0, 1).unwrap();
156 }
157 world.barrier();
158 ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}
193
194fn main() {
195 let (universe, provided) =
196 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
197 assert_eq!(provided, Threading::Multiple);
198 let world = universe.world();
199 let rank = world.rank();
200 let size = world.size();
201 assert_eq!(size, 2);
202 let next = (rank + 1) % size;
203 let prev = (rank + size - 1) % size;
204
205 // Configuration must agree across ranks.
206 if rank == 0 {
207 assert!(matches!(
208 Ring::safe(&world, &pair(1, 8)),
209 Err(Error::Ring("configuration differs between ranks"))
210 ));
211 } else {
212 assert!(matches!(
213 Ring::safe(&world, &pair(2, 8)),
214 Err(Error::Ring("configuration differs between ranks"))
215 ));
216 }
217 assert!(matches!(
218 Ring::raw(&world, &pair(0, 8)),
219 Err(Error::Ring("depth must be positive"))
220 ));
221
222 safe_basics(&world, rank, next, prev);
223 oneway_basics(&world, rank);
224 backpressure(&world, rank);
225 raw_basics(&world, rank, next, prev);
226
227 if rank == 0 {
228 println!("test_ring: ok");
229 }
230}337fn main() {
338 let mut args = std::env::args().skip(1);
339 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342 let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344 let (mut universe, provided) =
345 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346 assert_eq!(provided, Threading::Multiple);
347 let world = universe.world();
348 let rank = world.rank();
349 let size = world.size();
350 assert!(size >= 2, "compare needs at least 2 ranks");
351 assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352 // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353 // only it attaches the buffer; every rank allocating 512 MiB would need
354 // tens of GiB at scale. Sized past the largest amount a stream can leave
355 // in flight, so the comparison measures the transport and not a buffer
356 // running out.
357 if rank == 0 {
358 universe.set_buffer_size(BSEND_BUF);
359 }
360
361 let stop = Arc::new(AtomicBool::new(false));
362 let noisy = noise_kib > 0;
363
364 std::thread::scope(|scope| {
365 if noisy && rank >= 2 {
366 let stop = Arc::clone(&stop);
367 let shared = Shared(&world);
368 scope.spawn(move || {
369 let shared = shared;
370 noise(shared.0, noise_kib, &stop);
371 });
372 }
373 if rank > 1 {
374 // Noise ranks take no part in the measurement. They wait here for
375 // rank 0 to call time, then let their noise thread finish.
376 world
377 .process_at_rank(0)
378 .receive_vec_with_tag::<u8>(TAG_QUIT);
379 stop.store(true, Ordering::Relaxed);
380 return;
381 }
382
383 if rank == 0 {
384 println!("{HEADER}");
385 }
386
387 // The measured pair only. A world barrier here would deadlock against
388 // ranks sitting in the noise loop.
389 let pair: Vec<Rank> = vec![0, 1];
390 let group = world.group().include(&pair);
391 let duo = world
392 .split_by_subgroup(&group)
393 .expect("ranks 0 and 1 form the measured pair");
394
395 for rep in 0..repeats {
396 for payload in SIZES {
397 // Both ring modes share the lane shape, so the only difference
398 // between the two rows is the acknowledgement gate.
399 let lanes = vec![
400 (0 as Rank, 1 as Rank, depth, payload),
401 (1, 0, depth, payload),
402 ];
403 // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404 // 5 GiB through the wire for one row.
405 let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406 let iters = if payload >= 32768 { 500 } else { 2000 };
407
408 for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409 let ring = if safe {
410 Ring::safe(&duo, &lanes).unwrap()
411 } else {
412 Ring::raw(&duo, &lanes).unwrap()
413 };
414 let (sent, got, inject, total, wait) =
415 ring_stream(&duo, &ring, rank, rep, payload, n);
416 if rank == 0 {
417 Row {
418 transport: name,
419 measure: "stream",
420 payload,
421 sent,
422 delivered: got,
423 inject,
424 total,
425 wait,
426 max_us: 0.0,
427 }
428 .print(size, depth, noise_kib, rep);
429 }
430 duo.barrier();
431 let stalled = ring.wait_ns();
432 let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433 if rank == 0 {
434 Row {
435 transport: name,
436 measure: "rtt",
437 payload,
438 sent: iters * 2,
439 delivered: iters * 2,
440 inject: dt,
441 total: dt,
442 wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443 max_us,
444 }
445 .print(size, depth, noise_kib, rep);
446 }
447 duo.barrier();
448 ring.close().unwrap();
449 }
450
451 for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452 let (sent, got, inject, total) =
453 p2p_stream(&duo, rank, rep, payload, n, buffered);
454 if rank == 0 {
455 Row {
456 transport: name,
457 measure: "stream",
458 payload,
459 sent,
460 delivered: got,
461 inject,
462 total,
463 wait: 0.0,
464 max_us: 0.0,
465 }
466 .print(size, depth, noise_kib, rep);
467 }
468 duo.barrier();
469 }
470 let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471 if rank == 0 {
472 Row {
473 transport: "p2p-send",
474 measure: "rtt",
475 payload,
476 sent: iters * 2,
477 delivered: iters * 2,
478 inject: dt,
479 total: dt,
480 wait: 0.0,
481 max_us,
482 }
483 .print(size, depth, noise_kib, rep);
484 }
485 duo.barrier();
486 }
487 }
488
489 if rank == 0 {
490 for r in 2..size {
491 world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492 }
493 }
494 });
495
496 // Buffered sends must be drained before MPI tears the buffer down.
497 world.barrier();
498}121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn raw<C: Communicator + ?Sized>(
comm: &C,
rings: &[(Rank, Rank, usize, usize)],
) -> Result<Self, Error>
pub fn raw<C: Communicator + ?Sized>( comm: &C, rings: &[(Rank, Rank, usize, usize)], ) -> Result<Self, Error>
Construct a raw (overwrite-capable) ring. Collective over comm.
rings has the same shape as for Self::safe. No acknowledgement
counter: unread slots are silently overwritten when the depth is
exhausted, and the receiver reports the gap via Self::lost. The
sender never blocks on a slow peer. Requires a unified window memory
model.
§Errors
Same conditions as Self::safe.
Examples found in repository?
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}
193
194fn main() {
195 let (universe, provided) =
196 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
197 assert_eq!(provided, Threading::Multiple);
198 let world = universe.world();
199 let rank = world.rank();
200 let size = world.size();
201 assert_eq!(size, 2);
202 let next = (rank + 1) % size;
203 let prev = (rank + size - 1) % size;
204
205 // Configuration must agree across ranks.
206 if rank == 0 {
207 assert!(matches!(
208 Ring::safe(&world, &pair(1, 8)),
209 Err(Error::Ring("configuration differs between ranks"))
210 ));
211 } else {
212 assert!(matches!(
213 Ring::safe(&world, &pair(2, 8)),
214 Err(Error::Ring("configuration differs between ranks"))
215 ));
216 }
217 assert!(matches!(
218 Ring::raw(&world, &pair(0, 8)),
219 Err(Error::Ring("depth must be positive"))
220 ));
221
222 safe_basics(&world, rank, next, prev);
223 oneway_basics(&world, rank);
224 backpressure(&world, rank);
225 raw_basics(&world, rank, next, prev);
226
227 if rank == 0 {
228 println!("test_ring: ok");
229 }
230}More examples
20fn run(world: &SimpleCommunicator, safe: bool) {
21 let rank = world.rank();
22 let ring = if safe {
23 Ring::safe(world, &lanes(2, 8)).unwrap()
24 } else {
25 Ring::raw(world, &lanes(2, 8)).unwrap()
26 };
27
28 if rank < 2 {
29 ring.send(2, &[rank as u8, 1]).unwrap();
30 }
31 world.barrier();
32
33 if rank == 2 {
34 let messages = ring.poll().unwrap();
35 assert_eq!(messages.len(), 2);
36 assert_eq!(messages[0].origin, 0);
37 assert_eq!(messages[0].data, vec![0, 1]);
38 assert_eq!(messages[1].origin, 1);
39 assert_eq!(messages[1].data, vec![1, 1]);
40 ring.ack(0, 1).unwrap();
41 ring.ack(1, 1).unwrap();
42 ring.send(0, &[2, 1]).unwrap();
43 ring.send(1, &[2, 1]).unwrap();
44 }
45 world.barrier();
46
47 if rank < 2 {
48 let messages = ring.poll().unwrap();
49 assert_eq!(
50 messages,
51 vec![Message {
52 origin: 2,
53 sequence: 1,
54 data: vec![2, 1]
55 }]
56 );
57 ring.ack(2, 1).unwrap();
58 }
59 world.barrier();
60
61 ring.close().unwrap();
62}337fn main() {
338 let mut args = std::env::args().skip(1);
339 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342 let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344 let (mut universe, provided) =
345 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346 assert_eq!(provided, Threading::Multiple);
347 let world = universe.world();
348 let rank = world.rank();
349 let size = world.size();
350 assert!(size >= 2, "compare needs at least 2 ranks");
351 assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352 // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353 // only it attaches the buffer; every rank allocating 512 MiB would need
354 // tens of GiB at scale. Sized past the largest amount a stream can leave
355 // in flight, so the comparison measures the transport and not a buffer
356 // running out.
357 if rank == 0 {
358 universe.set_buffer_size(BSEND_BUF);
359 }
360
361 let stop = Arc::new(AtomicBool::new(false));
362 let noisy = noise_kib > 0;
363
364 std::thread::scope(|scope| {
365 if noisy && rank >= 2 {
366 let stop = Arc::clone(&stop);
367 let shared = Shared(&world);
368 scope.spawn(move || {
369 let shared = shared;
370 noise(shared.0, noise_kib, &stop);
371 });
372 }
373 if rank > 1 {
374 // Noise ranks take no part in the measurement. They wait here for
375 // rank 0 to call time, then let their noise thread finish.
376 world
377 .process_at_rank(0)
378 .receive_vec_with_tag::<u8>(TAG_QUIT);
379 stop.store(true, Ordering::Relaxed);
380 return;
381 }
382
383 if rank == 0 {
384 println!("{HEADER}");
385 }
386
387 // The measured pair only. A world barrier here would deadlock against
388 // ranks sitting in the noise loop.
389 let pair: Vec<Rank> = vec![0, 1];
390 let group = world.group().include(&pair);
391 let duo = world
392 .split_by_subgroup(&group)
393 .expect("ranks 0 and 1 form the measured pair");
394
395 for rep in 0..repeats {
396 for payload in SIZES {
397 // Both ring modes share the lane shape, so the only difference
398 // between the two rows is the acknowledgement gate.
399 let lanes = vec![
400 (0 as Rank, 1 as Rank, depth, payload),
401 (1, 0, depth, payload),
402 ];
403 // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404 // 5 GiB through the wire for one row.
405 let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406 let iters = if payload >= 32768 { 500 } else { 2000 };
407
408 for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409 let ring = if safe {
410 Ring::safe(&duo, &lanes).unwrap()
411 } else {
412 Ring::raw(&duo, &lanes).unwrap()
413 };
414 let (sent, got, inject, total, wait) =
415 ring_stream(&duo, &ring, rank, rep, payload, n);
416 if rank == 0 {
417 Row {
418 transport: name,
419 measure: "stream",
420 payload,
421 sent,
422 delivered: got,
423 inject,
424 total,
425 wait,
426 max_us: 0.0,
427 }
428 .print(size, depth, noise_kib, rep);
429 }
430 duo.barrier();
431 let stalled = ring.wait_ns();
432 let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433 if rank == 0 {
434 Row {
435 transport: name,
436 measure: "rtt",
437 payload,
438 sent: iters * 2,
439 delivered: iters * 2,
440 inject: dt,
441 total: dt,
442 wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443 max_us,
444 }
445 .print(size, depth, noise_kib, rep);
446 }
447 duo.barrier();
448 ring.close().unwrap();
449 }
450
451 for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452 let (sent, got, inject, total) =
453 p2p_stream(&duo, rank, rep, payload, n, buffered);
454 if rank == 0 {
455 Row {
456 transport: name,
457 measure: "stream",
458 payload,
459 sent,
460 delivered: got,
461 inject,
462 total,
463 wait: 0.0,
464 max_us: 0.0,
465 }
466 .print(size, depth, noise_kib, rep);
467 }
468 duo.barrier();
469 }
470 let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471 if rank == 0 {
472 Row {
473 transport: "p2p-send",
474 measure: "rtt",
475 payload,
476 sent: iters * 2,
477 delivered: iters * 2,
478 inject: dt,
479 total: dt,
480 wait: 0.0,
481 max_us,
482 }
483 .print(size, depth, noise_kib, rep);
484 }
485 duo.barrier();
486 }
487 }
488
489 if rank == 0 {
490 for r in 2..size {
491 world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492 }
493 }
494 });
495
496 // Buffered sends must be drained before MPI tears the buffer down.
497 world.barrier();
498}121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn is_safe(&self) -> bool
pub fn is_safe(&self) -> bool
Whether this ring gates overwrites with cumulative acknowledgements.
Examples found in repository?
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146 if rank == 0 {
147 assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148 }
149 world.barrier();
150 if rank == 1 {
151 let messages = ring.poll().unwrap();
152 assert_eq!(messages.len(), 1);
153 assert_eq!(messages[0].origin, 0);
154 assert_eq!(messages[0].data, vec![7]);
155 ring.ack(0, 1).unwrap();
156 }
157 world.barrier();
158 ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}Sourcepub fn depth(&self, destination: Rank) -> Option<usize>
pub fn depth(&self, destination: Rank) -> Option<usize>
Slot count configured for the outgoing lane to destination, if any.
Examples found in repository?
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}Sourcepub fn capacity(&self, destination: Rank) -> Option<usize>
pub fn capacity(&self, destination: Rank) -> Option<usize>
Per-slot payload capacity configured for the outgoing lane to destination, if any.
Examples found in repository?
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}Sourcepub fn lost(&self) -> u64
pub fn lost(&self) -> u64
Total messages observed as lost (raw mode only).
Examples found in repository?
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146 if rank == 0 {
147 assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148 }
149 world.barrier();
150 if rank == 1 {
151 let messages = ring.poll().unwrap();
152 assert_eq!(messages.len(), 1);
153 assert_eq!(messages[0].origin, 0);
154 assert_eq!(messages[0].data, vec![7]);
155 ring.ack(0, 1).unwrap();
156 }
157 world.barrier();
158 ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}More examples
121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn corrupt(&self) -> u64
pub fn corrupt(&self) -> u64
Total corrupt slot reads: torn header/footer, bad length, or CRC mismatch.
Examples found in repository?
121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn max_lag(&self) -> u64
pub fn max_lag(&self) -> u64
Greatest sequence advance one Self::poll made on one lane.
In raw mode this includes gaps counted as lost. In safe mode it is the number of messages drained.
Examples found in repository?
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}More examples
121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn waits(&self) -> u64
pub fn waits(&self) -> u64
Number of times a safe-mode sender actually blocked on acknowledgements.
Refreshing the cached counter does not count; only a sender that found no headroom after refreshing and had to spin does.
Examples found in repository?
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}More examples
121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn wait_ns(&self) -> u64
pub fn wait_ns(&self) -> u64
Cumulative nanoseconds spent in safe-mode wait spins.
Examples found in repository?
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}More examples
137fn ring_stream(
138 comm: &SimpleCommunicator,
139 ring: &Ring,
140 rank: Rank,
141 rep: u32,
142 payload: usize,
143 n: u64,
144) -> (u64, u64, f64, f64, f64) {
145 comm.barrier();
146 let stalled = ring.wait_ns();
147 let t0 = Instant::now();
148 if rank == 0 {
149 let buf = shuffled(rep, payload);
150 let mut sent = 0u64;
151 while sent < n {
152 ring.send(1, &buf).unwrap();
153 sent += 1;
154 }
155 let inject = t0.elapsed().as_secs_f64();
156 let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157 // Tell the drain side to stop, then collect what it actually saw.
158 comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159 let (reply, _) = comm
160 .process_at_rank(1)
161 .receive_vec_with_tag::<u64>(TAG_STOP);
162 (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163 } else {
164 let mut got = 0u64;
165 let mut stopping = false;
166 loop {
167 let batch = ring.poll().unwrap();
168 let empty = batch.is_empty();
169 let mut furthest = 0u64;
170 for m in &batch {
171 got += 1;
172 furthest = furthest.max(m.sequence);
173 }
174 if furthest > 0 {
175 ring.ack(0, furthest).unwrap();
176 }
177 if stopping && empty {
178 break;
179 }
180 if !stopping
181 && comm
182 .any_process()
183 .immediate_probe_with_tag(TAG_STOP)
184 .is_some()
185 {
186 comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187 stopping = true;
188 }
189 if empty {
190 std::thread::yield_now();
191 }
192 }
193 comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194 let dt = t0.elapsed().as_secs_f64();
195 (0, got, dt, dt, 0.0)
196 }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205 let buf = shuffled(rep, payload);
206 let other: Rank = if rank == 0 { 1 } else { 0 };
207 let mut seen = 0u64;
208 let await_one = |ring: &Ring, seen: &mut u64| {
209 loop {
210 let batch = ring.poll().unwrap();
211 if let Some(last) = batch.last() {
212 *seen = last.sequence;
213 ring.ack(other, *seen).unwrap();
214 return;
215 }
216 std::hint::spin_loop();
217 }
218 };
219 let mut max_us = 0.0f64;
220 let t0 = Instant::now();
221 for _ in 0..iters {
222 let start = Instant::now();
223 if rank == 0 {
224 ring.send(1, &buf).unwrap();
225 await_one(ring, &mut seen);
226 } else {
227 await_one(ring, &mut seen);
228 ring.send(0, &buf).unwrap();
229 }
230 max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231 }
232 (t0.elapsed().as_secs_f64(), max_us)
233}
234
235// ── Point to point ──────────────────────────────────────────────────────────
236
237fn p2p_stream(
238 comm: &SimpleCommunicator,
239 rank: Rank,
240 rep: u32,
241 payload: usize,
242 n: u64,
243 buffered: bool,
244) -> (u64, u64, f64, f64) {
245 comm.barrier();
246 let t0 = Instant::now();
247 if rank == 0 {
248 let buf = shuffled(rep, payload);
249 let peer = comm.process_at_rank(1);
250 let mut sent = 0u64;
251 while sent < n {
252 if buffered {
253 peer.buffered_send_with_tag(&buf[..], TAG_DATA);
254 } else {
255 peer.send_with_tag(&buf[..], TAG_DATA);
256 }
257 sent += 1;
258 }
259 let inject = t0.elapsed().as_secs_f64();
260 peer.send_with_tag(&[0u8], TAG_STOP);
261 // The receiver's count is authoritative and equals the sender's here:
262 // p2p does not drop. Waiting for it also drains the queue before the
263 // next measurement starts.
264 let (reply, _) = comm
265 .process_at_rank(1)
266 .receive_vec_with_tag::<u64>(TAG_STOP);
267 (sent, reply[0], inject, t0.elapsed().as_secs_f64())
268 } else {
269 let mut got = 0u64;
270 let source = comm.process_at_rank(0);
271 loop {
272 let (_, status) = source.receive_vec::<u8>();
273 if status.tag() == TAG_STOP {
274 break;
275 }
276 got += 1;
277 }
278 comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
279 let dt = t0.elapsed().as_secs_f64();
280 (got, got, dt, dt)
281 }
282}
283
284fn p2p_rtt(
285 comm: &SimpleCommunicator,
286 rank: Rank,
287 rep: u32,
288 payload: usize,
289 iters: u64,
290) -> (f64, f64) {
291 let buf = shuffled(rep, payload);
292 let peer = comm.process_at_rank(if rank == 0 { 1 } else { 0 });
293 let mut max_us = 0.0f64;
294 let t0 = Instant::now();
295 for _ in 0..iters {
296 let start = Instant::now();
297 if rank == 0 {
298 peer.send_with_tag(&buf[..], TAG_DATA);
299 peer.receive_vec_with_tag::<u8>(TAG_DATA);
300 } else {
301 peer.receive_vec_with_tag::<u8>(TAG_DATA);
302 peer.send_with_tag(&buf[..], TAG_DATA);
303 }
304 max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
305 }
306 (t0.elapsed().as_secs_f64(), max_us)
307}
308
309/// Background ping-pong among ranks 2+, in pairs, until `stop`.
310fn noise(world: &SimpleCommunicator, kib: usize, stop: &AtomicBool) {
311 let rank = world.rank();
312 let base = rank - 2;
313 let partner = if base % 2 == 0 { rank + 1 } else { rank - 1 };
314 if partner >= world.size() {
315 return;
316 }
317 let buf = vec![0xA5u8; kib * 1024];
318 let peer = world.process_at_rank(partner);
319 loop {
320 if rank < partner {
321 if stop.load(Ordering::Relaxed) {
322 peer.send_with_tag(&[0u8], TAG_QUIT);
323 return;
324 }
325 peer.send_with_tag(&buf[..], TAG_NOISE);
326 peer.receive_vec_with_tag::<u8>(TAG_NOISE);
327 } else {
328 let (_, status) = peer.receive_vec::<u8>();
329 if status.tag() == TAG_QUIT {
330 return;
331 }
332 peer.send_with_tag(&buf[..], TAG_NOISE);
333 }
334 }
335}
336
337fn main() {
338 let mut args = std::env::args().skip(1);
339 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342 let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344 let (mut universe, provided) =
345 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346 assert_eq!(provided, Threading::Multiple);
347 let world = universe.world();
348 let rank = world.rank();
349 let size = world.size();
350 assert!(size >= 2, "compare needs at least 2 ranks");
351 assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352 // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353 // only it attaches the buffer; every rank allocating 512 MiB would need
354 // tens of GiB at scale. Sized past the largest amount a stream can leave
355 // in flight, so the comparison measures the transport and not a buffer
356 // running out.
357 if rank == 0 {
358 universe.set_buffer_size(BSEND_BUF);
359 }
360
361 let stop = Arc::new(AtomicBool::new(false));
362 let noisy = noise_kib > 0;
363
364 std::thread::scope(|scope| {
365 if noisy && rank >= 2 {
366 let stop = Arc::clone(&stop);
367 let shared = Shared(&world);
368 scope.spawn(move || {
369 let shared = shared;
370 noise(shared.0, noise_kib, &stop);
371 });
372 }
373 if rank > 1 {
374 // Noise ranks take no part in the measurement. They wait here for
375 // rank 0 to call time, then let their noise thread finish.
376 world
377 .process_at_rank(0)
378 .receive_vec_with_tag::<u8>(TAG_QUIT);
379 stop.store(true, Ordering::Relaxed);
380 return;
381 }
382
383 if rank == 0 {
384 println!("{HEADER}");
385 }
386
387 // The measured pair only. A world barrier here would deadlock against
388 // ranks sitting in the noise loop.
389 let pair: Vec<Rank> = vec![0, 1];
390 let group = world.group().include(&pair);
391 let duo = world
392 .split_by_subgroup(&group)
393 .expect("ranks 0 and 1 form the measured pair");
394
395 for rep in 0..repeats {
396 for payload in SIZES {
397 // Both ring modes share the lane shape, so the only difference
398 // between the two rows is the acknowledgement gate.
399 let lanes = vec![
400 (0 as Rank, 1 as Rank, depth, payload),
401 (1, 0, depth, payload),
402 ];
403 // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404 // 5 GiB through the wire for one row.
405 let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406 let iters = if payload >= 32768 { 500 } else { 2000 };
407
408 for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409 let ring = if safe {
410 Ring::safe(&duo, &lanes).unwrap()
411 } else {
412 Ring::raw(&duo, &lanes).unwrap()
413 };
414 let (sent, got, inject, total, wait) =
415 ring_stream(&duo, &ring, rank, rep, payload, n);
416 if rank == 0 {
417 Row {
418 transport: name,
419 measure: "stream",
420 payload,
421 sent,
422 delivered: got,
423 inject,
424 total,
425 wait,
426 max_us: 0.0,
427 }
428 .print(size, depth, noise_kib, rep);
429 }
430 duo.barrier();
431 let stalled = ring.wait_ns();
432 let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433 if rank == 0 {
434 Row {
435 transport: name,
436 measure: "rtt",
437 payload,
438 sent: iters * 2,
439 delivered: iters * 2,
440 inject: dt,
441 total: dt,
442 wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443 max_us,
444 }
445 .print(size, depth, noise_kib, rep);
446 }
447 duo.barrier();
448 ring.close().unwrap();
449 }
450
451 for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452 let (sent, got, inject, total) =
453 p2p_stream(&duo, rank, rep, payload, n, buffered);
454 if rank == 0 {
455 Row {
456 transport: name,
457 measure: "stream",
458 payload,
459 sent,
460 delivered: got,
461 inject,
462 total,
463 wait: 0.0,
464 max_us: 0.0,
465 }
466 .print(size, depth, noise_kib, rep);
467 }
468 duo.barrier();
469 }
470 let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471 if rank == 0 {
472 Row {
473 transport: "p2p-send",
474 measure: "rtt",
475 payload,
476 sent: iters * 2,
477 delivered: iters * 2,
478 inject: dt,
479 total: dt,
480 wait: 0.0,
481 max_us,
482 }
483 .print(size, depth, noise_kib, rep);
484 }
485 duo.barrier();
486 }
487 }
488
489 if rank == 0 {
490 for r in 2..size {
491 world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492 }
493 }
494 });
495
496 // Buffered sends must be drained before MPI tears the buffer down.
497 world.barrier();
498}121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn send(&self, destination: Rank, data: &[u8]) -> Result<u64, Error>
pub fn send(&self, destination: Rank, data: &[u8]) -> Result<u64, Error>
Send one message and return its sequence number.
Completes the put at the target before returning. In safe mode,
spins on yield_now if the destination has not yet acknowledged
enough earlier messages to make room in the slot ring; the wait is
recorded in Self::waits and Self::wait_ns.
§Errors
Error::Rankifdestinationis outside the communicator.Error::Ringif no lane todestinationis configured, the sequence number would overflow, the acknowledgement counter regresses or exceeds what was sent, or the send state is poisoned.Error::Payloadifdatadoes not fit the lane’s capacity.- Whatever the underlying
MPI_Putfails with (wrapped), e.g.Error::MpiorError::Range.
Examples found in repository?
20fn run(world: &SimpleCommunicator, safe: bool) {
21 let rank = world.rank();
22 let ring = if safe {
23 Ring::safe(world, &lanes(2, 8)).unwrap()
24 } else {
25 Ring::raw(world, &lanes(2, 8)).unwrap()
26 };
27
28 if rank < 2 {
29 ring.send(2, &[rank as u8, 1]).unwrap();
30 }
31 world.barrier();
32
33 if rank == 2 {
34 let messages = ring.poll().unwrap();
35 assert_eq!(messages.len(), 2);
36 assert_eq!(messages[0].origin, 0);
37 assert_eq!(messages[0].data, vec![0, 1]);
38 assert_eq!(messages[1].origin, 1);
39 assert_eq!(messages[1].data, vec![1, 1]);
40 ring.ack(0, 1).unwrap();
41 ring.ack(1, 1).unwrap();
42 ring.send(0, &[2, 1]).unwrap();
43 ring.send(1, &[2, 1]).unwrap();
44 }
45 world.barrier();
46
47 if rank < 2 {
48 let messages = ring.poll().unwrap();
49 assert_eq!(
50 messages,
51 vec![Message {
52 origin: 2,
53 sequence: 1,
54 data: vec![2, 1]
55 }]
56 );
57 ring.ack(2, 1).unwrap();
58 }
59 world.barrier();
60
61 ring.close().unwrap();
62}More examples
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146 if rank == 0 {
147 assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148 }
149 world.barrier();
150 if rank == 1 {
151 let messages = ring.poll().unwrap();
152 assert_eq!(messages.len(), 1);
153 assert_eq!(messages[0].origin, 0);
154 assert_eq!(messages[0].data, vec![7]);
155 ring.ack(0, 1).unwrap();
156 }
157 world.barrier();
158 ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}137fn ring_stream(
138 comm: &SimpleCommunicator,
139 ring: &Ring,
140 rank: Rank,
141 rep: u32,
142 payload: usize,
143 n: u64,
144) -> (u64, u64, f64, f64, f64) {
145 comm.barrier();
146 let stalled = ring.wait_ns();
147 let t0 = Instant::now();
148 if rank == 0 {
149 let buf = shuffled(rep, payload);
150 let mut sent = 0u64;
151 while sent < n {
152 ring.send(1, &buf).unwrap();
153 sent += 1;
154 }
155 let inject = t0.elapsed().as_secs_f64();
156 let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157 // Tell the drain side to stop, then collect what it actually saw.
158 comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159 let (reply, _) = comm
160 .process_at_rank(1)
161 .receive_vec_with_tag::<u64>(TAG_STOP);
162 (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163 } else {
164 let mut got = 0u64;
165 let mut stopping = false;
166 loop {
167 let batch = ring.poll().unwrap();
168 let empty = batch.is_empty();
169 let mut furthest = 0u64;
170 for m in &batch {
171 got += 1;
172 furthest = furthest.max(m.sequence);
173 }
174 if furthest > 0 {
175 ring.ack(0, furthest).unwrap();
176 }
177 if stopping && empty {
178 break;
179 }
180 if !stopping
181 && comm
182 .any_process()
183 .immediate_probe_with_tag(TAG_STOP)
184 .is_some()
185 {
186 comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187 stopping = true;
188 }
189 if empty {
190 std::thread::yield_now();
191 }
192 }
193 comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194 let dt = t0.elapsed().as_secs_f64();
195 (0, got, dt, dt, 0.0)
196 }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205 let buf = shuffled(rep, payload);
206 let other: Rank = if rank == 0 { 1 } else { 0 };
207 let mut seen = 0u64;
208 let await_one = |ring: &Ring, seen: &mut u64| {
209 loop {
210 let batch = ring.poll().unwrap();
211 if let Some(last) = batch.last() {
212 *seen = last.sequence;
213 ring.ack(other, *seen).unwrap();
214 return;
215 }
216 std::hint::spin_loop();
217 }
218 };
219 let mut max_us = 0.0f64;
220 let t0 = Instant::now();
221 for _ in 0..iters {
222 let start = Instant::now();
223 if rank == 0 {
224 ring.send(1, &buf).unwrap();
225 await_one(ring, &mut seen);
226 } else {
227 await_one(ring, &mut seen);
228 ring.send(0, &buf).unwrap();
229 }
230 max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231 }
232 (t0.elapsed().as_secs_f64(), max_us)
233}121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn poll(&self) -> Result<Vec<Message>, Error>
pub fn poll(&self) -> Result<Vec<Message>, Error>
Drain every incoming lane and return newly observed messages.
Does not call MPI. Safe mode delivers in-order; raw mode scans all
slots when one is lapped, sorts the survivors, and counts the gaps
into Self::lost. The per-origin seen cursor only advances.
§Errors
Error::Lappedif a safe lane was overwritten before consumption, which the acknowledge gate makes impossible and so indicates a corrupted counter or window rather than backpressure.- Any window read error from the local slot memory.
Examples found in repository?
20fn run(world: &SimpleCommunicator, safe: bool) {
21 let rank = world.rank();
22 let ring = if safe {
23 Ring::safe(world, &lanes(2, 8)).unwrap()
24 } else {
25 Ring::raw(world, &lanes(2, 8)).unwrap()
26 };
27
28 if rank < 2 {
29 ring.send(2, &[rank as u8, 1]).unwrap();
30 }
31 world.barrier();
32
33 if rank == 2 {
34 let messages = ring.poll().unwrap();
35 assert_eq!(messages.len(), 2);
36 assert_eq!(messages[0].origin, 0);
37 assert_eq!(messages[0].data, vec![0, 1]);
38 assert_eq!(messages[1].origin, 1);
39 assert_eq!(messages[1].data, vec![1, 1]);
40 ring.ack(0, 1).unwrap();
41 ring.ack(1, 1).unwrap();
42 ring.send(0, &[2, 1]).unwrap();
43 ring.send(1, &[2, 1]).unwrap();
44 }
45 world.barrier();
46
47 if rank < 2 {
48 let messages = ring.poll().unwrap();
49 assert_eq!(
50 messages,
51 vec![Message {
52 origin: 2,
53 sequence: 1,
54 data: vec![2, 1]
55 }]
56 );
57 ring.ack(2, 1).unwrap();
58 }
59 world.barrier();
60
61 ring.close().unwrap();
62}More examples
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146 if rank == 0 {
147 assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148 }
149 world.barrier();
150 if rank == 1 {
151 let messages = ring.poll().unwrap();
152 assert_eq!(messages.len(), 1);
153 assert_eq!(messages[0].origin, 0);
154 assert_eq!(messages[0].data, vec![7]);
155 ring.ack(0, 1).unwrap();
156 }
157 world.barrier();
158 ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}137fn ring_stream(
138 comm: &SimpleCommunicator,
139 ring: &Ring,
140 rank: Rank,
141 rep: u32,
142 payload: usize,
143 n: u64,
144) -> (u64, u64, f64, f64, f64) {
145 comm.barrier();
146 let stalled = ring.wait_ns();
147 let t0 = Instant::now();
148 if rank == 0 {
149 let buf = shuffled(rep, payload);
150 let mut sent = 0u64;
151 while sent < n {
152 ring.send(1, &buf).unwrap();
153 sent += 1;
154 }
155 let inject = t0.elapsed().as_secs_f64();
156 let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157 // Tell the drain side to stop, then collect what it actually saw.
158 comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159 let (reply, _) = comm
160 .process_at_rank(1)
161 .receive_vec_with_tag::<u64>(TAG_STOP);
162 (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163 } else {
164 let mut got = 0u64;
165 let mut stopping = false;
166 loop {
167 let batch = ring.poll().unwrap();
168 let empty = batch.is_empty();
169 let mut furthest = 0u64;
170 for m in &batch {
171 got += 1;
172 furthest = furthest.max(m.sequence);
173 }
174 if furthest > 0 {
175 ring.ack(0, furthest).unwrap();
176 }
177 if stopping && empty {
178 break;
179 }
180 if !stopping
181 && comm
182 .any_process()
183 .immediate_probe_with_tag(TAG_STOP)
184 .is_some()
185 {
186 comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187 stopping = true;
188 }
189 if empty {
190 std::thread::yield_now();
191 }
192 }
193 comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194 let dt = t0.elapsed().as_secs_f64();
195 (0, got, dt, dt, 0.0)
196 }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205 let buf = shuffled(rep, payload);
206 let other: Rank = if rank == 0 { 1 } else { 0 };
207 let mut seen = 0u64;
208 let await_one = |ring: &Ring, seen: &mut u64| {
209 loop {
210 let batch = ring.poll().unwrap();
211 if let Some(last) = batch.last() {
212 *seen = last.sequence;
213 ring.ack(other, *seen).unwrap();
214 return;
215 }
216 std::hint::spin_loop();
217 }
218 };
219 let mut max_us = 0.0f64;
220 let t0 = Instant::now();
221 for _ in 0..iters {
222 let start = Instant::now();
223 if rank == 0 {
224 ring.send(1, &buf).unwrap();
225 await_one(ring, &mut seen);
226 } else {
227 await_one(ring, &mut seen);
228 ring.send(0, &buf).unwrap();
229 }
230 max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231 }
232 (t0.elapsed().as_secs_f64(), max_us)
233}121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn ack(&self, origin: Rank, sequence: u64) -> Result<(), Error>
pub fn ack(&self, origin: Rank, sequence: u64) -> Result<(), Error>
Acknowledge all messages from origin through sequence.
Cumulative and idempotent. In safe mode performs one atomic
MPI_Fetch_and_op against origin’s counter window. In raw mode
this is a no-op (returns Ok).
§Errors
Error::Rankiforiginis outside the communicator.Error::Ringif no incoming lane fromoriginis configured, or the counter diverges from the locally tracked value.Error::Ackifsequenceis past the last received message.
Examples found in repository?
20fn run(world: &SimpleCommunicator, safe: bool) {
21 let rank = world.rank();
22 let ring = if safe {
23 Ring::safe(world, &lanes(2, 8)).unwrap()
24 } else {
25 Ring::raw(world, &lanes(2, 8)).unwrap()
26 };
27
28 if rank < 2 {
29 ring.send(2, &[rank as u8, 1]).unwrap();
30 }
31 world.barrier();
32
33 if rank == 2 {
34 let messages = ring.poll().unwrap();
35 assert_eq!(messages.len(), 2);
36 assert_eq!(messages[0].origin, 0);
37 assert_eq!(messages[0].data, vec![0, 1]);
38 assert_eq!(messages[1].origin, 1);
39 assert_eq!(messages[1].data, vec![1, 1]);
40 ring.ack(0, 1).unwrap();
41 ring.ack(1, 1).unwrap();
42 ring.send(0, &[2, 1]).unwrap();
43 ring.send(1, &[2, 1]).unwrap();
44 }
45 world.barrier();
46
47 if rank < 2 {
48 let messages = ring.poll().unwrap();
49 assert_eq!(
50 messages,
51 vec![Message {
52 origin: 2,
53 sequence: 1,
54 data: vec![2, 1]
55 }]
56 );
57 ring.ack(2, 1).unwrap();
58 }
59 world.barrier();
60
61 ring.close().unwrap();
62}More examples
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146 if rank == 0 {
147 assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148 }
149 world.barrier();
150 if rank == 1 {
151 let messages = ring.poll().unwrap();
152 assert_eq!(messages.len(), 1);
153 assert_eq!(messages[0].origin, 0);
154 assert_eq!(messages[0].data, vec![7]);
155 ring.ack(0, 1).unwrap();
156 }
157 world.barrier();
158 ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}137fn ring_stream(
138 comm: &SimpleCommunicator,
139 ring: &Ring,
140 rank: Rank,
141 rep: u32,
142 payload: usize,
143 n: u64,
144) -> (u64, u64, f64, f64, f64) {
145 comm.barrier();
146 let stalled = ring.wait_ns();
147 let t0 = Instant::now();
148 if rank == 0 {
149 let buf = shuffled(rep, payload);
150 let mut sent = 0u64;
151 while sent < n {
152 ring.send(1, &buf).unwrap();
153 sent += 1;
154 }
155 let inject = t0.elapsed().as_secs_f64();
156 let wait = (ring.wait_ns() - stalled) as f64 * 1e-9;
157 // Tell the drain side to stop, then collect what it actually saw.
158 comm.process_at_rank(1).send_with_tag(&[0u8], TAG_STOP);
159 let (reply, _) = comm
160 .process_at_rank(1)
161 .receive_vec_with_tag::<u64>(TAG_STOP);
162 (sent, reply[0], inject, t0.elapsed().as_secs_f64(), wait)
163 } else {
164 let mut got = 0u64;
165 let mut stopping = false;
166 loop {
167 let batch = ring.poll().unwrap();
168 let empty = batch.is_empty();
169 let mut furthest = 0u64;
170 for m in &batch {
171 got += 1;
172 furthest = furthest.max(m.sequence);
173 }
174 if furthest > 0 {
175 ring.ack(0, furthest).unwrap();
176 }
177 if stopping && empty {
178 break;
179 }
180 if !stopping
181 && comm
182 .any_process()
183 .immediate_probe_with_tag(TAG_STOP)
184 .is_some()
185 {
186 comm.any_process().receive_vec_with_tag::<u8>(TAG_STOP);
187 stopping = true;
188 }
189 if empty {
190 std::thread::yield_now();
191 }
192 }
193 comm.process_at_rank(0).send_with_tag(&[got][..], TAG_STOP);
194 let dt = t0.elapsed().as_secs_f64();
195 (0, got, dt, dt, 0.0)
196 }
197}
198
199/// One message each way, `iters` times, on the ring.
200///
201/// Returns `(total, max_us)`: the run wall time and the worst single
202/// round trip, halved to a one-way latency so it shares the `us_per_msg`
203/// unit of the mean.
204fn ring_rtt(ring: &Ring, rank: Rank, rep: u32, payload: usize, iters: u64) -> (f64, f64) {
205 let buf = shuffled(rep, payload);
206 let other: Rank = if rank == 0 { 1 } else { 0 };
207 let mut seen = 0u64;
208 let await_one = |ring: &Ring, seen: &mut u64| {
209 loop {
210 let batch = ring.poll().unwrap();
211 if let Some(last) = batch.last() {
212 *seen = last.sequence;
213 ring.ack(other, *seen).unwrap();
214 return;
215 }
216 std::hint::spin_loop();
217 }
218 };
219 let mut max_us = 0.0f64;
220 let t0 = Instant::now();
221 for _ in 0..iters {
222 let start = Instant::now();
223 if rank == 0 {
224 ring.send(1, &buf).unwrap();
225 await_one(ring, &mut seen);
226 } else {
227 await_one(ring, &mut seen);
228 ring.send(0, &buf).unwrap();
229 }
230 max_us = max_us.max(start.elapsed().as_secs_f64() * 5e5);
231 }
232 (t0.elapsed().as_secs_f64(), max_us)
233}121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}Sourcepub fn close(self) -> Result<(), Error>
pub fn close(self) -> Result<(), Error>
Close the underlying windows. Collective over the ring communicator.
Prefer this over relying on Drop: the destructor’s shutdown runs
the same steps but the collective boundary is implicit.
Examples found in repository?
20fn run(world: &SimpleCommunicator, safe: bool) {
21 let rank = world.rank();
22 let ring = if safe {
23 Ring::safe(world, &lanes(2, 8)).unwrap()
24 } else {
25 Ring::raw(world, &lanes(2, 8)).unwrap()
26 };
27
28 if rank < 2 {
29 ring.send(2, &[rank as u8, 1]).unwrap();
30 }
31 world.barrier();
32
33 if rank == 2 {
34 let messages = ring.poll().unwrap();
35 assert_eq!(messages.len(), 2);
36 assert_eq!(messages[0].origin, 0);
37 assert_eq!(messages[0].data, vec![0, 1]);
38 assert_eq!(messages[1].origin, 1);
39 assert_eq!(messages[1].data, vec![1, 1]);
40 ring.ack(0, 1).unwrap();
41 ring.ack(1, 1).unwrap();
42 ring.send(0, &[2, 1]).unwrap();
43 ring.send(1, &[2, 1]).unwrap();
44 }
45 world.barrier();
46
47 if rank < 2 {
48 let messages = ring.poll().unwrap();
49 assert_eq!(
50 messages,
51 vec![Message {
52 origin: 2,
53 sequence: 1,
54 data: vec![2, 1]
55 }]
56 );
57 ring.ack(2, 1).unwrap();
58 }
59 world.barrier();
60
61 ring.close().unwrap();
62}More examples
21fn safe_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
22 // Depth 4 against 3 messages: this covers ordering and acknowledge
23 // validation with the overwrite gate deliberately out of the way.
24 // Backpressure is its own test, on a one-way lane.
25 let ring = Ring::safe(world, &pair(4, 8)).unwrap();
26 assert!(ring.is_safe());
27 assert_eq!(ring.depth(next), Some(4));
28 assert_eq!(ring.capacity(next), Some(8));
29
30 assert!(matches!(
31 ring.send(next, &[0; 9]),
32 Err(Error::Payload {
33 len: 9,
34 capacity: 8
35 })
36 ));
37 assert!(matches!(
38 ring.ack(prev, 1),
39 Err(Error::Ack {
40 sequence: 1,
41 received: 0,
42 ..
43 })
44 ));
45
46 for s in 1..=3u8 {
47 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
48 }
49 world.barrier();
50
51 let messages = ring.poll().unwrap();
52 assert_eq!(
53 messages,
54 (1..=3)
55 .map(|s| Message {
56 origin: prev,
57 sequence: s,
58 data: vec![prev as u8, s as u8],
59 })
60 .collect::<Vec<_>>()
61 );
62 assert!(ring.poll().unwrap().is_empty());
63 assert_eq!(ring.lost(), 0);
64 assert_eq!(ring.max_lag(), 3);
65
66 assert!(matches!(
67 ring.ack(prev, 4),
68 Err(Error::Ack {
69 sequence: 4,
70 received: 3,
71 ..
72 })
73 ));
74 ring.ack(prev, 3).unwrap();
75 ring.ack(prev, 3).unwrap();
76 ring.ack(prev, 2).unwrap();
77
78 world.barrier();
79 ring.close().unwrap();
80}
81
82/// A safe sender with no free slot blocks until the receiver acknowledges.
83///
84/// One-way, so the ack timing belongs to the test. On a symmetric pair each
85/// rank's ack races the other's gate check: whichever receiver drains first
86/// opens its peer's gate before that peer ever evaluates it, and neither side
87/// can be relied on to block.
88fn backpressure(world: &SimpleCommunicator, rank: Rank) {
89 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
90 if rank == 0 {
91 assert_eq!(ring.send(1, &[1]).unwrap(), 1);
92 assert_eq!(ring.send(1, &[2]).unwrap(), 2);
93 }
94 world.barrier();
95
96 if rank == 0 {
97 let sent = thread::scope(|scope| {
98 let sender = ˚
99 let handle = scope.spawn(move || sender.send(1, &[3]));
100 // Depth is 2 and nothing has been acknowledged, so there is no slot
101 // for the third message and the send has to wait.
102 let deadline = Instant::now() + Duration::from_secs(5);
103 while ring.waits() == 0 {
104 assert!(Instant::now() < deadline, "safe sender never blocked");
105 thread::yield_now();
106 }
107 // The receiver is parked here, so it cannot have drained early.
108 world.barrier();
109 handle.join().expect("sender thread panicked").unwrap()
110 });
111 assert_eq!(sent, 3);
112 assert!(ring.waits() > 0);
113 assert!(ring.wait_ns() > 0);
114 } else {
115 world.barrier();
116 let messages = ring.poll().unwrap();
117 assert_eq!(messages.len(), 2);
118 assert_eq!(messages[0].data, vec![1]);
119 assert_eq!(messages[1].data, vec![2]);
120 assert!(matches!(
121 ring.ack(0, 3),
122 Err(Error::Ack {
123 sequence: 3,
124 received: 2,
125 ..
126 })
127 ));
128 ring.ack(0, 2).unwrap();
129 ring.ack(0, 2).unwrap();
130 }
131
132 world.barrier();
133 if rank == 1 {
134 let messages = ring.poll().unwrap();
135 assert_eq!(messages.len(), 1);
136 assert_eq!(messages[0].sequence, 3);
137 assert_eq!(messages[0].data, vec![3]);
138 ring.ack(0, 3).unwrap();
139 }
140 world.barrier();
141 ring.close().unwrap();
142}
143
144fn oneway_basics(world: &SimpleCommunicator, rank: Rank) {
145 let ring = Ring::safe(world, &oneway(2, 8)).unwrap();
146 if rank == 0 {
147 assert_eq!(ring.send(1, &[7]).unwrap(), 1);
148 }
149 world.barrier();
150 if rank == 1 {
151 let messages = ring.poll().unwrap();
152 assert_eq!(messages.len(), 1);
153 assert_eq!(messages[0].origin, 0);
154 assert_eq!(messages[0].data, vec![7]);
155 ring.ack(0, 1).unwrap();
156 }
157 world.barrier();
158 ring.close().unwrap();
159}
160
161fn raw_basics(world: &SimpleCommunicator, rank: Rank, next: Rank, prev: Rank) {
162 let ring = Ring::raw(world, &pair(2, 8)).unwrap();
163 assert!(!ring.is_safe());
164
165 for s in 1..=3u8 {
166 assert_eq!(ring.send(next, &[rank as u8, s]).unwrap(), u64::from(s));
167 }
168 world.barrier();
169
170 let messages = ring.poll().unwrap();
171 assert_eq!(
172 messages,
173 vec![
174 Message {
175 origin: prev,
176 sequence: 2,
177 data: vec![prev as u8, 2]
178 },
179 Message {
180 origin: prev,
181 sequence: 3,
182 data: vec![prev as u8, 3]
183 },
184 ]
185 );
186 assert_eq!(ring.lost(), 1);
187
188 // Raw acks are no-ops.
189 ring.ack(prev, u64::MAX).unwrap();
190 world.barrier();
191 ring.close().unwrap();
192}337fn main() {
338 let mut args = std::env::args().skip(1);
339 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
340 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(32);
341 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
342 let repeats: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(5);
343
344 let (mut universe, provided) =
345 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
346 assert_eq!(provided, Threading::Multiple);
347 let world = universe.world();
348 let rank = world.rank();
349 let size = world.size();
350 assert!(size >= 2, "compare needs at least 2 ranks");
351 assert!(noise_kib == 0 || size >= 4, "noise needs at least 4 ranks");
352 // `p2p-bsend` needs somewhere to copy to. Only rank 0 sends buffered, so
353 // only it attaches the buffer; every rank allocating 512 MiB would need
354 // tens of GiB at scale. Sized past the largest amount a stream can leave
355 // in flight, so the comparison measures the transport and not a buffer
356 // running out.
357 if rank == 0 {
358 universe.set_buffer_size(BSEND_BUF);
359 }
360
361 let stop = Arc::new(AtomicBool::new(false));
362 let noisy = noise_kib > 0;
363
364 std::thread::scope(|scope| {
365 if noisy && rank >= 2 {
366 let stop = Arc::clone(&stop);
367 let shared = Shared(&world);
368 scope.spawn(move || {
369 let shared = shared;
370 noise(shared.0, noise_kib, &stop);
371 });
372 }
373 if rank > 1 {
374 // Noise ranks take no part in the measurement. They wait here for
375 // rank 0 to call time, then let their noise thread finish.
376 world
377 .process_at_rank(0)
378 .receive_vec_with_tag::<u8>(TAG_QUIT);
379 stop.store(true, Ordering::Relaxed);
380 return;
381 }
382
383 if rank == 0 {
384 println!("{HEADER}");
385 }
386
387 // The measured pair only. A world barrier here would deadlock against
388 // ranks sitting in the noise loop.
389 let pair: Vec<Rank> = vec![0, 1];
390 let group = world.group().include(&pair);
391 let duo = world
392 .split_by_subgroup(&group)
393 .expect("ranks 0 and 1 form the measured pair");
394
395 for rep in 0..repeats {
396 for payload in SIZES {
397 // Both ring modes share the lane shape, so the only difference
398 // between the two rows is the acknowledgement gate.
399 let lanes = vec![
400 (0 as Rank, 1 as Rank, depth, payload),
401 (1, 0, depth, payload),
402 ];
403 // Cap the stream by bytes as well as count: 256 KiB x 20k would be
404 // 5 GiB through the wire for one row.
405 let n = messages.min((STREAM_BYTES / payload as u64).max(64));
406 let iters = if payload >= 32768 { 500 } else { 2000 };
407
408 for (name, safe) in [("ring-safe", true), ("ring-raw", false)] {
409 let ring = if safe {
410 Ring::safe(&duo, &lanes).unwrap()
411 } else {
412 Ring::raw(&duo, &lanes).unwrap()
413 };
414 let (sent, got, inject, total, wait) =
415 ring_stream(&duo, &ring, rank, rep, payload, n);
416 if rank == 0 {
417 Row {
418 transport: name,
419 measure: "stream",
420 payload,
421 sent,
422 delivered: got,
423 inject,
424 total,
425 wait,
426 max_us: 0.0,
427 }
428 .print(size, depth, noise_kib, rep);
429 }
430 duo.barrier();
431 let stalled = ring.wait_ns();
432 let (dt, max_us) = ring_rtt(&ring, rank, rep, payload, iters);
433 if rank == 0 {
434 Row {
435 transport: name,
436 measure: "rtt",
437 payload,
438 sent: iters * 2,
439 delivered: iters * 2,
440 inject: dt,
441 total: dt,
442 wait: (ring.wait_ns() - stalled) as f64 * 1e-9,
443 max_us,
444 }
445 .print(size, depth, noise_kib, rep);
446 }
447 duo.barrier();
448 ring.close().unwrap();
449 }
450
451 for (name, buffered) in [("p2p-send", false), ("p2p-bsend", true)] {
452 let (sent, got, inject, total) =
453 p2p_stream(&duo, rank, rep, payload, n, buffered);
454 if rank == 0 {
455 Row {
456 transport: name,
457 measure: "stream",
458 payload,
459 sent,
460 delivered: got,
461 inject,
462 total,
463 wait: 0.0,
464 max_us: 0.0,
465 }
466 .print(size, depth, noise_kib, rep);
467 }
468 duo.barrier();
469 }
470 let (dt, max_us) = p2p_rtt(&duo, rank, rep, payload, iters);
471 if rank == 0 {
472 Row {
473 transport: "p2p-send",
474 measure: "rtt",
475 payload,
476 sent: iters * 2,
477 delivered: iters * 2,
478 inject: dt,
479 total: dt,
480 wait: 0.0,
481 max_us,
482 }
483 .print(size, depth, noise_kib, rep);
484 }
485 duo.barrier();
486 }
487 }
488
489 if rank == 0 {
490 for r in 2..size {
491 world.process_at_rank(r).send_with_tag(&[0u8], TAG_QUIT);
492 }
493 }
494 });
495
496 // Buffered sends must be drained before MPI tears the buffer down.
497 world.barrier();
498}121fn main() {
122 let mut args = std::env::args().skip(1);
123 let mode = args.next().unwrap_or_else(|| "safe".into());
124 let messages: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(20_000);
125 let payload: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(256);
126 let depth: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
127 let noise_kib: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
128 let pace_ns: u64 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
129 // Echoed straight into the output so repeats of one point stay distinguishable.
130 let rep: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
131 assert!(
132 payload >= STAMP,
133 "payload must hold the origin and sequence"
134 );
135 let safe = match mode.as_str() {
136 "safe" => true,
137 "raw" => false,
138 other => panic!("mode must be safe or raw, got {other:?}"),
139 };
140
141 let (universe, provided) =
142 mpi::initialize_with_threading(Threading::Multiple).expect("MPI must initialize once");
143 assert_eq!(provided, Threading::Multiple);
144 let world = universe.world();
145 let rank = world.rank();
146 let size = world.size();
147 assert!(size >= 2, "soak needs at least 2 ranks");
148
149 // All-to-all: every ordered pair of distinct ranks gets a lane.
150 let mut lanes = Vec::new();
151 for source in 0..size {
152 for destination in 0..size {
153 if source != destination {
154 lanes.push((source, destination, depth, payload));
155 }
156 }
157 }
158 let ring = Arc::new(if safe {
159 Ring::safe(&world, &lanes).unwrap()
160 } else {
161 Ring::raw(&world, &lanes).unwrap()
162 });
163
164 let stop = Arc::new(AtomicBool::new(false));
165 let done = Arc::new(AtomicBool::new(false));
166 let count = Arc::new(AtomicU64::new(0));
167 let peers = (size - 1) as u64;
168 let sent = messages * peers;
169
170 world.barrier();
171 let started = std::time::Instant::now();
172
173 let outcome = std::thread::scope(|scope| {
174 if noise_kib > 0 {
175 let stop = Arc::clone(&stop);
176 let shared = Shared(&world);
177 scope.spawn(move || {
178 // Name the wrapper as a whole first. Closures capture the
179 // narrowest place they use, and `shared.0` alone would be
180 // captured as a bare reference, losing the `Send` impl.
181 let shared = shared;
182 noise(shared.0, noise_kib, &stop);
183 });
184 }
185
186 let poller = {
187 let (ring, done, count) = (Arc::clone(&ring), Arc::clone(&done), Arc::clone(&count));
188 scope.spawn(move || -> Result<Vec<u64>, String> {
189 let mut seen = vec![0u64; size as usize];
190 let mut total = 0u64;
191 loop {
192 // If true, this poll follows the global send barrier.
193 let finished = done.load(Ordering::Acquire);
194 let batch = ring.poll().map_err(|e| format!("poll: {e}"))?;
195 let empty = batch.is_empty();
196 let mut furthest: HashMap<Rank, u64> = HashMap::new();
197 for m in &batch {
198 verify(m)?;
199 let previous = seen[m.origin as usize];
200 if m.sequence <= previous {
201 return Err(format!(
202 "sequence from {} went backwards: {} after {previous}",
203 m.origin, m.sequence
204 ));
205 }
206 if safe && m.sequence != previous + 1 {
207 return Err(format!(
208 "safe lane from {} skipped {} to {}",
209 m.origin,
210 previous + 1,
211 m.sequence
212 ));
213 }
214 seen[m.origin as usize] = m.sequence;
215 let f = furthest.entry(m.origin).or_insert(0);
216 *f = (*f).max(m.sequence);
217 total += 1;
218 }
219 for (origin, sequence) in furthest {
220 ring.ack(origin, sequence)
221 .map_err(|e| format!("ack to {origin}: {e}"))?;
222 }
223 count.store(total, Ordering::Relaxed);
224 if finished && empty {
225 return Ok(seen);
226 }
227 if empty {
228 std::thread::yield_now();
229 }
230 }
231 })
232 };
233
234 // Round-robin over peers so every outgoing lane stays hot at once.
235 let mut buf = vec![0u8; payload];
236 let pace = std::time::Duration::from_nanos(pace_ns);
237 let opened = std::time::Instant::now();
238 for sequence in 1..=messages {
239 for destination in (0..size).filter(|&r| r != rank) {
240 paint(rank, sequence, &mut buf);
241 ring.send(destination, &buf)
242 .unwrap_or_else(|e| panic!("rank {rank}: send to {destination}: {e}"));
243 }
244 // Absolute deadlines, so a slow round is not paid for twice and the
245 // offered rate stays the one that was asked for.
246 if pace_ns > 0 {
247 let due = opened + pace * sequence as u32;
248 while std::time::Instant::now() < due {
249 std::hint::spin_loop();
250 }
251 }
252 }
253 // Every rank's sends have completed at their targets before this
254 // returns, so after the barrier nothing new can appear in a slot.
255 world.barrier();
256 done.store(true, Ordering::Release);
257
258 let seen = poller.join().expect("poller thread panicked");
259 stop.store(true, Ordering::Relaxed);
260 seen
261 });
262
263 let elapsed = started.elapsed().as_secs_f64();
264 let fail = |why: String| -> ! {
265 eprintln!("[soak] FAIL rank {rank}: {why}");
266 world.abort(1)
267 };
268 let seen = outcome.unwrap_or_else(|why| fail(why));
269
270 let got = count.load(Ordering::Relaxed);
271 let lost = ring.lost();
272 let corrupt = ring.corrupt();
273
274 // The last message on a lane is never overwritten, so every lane must end
275 // exactly at the sender's high-water mark in both modes.
276 for origin in (0..size).filter(|&r| r != rank) {
277 if seen[origin as usize] != messages {
278 fail(format!(
279 "lane from {origin} ended at {} not {messages}",
280 seen[origin as usize]
281 ));
282 }
283 }
284 if safe && lost != 0 {
285 fail(format!("safe ring lost {lost}"));
286 }
287 if got + lost != sent {
288 fail(format!("received {got} + lost {lost} != sent {sent}"));
289 }
290
291 // Gather the counters and let rank 0 write every row. Interleaving the
292 // ranks' own stdout would scatter the header into the middle of the table:
293 // mpirun orders a rank's output against itself and nothing else.
294 let mine = [
295 got,
296 lost,
297 corrupt,
298 ring.max_lag(),
299 ring.waits(),
300 ring.wait_ns(),
301 ];
302 let mut all = vec![0u64; mine.len() * size as usize];
303 world.all_gather_into(&mine[..], &mut all[..]);
304 if rank == 0 {
305 println!(
306 "mode\tranks\tdepth\tpayload\tnoise_kib\tpace_ns\trep\trank\tsent\treceived\tlost\tcorrupt\tmax_lag\twaits\twait_s\tseconds"
307 );
308 for (r, row) in all.chunks_exact(mine.len()).enumerate() {
309 println!(
310 "{mode}\t{size}\t{depth}\t{payload}\t{noise_kib}\t{pace_ns}\t{rep}\t{r}\t{sent}\t{}\t{}\t{}\t{}\t{}\t{:.6}\t{elapsed:.6}",
311 row[0],
312 row[1],
313 row[2],
314 row[3],
315 row[4],
316 row[5] as f64 * 1e-9,
317 );
318 }
319 }
320
321 world.barrier();
322 Arc::try_unwrap(ring)
323 .unwrap_or_else(|_| panic!("ring still shared at close"))
324 .close()
325 .unwrap();
326 if rank == 0 {
327 eprintln!("[soak] ok: {mode}, {size} ranks, {sent} messages/rank in {elapsed:.2}s");
328 }
329}