diff options
| author | Joshua Bakita <bakitajoshua@gmail.com> | 2023-06-09 18:55:07 -0400 |
|---|---|---|
| committer | Joshua Bakita <bakitajoshua@gmail.com> | 2023-06-09 18:55:07 -0400 |
| commit | e63404cfda97050b9d516232ceb4f918f48edcfc (patch) | |
| tree | 3eaa93b74c46cc31749c767856eda99e7204f0bb /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.h | 97 |
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 | ||
| 23 | static __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 | ||
| 44 | static __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 | |||
| 88 | static 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 | } | ||
