summaryrefslogtreecommitdiffstats
path: root/task_host_utilities.cu
diff options
context:
space:
mode:
authorJoshua Bakita <bakitajoshua@gmail.com>2023-07-06 11:45:59 -0400
committerJoshua Bakita <bakitajoshua@gmail.com>2023-07-06 11:45:59 -0400
commite415e282be53a3d8264b67d22b4b15b518f71099 (patch)
treee57cfc4b8333ed2109bc8ab8629ae5308102054b /task_host_utilities.cu
parentb587ea88f62a207a18b6efa56025bc307e06d5ae (diff)
Add preemption_logger benchmark, make target, and fix d82c60c
Benchmark supports: => Spinning on-GPU until some number of preemptions have occured => Monitoring GPU scheduling by logging preempt and resume times => Logging the above as raw GPU, or converted CPU, times => Measuring launch overheads Also imports task_host_utilities.cu from cuda_scheduling_examiner as of commit 9fc0ca898ef64b82cbc3734c60405723650bc66f. This is needed for timing logic used in both preemption_logger and mon_cross_ctx_copies, thus the latter would not compile prior to this commit. Also adds a new testbench helper, and a .gitignore for binaries.
Diffstat (limited to 'task_host_utilities.cu')
-rw-r--r--task_host_utilities.cu384
1 files changed, 384 insertions, 0 deletions
diff --git a/task_host_utilities.cu b/task_host_utilities.cu
new file mode 100644
index 0000000..e35292f
--- /dev/null
+++ b/task_host_utilities.cu
@@ -0,0 +1,384 @@
1// This file contains the implementation of the functions defined in
2// task_host_utilities.h--used by task_host_utilities.c to work with the GPU.
3#include <cuda_runtime.h>
4#include <errno.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <string.h>
8#include <sys/mman.h>
9#include <sys/types.h>
10#include <sys/wait.h>
11#include <time.h>
12#include <unistd.h>
13
14// The number of GPU nanoseconds to spin for GetGPUTimerScale. Increasing this
15// will both increase the accuracy and the time the function takes to return.
16#define TIMER_SPIN_DURATION (2ull * 1000 * 1000 * 1000)
17
18// This macro takes a cudaError_t value. It prints an error message and returns
19// 0 if the cudaError_t isn't cudaSuccess. Otherwise, it returns nonzero.
20#define CheckCUDAError(val) (InternalCUDAErrorCheck((val), #val, __FILE__, __LINE__))
21
22// Prints an error message and returns 0 if the given CUDA result is an error.
23static int InternalCUDAErrorCheck(cudaError_t result, const char *fn,
24 const char *file, int line) {
25 if (result == cudaSuccess) return 1;
26 printf("CUDA error %d: %s. In %s, line %d (%s)\n", (int) result,
27 cudaGetErrorString(result), file, line, fn);
28 return 0;
29}
30
31static double CurrentSeconds(void) {
32 struct timespec ts;
33 if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts) != 0) {
34 printf("Error getting time.\n");
35 exit(1);
36 }
37 return ((double) ts.tv_sec) + (((double) ts.tv_nsec) / 1e9);
38}
39
40// GlobalTimer64: Get 64-bit counter of current time on GPU
41// ***This is duplicated in benchmark_gpu_utilities.h***
42#if __CUDA_ARCH__ >= 300 // Kepler+
43// Returns the value of CUDA's global nanosecond timer.
44// Starting with sm_30, `globaltimer64` was added
45// Tuned for sm_5X architectures, but tested to still be efficient on sm_7X
46static __device__ inline uint64_t GlobalTimer64(void) {
47 uint32_t lo_bits, hi_bits, hi_bits_2;
48 uint64_t ret;
49 // Upper bits may rollover between our 1st and 2nd read
50 // (The bug seems constrained to certain old Jetson boards, so this
51 // workaround could probably be gated to only those GPUs.)
52 asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(hi_bits));
53 asm volatile("mov.u32 %0, %%globaltimer_lo;" : "=r"(lo_bits));
54 asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(hi_bits_2));
55 // If upper bits rolled over, lo_bits = 0
56 lo_bits = (hi_bits != hi_bits_2) ? 0 : lo_bits;
57 // SASS on older architectures (such as sm_52) is natively 32-bit, so the
58 // following three lines get optimized out.
59 ret = hi_bits_2;
60 ret <<= 32;
61 ret |= lo_bits;
62 return ret;
63}
64#elif __CUDA_ARCH__ < 200 // Tesla
65// On sm_13, we /only/ have `clock` (32 bits)
66static __device__ inline uint64_t GlobalTimer64(void) {
67 uint32_t lo_bits;
68 uint64_t ret;
69 asm volatile("mov.u32 %0, %%clock;" : "=r"(lo_bits));
70 ret = 0;
71 ret |= lo_bits;
72 return ret;
73}
74#else
75// Could use clock64 for sm_2x (Fermi), but that's untested
76#error Fermi-based GPUs (sm_2x) are unsupported!
77#endif
78
79// A simple kernel which writes the value of the globaltimer64 register to a
80// location in device memory.
81static __global__ void GetTime(uint64_t *time, volatile uint32_t *ready_barrier,
82 volatile uint32_t *start_barrier, volatile uint32_t *end_barrier) {
83 *ready_barrier = 1;
84 while (!*start_barrier)
85 continue;
86 *time = GlobalTimer64();
87 *end_barrier = 1;
88}
89
90// Allocates a private shared memory buffer containing the given number of
91// bytes. Can be freed by using FreeSharedBuffer. Returns NULL on error.
92// Initializes the buffer to contain 0.
93static void* AllocateSharedBuffer(size_t size) {
94 void *to_return = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS |
95 MAP_SHARED, -1, 0);
96 if (to_return == MAP_FAILED) return NULL;
97 memset(to_return, 0, size);
98 return to_return;
99}
100
101// Frees a shared buffer returned by AllocateSharedBuffer.
102static void FreeSharedBuffer(void *buffer, size_t size) {
103 munmap(buffer, size);
104}
105
106// This function should be run in a separate process in order to read the GPU's
107// nanosecond counter. Sets times to 0 on error.
108static void InternalReadGPUNanoseconds(int cuda_device, double *cpu_time,
109 uint64_t *gpu_time) {
110 uint64_t *device_time = NULL;
111 volatile uint32_t *gpu_start_barrier, *start_barrier = NULL;
112 volatile uint32_t *gpu_end_barrier, *end_barrier = NULL;
113 volatile uint32_t *gpu_ready_barrier, *ready_barrier = NULL;
114 volatile double cpu_start, cpu_end;
115 double max_error;
116 // Times need to be zero in case any of the following logic fails
117 *cpu_time = 0;
118 *gpu_time = 0;
119 if (!CheckCUDAError(cudaSetDevice(cuda_device))) return;
120 if (!CheckCUDAError(cudaMalloc(&device_time, sizeof(*device_time)))) return;
121 if (!CheckCUDAError(cudaHostAlloc(&start_barrier, sizeof(*start_barrier),
122 cudaHostAllocMapped))) goto out;
123 if (!CheckCUDAError(cudaHostAlloc(&end_barrier, sizeof(*end_barrier),
124 cudaHostAllocMapped))) goto out;
125 if (!CheckCUDAError(cudaHostAlloc(&ready_barrier, sizeof(*ready_barrier),
126 cudaHostAllocMapped))) goto out;
127 // Setup device pointers for all the barriers
128 if (!CheckCUDAError(cudaHostGetDevicePointer((uint32_t**)&gpu_start_barrier,
129 (uint32_t*)start_barrier, 0))) goto out;
130 if (!CheckCUDAError(cudaHostGetDevicePointer((uint32_t**)&gpu_end_barrier,
131 (uint32_t*)end_barrier, 0))) goto out;
132 if (!CheckCUDAError(cudaHostGetDevicePointer((uint32_t**)&gpu_ready_barrier,
133 (uint32_t*)ready_barrier, 0))) goto out;
134 /* Clock Synchronization Flow
135
136 Start Barrier--+
137 |
138 CPU: Launch Kernel......^-->CPU TS 1-->|............^-->CPU TS 2-->Done
139 | | | |
140 GPU: +---------->|..............v-->GPU TS-->|-->Terminate
141 | |
142 Ready Barrier--+ End Barrier--+
143
144 GPU TS ~= CPU TS 1 + (CPU TS 2 - CPU TS 1) / 2.0
145
146 If it takes about as long for a CPU memory write to DRAM to be visible to
147 the GPU as it takes for a GPU memory write to DRAM to be visible to the
148 CPU.
149
150 If not, the worst possible error is if one of the above legs is instant,
151 and the other is extremely slow, in which case the time estimate is off by
152 as much as half the CPU interval [(CPU TS 2 - CPU TS 1) / 2.0]. (Depending
153 on the platform's CPU cache configuration, an inbalance may be expected.)
154
155 The typical error should be about the difference in the amount of time it
156 takes to read the CPU TS counter and the GPU TS counter. On recent
157 platforms, the difference shouldn't be more than double-digit nanoseconds.
158 */
159 // Run the kernel a first time to warm up the GPU.
160 GetTime<<<1, 1>>>(device_time, gpu_ready_barrier, gpu_start_barrier,
161 gpu_end_barrier);
162 *start_barrier = 1;
163 if (!CheckCUDAError(cudaDeviceSynchronize())) goto out;
164 // Now run the actual time-checking kernel.
165 *start_barrier = 0;
166 *end_barrier = 0;
167 *ready_barrier = 0;
168 GetTime<<<1, 1>>>(device_time, gpu_ready_barrier, gpu_start_barrier,
169 gpu_end_barrier);
170 // Wait for kernel to initialize
171 while (!*ready_barrier)
172 continue;
173 // Immediately record CPU time and tell GPU kernel to record time
174 cpu_start = CurrentSeconds();
175 *start_barrier = 1;
176 // Wait for kernel to finish recording time, and immediately record CPU time again
177 while (!*end_barrier)
178 continue;
179 cpu_end = CurrentSeconds();
180 // GPU clock should have been stored about half-way between the CPU reads
181 *cpu_time = (cpu_end - cpu_start) / 2.0 + cpu_start;
182 if (!CheckCUDAError(cudaMemcpy(gpu_time, device_time, sizeof(device_time),
183 cudaMemcpyDeviceToHost))) goto out;
184 max_error = (cpu_end - cpu_start) / 2.0;
185 fprintf(stderr, "Time synchronized to a maximum error of +/- %f us.\n",
186 max_error * (1000.0 * 1000.0));
187out:
188 cudaFree(device_time);
189 cudaFree((void*)start_barrier);
190 cudaFree((void*)end_barrier);
191 cudaFree((void*)ready_barrier);
192}
193
194int GetHostDeviceTimeOffset(int cuda_device, double *host_seconds,
195 uint64_t *gpu_nanoseconds) {
196 uint64_t *shared_gpu_time = NULL;
197 double *shared_cpu_time = NULL;
198 int status;
199 pid_t pid = -1;
200 shared_gpu_time = (uint64_t *) AllocateSharedBuffer(
201 sizeof(*shared_gpu_time));
202 if (!shared_gpu_time) {
203 printf("Failed allocating shared buffer for IPC.\n");
204 return 0;
205 }
206 shared_cpu_time = (double *) AllocateSharedBuffer(sizeof(*shared_cpu_time));
207 if (!shared_cpu_time) {
208 printf("Failed allocating shared CPU time buffer for IPC.\n");
209 FreeSharedBuffer(shared_gpu_time, sizeof(*shared_gpu_time));
210 return 0;