summaryrefslogtreecommitdiffstats
path: root/copy_experiments/copy_testbench.h
blob: e1c4aa57f7b8d6474d28f73506a12892edb8de3d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/* Copyright 2023 Joshua Bakita
 * Header-only library that provides an ultra-low-overhead GPU timing function,
 * a copy monitoring kernel, and other useful sundries.
 */
#include <stdio.h>
#include <stdint.h>

// 4k pages (4096 bytes)
#define PG_SZ 0x1000

#define SAFE(x) \
	if ((err = (cudaError_t)(x)) != 0) { \
		printf("CUDA error %d! %s\n", err, cudaGetErrorString(err)); \
		printf("Suspect line: %s\n", #x); \
		exit(1); \
	}

#define s2ns(s) ((s)*1000l*1000l*1000l)
#define ns2ms(s) ((s)/(1000l*1000l))
#define timediff(start, end) ((s2ns((end).tv_sec) + (end).tv_nsec) - (s2ns((start).tv_sec) + (start).tv_nsec))

// Compiles to 5 inline SASS instructions on sm_52
static __device__ inline uint64_t gclock64_fast(void) {
	uint32_t lo_bits;
	uint32_t hi_bits;
	uint32_t hi_bits_2;
	uint64_t ret;
	// Upper bits may rollover between our first and 2nd read
	asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(hi_bits));
	asm volatile("mov.u32 %0, %%globaltimer_lo;" : "=r"(lo_bits));
	asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(hi_bits_2));
	// If upper bits rolled over, lo_bits = 0
	lo_bits = (hi_bits != hi_bits_2) ? 0 : lo_bits;
	// As sm_52 SASS is naively 32-bit, the following ops get optimized out
	ret = hi_bits_2;
	ret <<= 32;
	ret |= lo_bits;
	return ret;
}

/* Old copy function. Copyright Nathan Otterness
// Returns the value of CUDA's global nanosecond timer.
// Compiles to 9 inline SASS instructions on sm_52
static __device__ inline uint64_t gclock64(void) {
  // Due to a bug in CUDA's 64-bit globaltimer, the lower 32 bits can wrap
  // around after the upper bits have already been read. Work around this by
  // reading the high bits a second time. Use the second value to detect a
  // rollover, and set the lower bits of the 64-bit "timer reading" to 0, which
  // would be valid, it's passed over during the duration of the reading. If no
  // rollover occurred, just return the initial reading.
  volatile uint64_t first_reading;
  volatile uint32_t second_reading;
  uint32_t high_bits_first;
  asm volatile("mov.u64 %0, %%globaltimer;" : "=l"(first_reading));
  high_bits_first = first_reading >> 32;
  asm volatile("mov.u32 %0, %%globaltimer_hi;" : "=r"(second_reading));
  if (high_bits_first == second_reading) {
    return first_reading;
  }
  // Return the value with the updated high bits, but the low bits set to 0.
  return ((uint64_t) second_reading) << 32;
}
*/

// This works by monitoring and recording when the last byte of each
// 4k page of the `devmem_to_watch` becomes non-zero.
// @param devmem_len Length of devmem_to_watch in bytes
// @param times      Arr to store fin time of each pg cpy. Len == devmem_len / PG_SZ
__global__ void watch_devmem(char* devmem_to_watch, int devmem_len, uint64_t* times, int* barrier) {
	volatile char* cursor = devmem_to_watch;
	// If more than one thread is launched, distribute work by offsetting threads
	if (blockDim.x > 1) {
		times += threadIdx.x;
		cursor += threadIdx.x * PG_SZ;
	}
	barrier[threadIdx.x] = 1;
	// Assume they show up in the right order
	while (cursor < devmem_to_watch + devmem_len) {
		// Wait until last byte of the page is written to
		while (!*(cursor + PG_SZ - 1))
			continue;
		*times = gclock64_fast(); // Log time
		times += blockDim.x;
		cursor += PG_SZ * blockDim.x; // Advance a page
	}
}

static inline char* human_readable_bytes(size_t bytes) {
	static char out[100];
	if (bytes > 1024.*1024*1024)
		snprintf(out, 100, "%.2fGiB", bytes/(1024.*1024*1024));
	else if (bytes > 1024.*1024)
		snprintf(out, 100, "%.2fMiB", bytes/(1024.*1024));
	else if (bytes > 1024.)
		snprintf(out, 100, "%.2fKiB", bytes/1024.);
	return out;
}