summaryrefslogtreecommitdiffstats
path: root/copy_experiments/copy_testbench.h
diff options
context:
space:
mode:
authorJoshua Bakita <bakitajoshua@gmail.com>2023-06-09 18:55:07 -0400
committerJoshua Bakita <bakitajoshua@gmail.com>2023-06-09 18:55:07 -0400
commite63404cfda97050b9d516232ceb4f918f48edcfc (patch)
tree3eaa93b74c46cc31749c767856eda99e7204f0bb /copy_experiments/copy_testbench.h
Add copy_contender benchmark, Makefile, and copy_testbench header
Benchmark supports: => Generating a chain of copies in a single context - No timing pause between copies => Copies to, from, or between device(s) => Variable size and number of copies => Logging how many milliseconds each copy takes *** Warning *** * Relies on implicit synchronization of unidirectional copies across * streams. Verify that this is still the case with CUDA before * trusting results. One can check by verifying that the runtime for * one iteration roughly matches the time for each iteration in a * multi-iteration run. ***
Diffstat (limited to 'copy_experiments/copy_testbench.h')
-rw-r--r--copy_experiments/copy_testbench.h97
1 files changed, 97 insertions, 0 deletions
diff --git a/copy_experiments/copy_testbench.h b/copy_experiments/copy_testbench.h
new file mode 100644
index 0000000..e1c4aa5
--- /dev/null
+++ b/copy_experiments/copy_testbench.h
@@ -0,0 +1,97 @@
1/* Copyright 2023 Joshua Bakita
2 * Header-only library that provides an ultra-low-overhead GPU timing function,
3 * a copy monitoring kernel, and other useful sundries.
4 */
5#include <stdio.h>
6#include <stdint.h>
7
8// 4k pages (4096 bytes)
9#define PG_SZ 0x1000
10
11#define SAFE(x) \
12 if ((err = (cudaError_t)(x)) != 0) { \
13 printf("CUDA error %d! %s\n", err, cudaGetErrorString(err)); \
14 printf("Suspect line: %s\n", #x); \
15 exit(1); \
16 }
17
18#define s2ns(s) ((s)*1000l*1000l*1000l)
19#define ns2ms(s) ((s)/(1000l*1000l))
20#define timediff(start, end) ((s2ns((end).tv_sec) + (end).tv_nsec) - (s2ns((start).tv_sec) + (start).tv_nsec))
21
22// Compiles to 5 inline SASS instructions on sm_52
23static __device__ inline uint64_t gclock64_fast(void) {
24 uint32_t lo_bits;
25 uint32_t hi_bits;
26 uint32_t hi_bits_2;
27 uint64_t ret;
28 // Upper bits may rollover between our first and 2nd read
29 asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(hi_bits));
30 asm volatile("mov.u32 %0, %%globaltimer_lo;" : "=r"(lo_bits));
31 asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(hi_bits_2));
32 // If upper bits rolled over, lo_bits = 0
33 lo_bits = (hi_bits != hi_bits_2) ? 0 : lo_bits;
34 // As sm_52 SASS is naively 32-bit, the following ops get optimized out
35 ret = hi_bits_2;
36 ret <<= 32;
37 ret |= lo_bits;
38 return ret;
39}
40
41/* Old copy function. Copyright Nathan Otterness
42// Returns the value of CUDA's global nanosecond timer.
43// Compiles to 9 inline SASS instructions on sm_52
44static __device__ inline uint64_t gclock64(void) {
45 // Due to a bug in CUDA's 64-bit globaltimer, the lower 32 bits can wrap
46 // around after the upper bits have already been read. Work around this by
47 // reading the high bits a second time. Use the second value to detect a
48 // rollover, and set the lower bits of the 64-bit "timer reading" to 0, which
49 // would be valid, it's passed over during the duration of the reading. If no
50 // rollover occurred, just return the initial reading.
51 volatile uint64_t first_reading;
52 volatile uint32_t second_reading;
53 uint32_t high_bits_first;
54 asm volatile("mov.u64 %0, %%globaltimer;" : "=l"(first_reading));
55 high_bits_first = first_reading >> 32;
56 asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(second_reading));
57 if (high_bits_first == second_reading) {
58 return first_reading;
59 }
60 // Return the value with the updated high bits, but the low bits set to 0.
61 return ((uint64_t) second_reading) << 32;
62}
63*/
64
65// This works by monitoring and recording when the last byte of each
66// 4k page of the `devmem_to_watch` becomes non-zero.
67// @param devmem_len Length of devmem_to_watch in bytes
68// @param times Arr to store fin time of each pg cpy. Len == devmem_len / PG_SZ
69__global__ void watch_devmem(char* devmem_to_watch, int devmem_len, uint64_t* times, int* barrier) {
70 volatile char* cursor = devmem_to_watch;
71 // If more than one thread is launched, distribute work by offsetting threads
72 if (blockDim.x > 1) {
73 times += threadIdx.x;
74 cursor += threadIdx.x * PG_SZ;
75 }
76 barrier[threadIdx.x] = 1;
77 // Assume they show up in the right order
78 while (cursor < devmem_to_watch + devmem_len) {
79 // Wait until last byte of the page is written to
80 while (!*(cursor + PG_SZ - 1))
81 continue;
82 *times = gclock64_fast(); // Log time
83 times += blockDim.x;
84 cursor += PG_SZ * blockDim.x; // Advance a page
85 }
86}
87
88static inline char* human_readable_bytes(size_t bytes) {
89 static char out[100];
90 if (bytes > 1024.*1024*1024)
91 snprintf(out, 100, "%.2fGiB", bytes/(1024.*1024*1024));
92 else if (bytes > 1024.*1024)
93 snprintf(out, 100, "%.2fMiB", bytes/(1024.*1024));
94 else if (bytes > 1024.)
95 snprintf(out, 100, "%.2fKiB", bytes/1024.);
96 return out;
97}