-
Notifications
You must be signed in to change notification settings - Fork 0
/
graveler-while-race-condition.cu
108 lines (97 loc) · 4.49 KB
/
graveler-while-race-condition.cu
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
98
99
100
101
102
103
104
105
106
107
108
#include <curand_kernel.h>
#include <iostream>
#define ROUNDS 1000000000
#define ROLLS 231
#define BLOCKSIZE 1024
/*
*
This is made to simulate the number of times a 1 is rolled on a d4 per round of 231 rolls, similar to Austin Hourigan's graveler.py
I've taken some liberties to optimize it further, in addition to making it run in CUDA.
The kernel, __global__ void sim_rolls, does the heavy lifting, as it runs on the CUDA cores, reporting back to the system after completing.
Unnecessary arrays were dropped, instead keeping track of the fact that the rolls were done and how many times in a round a one was rolled, AKA graveler was too paralyzed to move.
The unnecessary check for 177 ones has been dropped. This is running too fast to stop at a precise target.
In this version, the number of roll sessions can overflow, but should not go below target. This wastes valuable time on excess roll sessions, but is faster than my other code because it's not constructing and destructing threads over and over.
Two kills are included - One within the stream that runs the simulation, one in a dedicated stream for stopping the simulation.
*
*/
__global__ void sim_stop(int *d_rolls, bool *d_kill, int *d_blocks) {
while(*d_rolls < ROUNDS - (*d_blocks * BLOCKSIZE)) {
if(*d_rolls >= ROUNDS - *d_blocks) *d_kill = true; return;
}
*d_kill = true;
return;
}
__global__ void sim_rolls(int *d_maxOnes, int *d_rolls, int seed, bool *d_kill) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= ROUNDS) return;
curandState state;
curand_init(seed, idx, 0, &state);
while(*d_rolls < ROUNDS - (idx * 2) && !*d_kill){
int ones = 0;
for (int i = 0; i < ROLLS; i++) {
int roll = curand(&state) % 4 + 1;
if (roll == 1) ones++;
}
if(*d_kill) return;
// atomicMax writes the number of ones rolled this session to d_maxOnes if it is greater, atomicAdd increments d_rolls by one each session
atomicMax(d_maxOnes, ones);
atomicAdd(d_rolls, 1);
}
}
int main() {
int maxOnes = 0;
int *d_maxOnes;
int rolls = 0;
int *d_rolls;
bool kill = false;
bool *d_kill;
cudaDeviceProp prop;
int deviceId;
cudaGetDevice(&deviceId);
cudaGetDeviceProperties(&prop, deviceId);
int smCount = prop.multiProcessorCount;
cudaStream_t stream1, stream2;
cudaStreamCreate(&stream1); cudaStreamCreate(&stream2);
// Allocate memory for CUDA copies of maxOnes and rolls
cudaMalloc(&d_maxOnes, sizeof(int));
cudaMalloc(&d_rolls, sizeof(int));
cudaMalloc(&d_kill, sizeof(bool));
// Initialize these values to 0 on the card
cudaMemcpy(d_maxOnes, &maxOnes, sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_rolls, &rolls, sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_kill, &kill, sizeof(bool), cudaMemcpyHostToDevice);
// Set block size, allocate blocks, run kernel
int blockSize = BLOCKSIZE;
// The code now polls the CUDA device for how many blocks it can run. The kernel will kill itself thread by thread when the target number of sessions has been reached.
int maxActiveBlocks = 0;
cudaOccupancyMaxActiveBlocksPerMultiprocessor(&maxActiveBlocks, sim_rolls, blockSize, 0);
int numBlocks = smCount * maxActiveBlocks;
// Passing number of blocks to device for the dedicated killswitch kernel
int *d_blocks;
cudaMalloc(&d_blocks, sizeof(int));
cudaMemcpy(d_blocks, &numBlocks, sizeof(int), cudaMemcpyHostToDevice);
//New timing method using cuda events
float totalTime=0;
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start,0);
sim_stop<<<1, 1, 0, stream1>>>(d_rolls, d_kill, d_blocks);
sim_rolls<<<numBlocks, blockSize, 0, stream2>>>(d_maxOnes, d_rolls, time(NULL), d_kill);
cudaEventRecord(stop,0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&totalTime, start, stop);
// Copy the result back to system memory, now that the CUDA program is over
cudaMemcpy(&maxOnes, d_maxOnes, sizeof(int), cudaMemcpyDeviceToHost);
cudaMemcpy(&rolls, d_rolls, sizeof(int), cudaMemcpyDeviceToHost);
// Report the important numbers
std::cout << "Highest Ones Roll: " << maxOnes << std::endl;
std::cout << "Number of Roll Sessions: " << rolls << std::endl;
std::cout << totalTime << "ms" << std::endl;
// Never malloc without a free
cudaFree(d_maxOnes);
cudaFree(d_rolls);
cudaFree(d_kill);
cudaFree(d_blocks);
return 0;
}