1use std::sync::Arc;
38use std::sync::atomic::{AtomicBool, Ordering};
39use std::time::Instant;
40
41use mpi::Threading;
42use mpi::collective::CommunicatorCollectives;
43use mpi::point_to_point::{Destination, Source};
44use mpi::topology::{Communicator, Group, Rank, SimpleCommunicator};
45
46use mpi_rma::Ring;
47
48const TAG_DATA: i32 = 1;
49const TAG_STOP: i32 = 2;
50const TAG_NOISE: i32 = 11;
51const TAG_QUIT: i32 = 12;
52
53const SIZES: [usize; 5] = [64, 512, 4096, 32768, 262_144];
54const STREAM_BYTES: u64 = 64 << 20;
56const BSEND_BUF: usize = 512 << 20;
58
59struct Shared<'a>(&'a SimpleCommunicator);
63unsafe impl Send for Shared<'_> {}
64
65const HEADER: &str = "transport\tmeasure\tranks\tdepth\tnoise_kib\trep\tpayload\
66\tsent\tdelivered\tinject_s\ttotal_s\twait_s\tinject_per_s\tgoodput_per_s\
67\tgoodput_MiB_per_s\tus_per_msg\tlost\tmax_us_per_msg";
68
69struct Row {
71 transport: &'static str,
72 measure: &'static str,
73 payload: usize,
74 sent: u64,
76 delivered: u64,
78 inject: f64,
80 total: f64,
82 wait: f64,
84 max_us: f64,
86}
87
88impl Row {
89 fn print(&self, ranks: i32, depth: usize, noise_kib: usize, rep: u32) {
90 let inject = self.sent as f64 / self.inject;
91 let goodput = self.delivered as f64 / self.total;
92 println!(
93 "{}\t{}\t{ranks}\t{depth}\t{noise_kib}\t{rep}\t{}\t{}\t{}\
94 \t{:.6}\t{:.6}\t{:.6}\t{inject:.1}\t{goodput:.1}\t{:.4}\t{:.3}\t{}\t{:.3}",
95 self.transport,
96 self.measure,
97 self.payload,
98 self.sent,
99 self.delivered,
100 self.inject,
101 self.total,
102 self.wait,
103 goodput * self.payload as f64 / (1 << 20) as f64,
104 self.total * 1e6 / self.delivered.max(1) as f64,
105 self.sent - self.delivered,
106 self.max_us,
107 );
108 }
109}
110
111fn shuffled(rep: u32, len: usize) -> Vec<u8> {
116 let mut buf = vec![0xC3u8; len];
117 let mut x = (rep as u64 + 1) | 1;
118 let mut rnd = || {
119 x ^= x << 13;
120 x ^= x >> 7;
121 x ^= x << 17;
122 x
123 };
124 for i in (1..buf.len()).rev() {
125 buf.swap(i, (rnd() % (i as u64 + 1)) as usize);
126 }
127 buf
128}
129
130fn 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 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
199fn 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
235fn 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 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
309fn 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 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 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 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 let lanes = vec![
400 (0 as Rank, 1 as Rank, depth, payload),
401 (1, 0, depth, payload),
402 ];
403 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 world.barrier();
498}