|
| 1 | +//! Isolated device->host strategy probe: full cost of producing an owned |
| 2 | +//! `Vec<f32>` on the host from a device buffer. |
| 3 | +//! Run: cargo run --example cuda_d2h_bw --features cuda-native --release |
| 4 | +use std::time::Instant; |
| 5 | + |
| 6 | +use cudarc::driver::CudaContext; |
| 7 | +use cudarc::driver::sys; |
| 8 | +use rayon::prelude::*; |
| 9 | + |
| 10 | +fn fresh_vec(n: usize) -> Vec<f32> { |
| 11 | + let mut v = Vec::<f32>::with_capacity(n); |
| 12 | + #[allow(clippy::uninit_vec)] |
| 13 | + unsafe { |
| 14 | + v.set_len(n) |
| 15 | + }; |
| 16 | + v |
| 17 | +} |
| 18 | + |
| 19 | +fn main() { |
| 20 | + let n: usize = 256 * 1024 * 1024; |
| 21 | + let bytes = n * std::mem::size_of::<f32>(); |
| 22 | + let iters = 10; |
| 23 | + let gb = bytes as f64 / 1e9; |
| 24 | + |
| 25 | + let ctx = CudaContext::new(0).unwrap(); |
| 26 | + let stream = ctx.new_stream().unwrap(); |
| 27 | + let d = stream.alloc_zeros::<f32>(n).unwrap(); |
| 28 | + |
| 29 | + let mut warm = fresh_vec(n); |
| 30 | + for _ in 0..3 { |
| 31 | + stream.memcpy_dtoh(&d, &mut warm).unwrap(); |
| 32 | + stream.synchronize().unwrap(); |
| 33 | + } |
| 34 | + |
| 35 | + // Current production path: DMA straight into a fresh pageable Vec. |
| 36 | + let t = Instant::now(); |
| 37 | + for _ in 0..iters { |
| 38 | + let v: Vec<f32> = stream.clone_dtoh(&d).unwrap(); |
| 39 | + std::hint::black_box(&v); |
| 40 | + } |
| 41 | + let a = t.elapsed().as_secs_f64() / iters as f64; |
| 42 | + |
| 43 | + // Cached pinned staging buffer, then a serial copy into a fresh output Vec. |
| 44 | + let pin = unsafe { cudarc::driver::result::malloc_host(bytes, 0) }.unwrap() as *mut f32; |
| 45 | + let staging = unsafe { std::slice::from_raw_parts_mut(pin, n) }; |
| 46 | + stream.memcpy_dtoh(&d, staging).unwrap(); |
| 47 | + stream.synchronize().unwrap(); |
| 48 | + |
| 49 | + let t = Instant::now(); |
| 50 | + for _ in 0..iters { |
| 51 | + stream.memcpy_dtoh(&d, staging).unwrap(); |
| 52 | + stream.synchronize().unwrap(); |
| 53 | + let mut out = fresh_vec(n); |
| 54 | + out.copy_from_slice(staging); |
| 55 | + std::hint::black_box(&out); |
| 56 | + } |
| 57 | + let b = t.elapsed().as_secs_f64() / iters as f64; |
| 58 | + |
| 59 | + // Cached pinned staging buffer, then a rayon-parallel copy (spreads the |
| 60 | + // first-touch page faults across cores) into a fresh output Vec. |
| 61 | + let t = Instant::now(); |
| 62 | + for _ in 0..iters { |
| 63 | + stream.memcpy_dtoh(&d, staging).unwrap(); |
| 64 | + stream.synchronize().unwrap(); |
| 65 | + let mut out = fresh_vec(n); |
| 66 | + let chunk = 1 << 20; |
| 67 | + out |
| 68 | + .par_chunks_mut(chunk) |
| 69 | + .zip(staging.par_chunks(chunk)) |
| 70 | + .for_each(|(o, s)| o.copy_from_slice(s)); |
| 71 | + std::hint::black_box(&out); |
| 72 | + } |
| 73 | + let e = t.elapsed().as_secs_f64() / iters as f64; |
| 74 | + |
| 75 | + unsafe { |
| 76 | + let _ = cudarc::driver::result::free_host(pin as *mut std::ffi::c_void); |
| 77 | + } |
| 78 | + |
| 79 | + // Lower bound: reuse one registered, already-faulted Vec, DMA only. |
| 80 | + let mut reuse = fresh_vec(n); |
| 81 | + let rc = unsafe { sys::cuMemHostRegister_v2(reuse.as_mut_ptr() as *mut _, bytes, 0) }; |
| 82 | + assert_eq!(rc, sys::CUresult::CUDA_SUCCESS); |
| 83 | + let t = Instant::now(); |
| 84 | + for _ in 0..iters { |
| 85 | + stream.memcpy_dtoh(&d, reuse.as_mut_slice()).unwrap(); |
| 86 | + stream.synchronize().unwrap(); |
| 87 | + } |
| 88 | + let dd = t.elapsed().as_secs_f64() / iters as f64; |
| 89 | + unsafe { |
| 90 | + sys::cuMemHostUnregister(reuse.as_mut_ptr() as *mut _); |
| 91 | + } |
| 92 | + |
| 93 | + println!( |
| 94 | + "buffer = {gb:.3} GB, iters = {iters}, threads = {}\n", |
| 95 | + rayon::current_num_threads() |
| 96 | + ); |
| 97 | + println!( |
| 98 | + "A) clone_dtoh -> fresh pageable Vec : {:7.1} ms ({:5.2} GB/s)", |
| 99 | + a * 1e3, |
| 100 | + gb / a |
| 101 | + ); |
| 102 | + println!( |
| 103 | + "B) pinned staging + serial copy : {:7.1} ms ({:5.2} GB/s)", |
| 104 | + b * 1e3, |
| 105 | + gb / b |
| 106 | + ); |
| 107 | + println!( |
| 108 | + "E) pinned staging + parallel copy : {:7.1} ms ({:5.2} GB/s)", |
| 109 | + e * 1e3, |
| 110 | + gb / e |
| 111 | + ); |
| 112 | + println!( |
| 113 | + "D) reuse registered Vec (DMA only) : {:7.1} ms ({:5.2} GB/s)", |
| 114 | + dd * 1e3, |
| 115 | + gb / dd |
| 116 | + ); |
| 117 | +} |
0 commit comments