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_contender.cu | |
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_contender.cu')
| -rw-r--r-- | copy_experiments/copy_contender.cu | 148 |
1 files changed, 148 insertions, 0 deletions
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 | |||
| 16 | typedef enum { | ||
| 17 | MODE_TO_GPU, | ||
| 18 | MODE_FROM_GPU, | ||
| 19 | MODE_PEER, | ||
| 20 | } copy_mode_t; | ||
| 21 | |||
| 22 | void 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 | |||
| 27 | int 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 | |||
