summaryrefslogtreecommitdiffstats
path: root/copy_experiments
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
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')
-rw-r--r--copy_experiments/Makefile3
-rw-r--r--copy_experiments/copy_contender.cu148
-rw-r--r--copy_experiments/copy_testbench.h97
3 files changed, 248 insertions, 0 deletions
diff --git a/copy_experiments/Makefile b/copy_experiments/Makefile
new file mode 100644
index 0000000..2358c60
--- /dev/null
+++ b/copy_experiments/Makefile
@@ -0,0 +1,3 @@
1
2copy_contender: copy_contender.cu copy_testbench.h
3 /usr/local/cuda/bin/nvcc copy_contender.cu -o $@ -lcuda -g
diff --git a/copy_experiments/copy_contender.cu b/copy_experiments/copy_contender.cu
new file mode 100644
index 0000000..5514552
--- /dev/null
+++ b/copy_experiments/copy_contender.cu
@@ -0,0 +1,148 @@
1/* Copyright 2023 Joshua Bakita
2 * Serially dispatch copies in one direction and record the transfer times
3 * In order to both record individual copy times, and maintain constant
4 * pending copies, this program relies on the implict synchronization which
5 * occurs in CUDA when unidirectional copies are dispatched into seprat
6 * streams.
7 *
8 * Parameters include the size of the copy to perform and the number of times
9 * to perform said copy.
10 */
11#include <stdio.h>
12#include <cuda.h>
13
14#include "copy_testbench.h"
15
16typedef enum {
17 MODE_TO_GPU,
18 MODE_FROM_GPU,
19 MODE_PEER,
20} copy_mode_t;
21
22void usage(char** argv) {
23 fprintf(stderr, "Usage: %s <# of 4KiB pages to copy> <# of iterations>"
24 "<direction [to/from/peer] GPU>\n", argv[0]);
25}
26
27int main(int argc, char** argv) {
28 cudaError_t err;
29 int dev1, dev2;
30 size_t i;
31 char *pinned_hostmem, *devmem, *devmem_d2;
32 cudaStream_t even_stream, odd_stream;
33 struct timespec even_finish, odd_finish;
34 copy_mode_t mode;
35
36 if (argc != 4) {
37 usage(argv);
38 return 1;
39 }
40
41 // Size of each copy to the GPU, in bytes
42 const size_t COPY_SIZE = strtoul(argv[1], NULL, 10) * PG_SZ;
43 // Total copies = NUM_ITERS * 2 + 1
44 const size_t NUM_ITERS = strtoul(argv[2], NULL, 10);
45 if (strcmp(argv[3], "to") == 0)
46 mode = MODE_TO_GPU;
47 else if (strcmp(argv[3], "from") == 0)
48 mode = MODE_FROM_GPU;
49 else if (strcmp(argv[3], "peer") == 0)
50 mode = MODE_PEER;
51 else {
52 usage(argv);
53 return 1;
54 }
55 fprintf(stderr, "Copying %s %lu times %s.\n", human_readable_bytes(COPY_SIZE),
56 NUM_ITERS * 2 + 1, argv[3]);
57
58 // When doing GPU-to-GPU copy, what is the source device?
59 if (mode == MODE_PEER) {
60 SAFE(cudaGetDevice(&dev1));
61 if (dev1 == 0)
62 dev2 = 1;
63 else
64 dev2 = 0;
65 }
66
67 SAFE(cudaMallocHost(&pinned_hostmem, COPY_SIZE));
68 // Populate pinned_hostmem with random data
69 for (i = 0; i < COPY_SIZE; i++)
70 // Don't allow 0 so that the copy detection logic works
71 pinned_hostmem[i] = max((rand() & 0xff), 1);
72 SAFE(cudaMalloc(&devmem, COPY_SIZE));
73 SAFE(cudaMemset(devmem, 0, COPY_SIZE));
74 if (mode == MODE_PEER) {
75 err = cudaSetDevice(dev2);
76 if (err)
77 fprintf(stderr, "Warning: No other device available. Treating 'peer' as an internal device transfer.\n");
78 SAFE(cudaMalloc(&devmem_d2, COPY_SIZE));
79 SAFE(cudaMemset(devmem_d2, 0, COPY_SIZE));
80 SAFE(cudaSetDevice(dev1));
81 }
82
83 // If copying from device (to CPU or peer), first copy over initialization data
84 if (mode != MODE_TO_GPU) {
85 // Use a synchronizing memory copy
86 SAFE(cudaMemcpy(devmem, pinned_hostmem, COPY_SIZE, cudaMemcpyHostToDevice));
87 }
88
89 // Initialize two streams for alternating dispatch and synchronization
90 SAFE(cudaStreamCreate(&even_stream));
91 SAFE(cudaStreamCreate(&odd_stream));
92
93 // Reconfigure depending on the copy type
94 void *target, *source;
95 cudaMemcpyKind direction;
96 switch (mode) {
97 case MODE_TO_GPU:
98 target = devmem;
99 source = pinned_hostmem;
100 direction = cudaMemcpyHostToDevice;
101 break;
102 case MODE_FROM_GPU:
103 target = pinned_hostmem;
104 source = devmem;
105 direction = cudaMemcpyDeviceToHost;
106 break;
107 case MODE_PEER:
108 target = devmem;
109 source = devmem_d2;
110 direction = cudaMemcpyDeviceToDevice;
111 break;
112 default:
113 fprintf(stderr, "FATAL ERROR: 'mode' is not a valid enum value.\n");
114 return 1;
115 }
116
117 // Kick off the first copy that doesn't include a log
118 clock_gettime(CLOCK_MONOTONIC_RAW, &even_finish);
119 SAFE(cudaMemcpyAsync(target, source, COPY_SIZE, direction, odd_stream));
120 // Primary timing and dispatch loop. NUM_ITERS * 2 + 1 copies will be done
121 for (i = 1; NUM_ITERS == (size_t) -1 || i < NUM_ITERS + 2;) {
122 // Odd copy is underway. Queue up even copy so that there's no break.
123 SAFE(cudaMemcpyAsync(target, source, COPY_SIZE, direction, even_stream));
124 SAFE(cudaStreamSynchronize(odd_stream));
125 // Odd copy has finished. Record and print time.
126 clock_gettime(CLOCK_MONOTONIC_RAW, &odd_finish);
127 // Time for the 'odd' copy: odd_finish (end) - even_finish (start)
128 printf("%s/%lu: %ldms\n", argv[3], i++, ns2ms(timediff(even_finish, odd_finish)));
129 // Even copy is underway. Queue up odd copy so that there's no break.
130 SAFE(cudaMemcpyAsync(target, source, COPY_SIZE, direction, odd_stream));
131 SAFE(cudaStreamSynchronize(even_stream));
132 // Even copy has finished. Record and print time.
133 clock_gettime(CLOCK_MONOTONIC_RAW, &even_finish);
134 // Time for the 'even' copy: even_finish (end) - odd_finish (start)
135 printf("%s/%lu: %ldms\n", argv[3], i++, ns2ms(timediff(odd_finish, even_finish)));
136 }
137 // Record the time for the last copy to end
138 SAFE(cudaStreamSynchronize(odd_stream));
139 clock_gettime(CLOCK_MONOTONIC_RAW, &odd_finish);
140 printf("%s/%lu: %ldms\n", argv[3], i, ns2ms(timediff(even_finish, odd_finish)));
141 fprintf(stderr, "Done. Shutting down...\n");
142
143 cudaFree(pinned_hostmem);
144 cudaFree(devmem_d2);
145 cudaFree(devmem);
146 return 0;
147}
148
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