From 8ebefe07443bc4ff15fb523b4d6ca3158dec91a9 Mon Sep 17 00:00:00 2001 From: Yuhan Liu <02yuhanliu@gmail.com> Date: Sat, 14 Sep 2024 10:54:15 -0400 Subject: [PATCH 01/12] finish parts 1 and 2 --- src/main.cpp | 4 +-- stream_compaction/cpu.cu | 47 ++++++++++++++++++++++++++++++---- stream_compaction/naive.cu | 52 +++++++++++++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 896ac2b..54cf8e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,8 +102,8 @@ int main(int argc, char* argv[]) { // Compaction tests - genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case - a[SIZE - 1] = 0; + genArray(SIZE - 1, a, 4); + a[SIZE - 1] = 1; // Leave a 1 at the end to test that edge case printArray(SIZE, a, true); int count, expectedCount, expectedNPOT; diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 719fa11..4ac7194 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -19,20 +19,34 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + + odata[0] = 0; + for (int i = 0; i < n - 1; i++) { + odata[i + 1] = odata[i] + idata[i]; + } + timer().endCpuTimer(); } /** * CPU stream compaction without using the scan function. + * remove 0s from an array of ints * * @returns the number of elements remaining after compaction. */ int compactWithoutScan(int n, int *odata, const int *idata) { + int idx = 0; timer().startCpuTimer(); - // TODO + + for (int i = 0; i < n; i++) { + if (idata[i] != 0) { + odata[idx] = idata[i]; + idx++; + } + } + timer().endCpuTimer(); - return -1; + return idx; } /** @@ -41,10 +55,33 @@ namespace StreamCompaction { * @returns the number of elements remaining after compaction. */ int compactWithScan(int n, int *odata, const int *idata) { + int* tmp = new int[n]; + int* scanned = new int[n]; timer().startCpuTimer(); - // TODO + + // temporary array + for (int i = 0; i < n; i++) { + tmp[i] = (idata[i] == 0) ? 0 : 1; + } + + // copied scan function (to not call timer twice) + scanned[0] = 0; + for (int i = 0; i < n - 1; i++) { + scanned[i + 1] = scanned[i] + tmp[i]; + } + + // scatter + for (int i = 0; i < n; i++) { + if (tmp[i] == 1) { + odata[scanned[i]] = idata[i]; + } + } + timer().endCpuTimer(); - return -1; + int num = scanned[n - 1] + tmp[n - 1]; + delete[] tmp; + delete[] scanned; + return num; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 4308876..116bfe1 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,6 +3,10 @@ #include "common.h" #include "naive.h" +#include + +#define blockSize 256 + namespace StreamCompaction { namespace Naive { using StreamCompaction::Common::PerformanceTimer; @@ -13,13 +17,59 @@ namespace StreamCompaction { } // TODO: __global__ + /** + * Kernel function for scan + */ + __global__ void kernScan(int n, int d, int* odata, int* idata) { + + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + int off = 1 << (d - 1); + + if (k >= n) { + return; + } + else if (k >= off) { + odata[k] = idata[k] + idata[k - off]; + } + else { + odata[k] = idata[k]; + } + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + + int* dev_idata; + int* dev_odata; + + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_idata failed!"); + + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_odata failed!"); + + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + timer().startGpuTimer(); - // TODO + + // kernel invocations + for (int d = 1; d <= ilog2ceil(n); d++) { + kernScan<<>>(n, d, dev_odata, dev_idata); + checkCUDAError("kernScan failed!"); + + std::swap(dev_odata, dev_idata); + } + timer().endGpuTimer(); + + cudaMemcpy(odata + 1, dev_idata, (n-1) * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_idata); + cudaFree(dev_odata); } } } From 7408f99794ac3c6538a343930dc3bc62c2f49ba4 Mon Sep 17 00:00:00 2001 From: Yuhan Liu <02yuhanliu@gmail.com> Date: Tue, 17 Sep 2024 23:42:01 +0200 Subject: [PATCH 02/12] finish code --- stream_compaction/common.cu | 18 +++- stream_compaction/efficient.cu | 150 +++++++++++++++++++++++++++++++-- stream_compaction/naive.cu | 4 +- stream_compaction/thrust.cu | 13 ++- 4 files changed, 171 insertions(+), 14 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 2ed6d63..243f712 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -1,4 +1,5 @@ #include "common.h" +#include void checkCUDAErrorFn(const char *msg, const char *file, int line) { cudaError_t err = cudaGetLastError(); @@ -23,7 +24,13 @@ namespace StreamCompaction { * which map to 0 will be removed, and elements which map to 1 will be kept. */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { - // TODO + int i = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (i >= n) { + return; + } else { + bools[i] = (idata[i] == 0) ? 0 : 1; + } } /** @@ -32,7 +39,14 @@ namespace StreamCompaction { */ __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { - // TODO + + int i = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (i >= n) { + return; + } else if (bools[i] == 1) { + odata[indices[i]] = idata[i]; + } } } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 2db346e..f46281f 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,6 +3,10 @@ #include "common.h" #include "efficient.h" +#include + +#define blockSize 128 + namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; @@ -12,13 +16,82 @@ namespace StreamCompaction { return timer; } + /** + * Kernel function for up-sweep + */ + __global__ void kernUpSweep(int n, int d, int* idata) { + + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + k *= (1 << (d + 1)); + + if (k >= n) { + return; + } else { + // from notes: x[k + 2^(d+1) - 1] += x[k + 2^(d) -1] + idata[k + (1 << (d + 1)) - 1] += idata[k + (1 << d) - 1]; + } + } + + /** + * Kernal function for down-sweep + */ + __global__ void kernDownSweep(int n, int d, int* data) { + + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + k *= (1 << (d + 1)); + + if (k >= n) { + return; + } + else { + int t = data[k + (1 << d) - 1]; // save left child + data[k + (1 << d) - 1] = data[k + (1 << (d + 1)) - 1]; // set left child to this node's val + data[k + (1 << (d + 1)) - 1] += t; // set right child to old left val + this node's val + } + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); - // TODO - timer().endGpuTimer(); + + int* dev_idata; // one buffer for in-place scan + + int nextPowSpace = 1 << ilog2ceil(n); + + cudaMalloc((void**)&dev_idata, nextPowSpace * sizeof(int)); + checkCUDAError("cudaMalloc dev_idata failed!"); + + cudaMemcpy(dev_idata + (nextPowSpace - n), idata, n * sizeof(int), cudaMemcpyHostToDevice); + + timer().startGpuTimer(); // ----------------------------- + + // up-sweep + for (int d = 0; d < ilog2ceil(n); d++) { + + int n_adj = nextPowSpace / (1 << (d + 1)); + dim3 fullBlocksPerGrid((n_adj + blockSize - 1) / blockSize); + + kernUpSweep<<>>(nextPowSpace, d, dev_idata); + checkCUDAError("kernUpSweep failed!"); + } + + cudaMemset(dev_idata + (nextPowSpace - 1), 0, sizeof(int)); + + // down-sweep + for (int d = ilog2ceil(n) - 1; d >= 0; d--) { + + int n_adj = nextPowSpace / (1 << (d + 1)); + dim3 fullBlocksPerGrid((n_adj + blockSize - 1) / blockSize); + + kernDownSweep<<>>(nextPowSpace, d, dev_idata); + checkCUDAError("kernDownSweep failed!"); + } + + timer().endGpuTimer(); // -------------------------------- + + cudaMemcpy(odata, dev_idata + (nextPowSpace - n), n * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_idata); } /** @@ -31,10 +104,73 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { - timer().startGpuTimer(); - // TODO - timer().endGpuTimer(); - return -1; + + int* dev_idata; + int* dev_scanned; + int* dev_bools; + + int nextPowSpace = 1 << ilog2ceil(n); + + cudaMalloc((void**)&dev_idata, nextPowSpace * sizeof(int)); + checkCUDAError("cudaMalloc dev_idata failed!"); + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + cudaMalloc((void**)&dev_bools, nextPowSpace * sizeof(int)); + checkCUDAError("cudaMalloc dev_bools failed!"); + + cudaMalloc((void**)&dev_scanned, nextPowSpace * sizeof(int)); + checkCUDAError("cudaMalloc dev_scattered failed!"); + + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + timer().startGpuTimer(); // -------------- + + // create temporary binary array + StreamCompaction::Common::kernMapToBoolean <<>> (n, dev_bools, dev_idata); + cudaMemcpy(dev_scanned, dev_bools, nextPowSpace * sizeof(int), cudaMemcpyDeviceToDevice); + + // scan (copied and pasted) -------| + + // up-sweep + for (int d = 0; d < ilog2ceil(n); d++) { + + int n_adj = nextPowSpace / (1 << (d + 1)); + dim3 fullBlocksPerGrid_adj((n_adj + blockSize - 1) / blockSize); + + kernUpSweep << > > (nextPowSpace, d, dev_scanned); + checkCUDAError("kernUpSweep failed!"); + } + + cudaMemset(dev_scanned + (nextPowSpace - 1), 0, sizeof(int)); + + // down-sweep + for (int d = ilog2ceil(n) - 1; d >= 0; d--) { + + int n_adj = nextPowSpace / (1 << (d + 1)); + dim3 fullBlocksPerGrid_adj((n_adj + blockSize - 1) / blockSize); + + kernDownSweep <<>> (nextPowSpace, d, dev_scanned); + checkCUDAError("kernDownSweep failed!"); + } + + // end scan stuff ------------------| + + // scatter + StreamCompaction::Common::kernScatter <<>> (n, dev_idata, dev_idata, dev_bools, dev_scanned); + + timer().endGpuTimer(); // ---------------- + + int count; + cudaMemcpy(&count, dev_scanned + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); + count += (idata[n - 1] != 0); + + cudaMemcpy(odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_idata); + cudaFree(dev_bools); + cudaFree(dev_scanned); + + return count; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 116bfe1..84f21ea 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -5,7 +5,7 @@ #include -#define blockSize 256 +#define blockSize 128 namespace StreamCompaction { namespace Naive { @@ -15,7 +15,6 @@ namespace StreamCompaction { static PerformanceTimer timer; return timer; } - // TODO: __global__ /** * Kernel function for scan @@ -34,6 +33,7 @@ namespace StreamCompaction { else { odata[k] = idata[k]; } + } /** diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 1def45e..41a2ea0 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -18,11 +18,18 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + + thrust::host_vector host_in(idata, idata + n); + thrust::device_vector dv_in = host_in; + thrust::device_vector dv_out = host_in; + timer().startGpuTimer(); - // TODO use `thrust::exclusive_scan` - // example: for device_vectors dv_in and dv_out: - // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + + // for device_vectors dv_in and dv_out: + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + timer().endGpuTimer(); + cudaMemcpy(odata, dv_out.data().get(), n * sizeof(int), cudaMemcpyDeviceToHost); } } } From 8f6805eab811eeda473d7d9554f1d09b6e23425a Mon Sep 17 00:00:00 2001 From: Yuhan Liu <02yuhanliu@gmail.com> Date: Wed, 18 Sep 2024 16:18:53 +0200 Subject: [PATCH 03/12] implement radix sort --- src/main.cpp | 40 ++++++- stream_compaction/CMakeLists.txt | 2 + stream_compaction/cpu.cu | 12 ++ stream_compaction/cpu.h | 2 + stream_compaction/radix.cu | 188 +++++++++++++++++++++++++++++++ stream_compaction/radix.h | 11 ++ 6 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 stream_compaction/radix.cu create mode 100644 stream_compaction/radix.h diff --git a/src/main.cpp b/src/main.cpp index 54cf8e9..0e090ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,16 +11,18 @@ #include #include #include +#include #include "testing_helpers.hpp" +#include -const int SIZE = 1 << 8; // feel free to change the size of array +const int SIZE = 1 << 3; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; int *c = new int[SIZE]; int main(int argc, char* argv[]) { - // Scan tests + // Scan tests -------------------------------------- printf("\n"); printf("****************\n"); @@ -100,7 +102,7 @@ int main(int argc, char* argv[]) { printf("** STREAM COMPACTION TESTS **\n"); printf("*****************************\n"); - // Compaction tests + // Compaction tests -------------------------------------- genArray(SIZE - 1, a, 4); a[SIZE - 1] = 1; // Leave a 1 at the end to test that edge case @@ -147,8 +149,38 @@ int main(int argc, char* argv[]) { //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); - system("pause"); // stop Win32 console from closing on exit + //system("pause"); // stop Win32 console from closing on exit delete[] a; delete[] b; delete[] c; + + + // radix sort tests -------------------------------------- + + printf("\n"); + printf("*****************************\n"); + printf("***** RADIX SORT TESTS ******\n"); + printf("*****************************\n"); + + genArray(SIZE, a, SIZE); + + for (int i = 0; i < SIZE; i++) { + a[i] = i; + } + + printArray(SIZE, a, true); + + zeroArray(SIZE, b); + printDesc("cpu sort (std::sort)"); + StreamCompaction::CPU::sort(SIZE, b, a); + printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + printArray(SIZE, b, true); + + zeroArray(SIZE, c); + printDesc("radix sort"); + StreamCompaction::Radix::sort(SIZE, c, a); + printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + printArray(SIZE, c, false); + printCmpResult(SIZE, b, c); + } diff --git a/stream_compaction/CMakeLists.txt b/stream_compaction/CMakeLists.txt index 19511ca..7378625 100644 --- a/stream_compaction/CMakeLists.txt +++ b/stream_compaction/CMakeLists.txt @@ -4,6 +4,7 @@ set(headers "naive.h" "efficient.h" "thrust.h" + "radix.h" ) set(sources @@ -12,6 +13,7 @@ set(sources "naive.cu" "efficient.cu" "thrust.cu" + "radix.cu" ) list(SORT headers) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 4ac7194..137e695 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -12,6 +12,18 @@ namespace StreamCompaction { return timer; } + /** + * Standard sort + * (for parallel radix comparison) + */ + void sort(int n, int* odata, const int* idata) { + memcpy(odata, idata, n * sizeof(int)); + + timer().startCpuTimer(); + std::sort(odata, odata + n); + timer().endCpuTimer(); + } + /** * CPU scan (prefix sum). * For performance analysis, this is supposed to be a simple for loop. diff --git a/stream_compaction/cpu.h b/stream_compaction/cpu.h index 873c047..ae8d3be 100644 --- a/stream_compaction/cpu.h +++ b/stream_compaction/cpu.h @@ -6,6 +6,8 @@ namespace StreamCompaction { namespace CPU { StreamCompaction::Common::PerformanceTimer& timer(); + void sort(int n, int* odata, const int* idata); + void scan(int n, int *odata, const int *idata); int compactWithoutScan(int n, int *odata, const int *idata); diff --git a/stream_compaction/radix.cu b/stream_compaction/radix.cu new file mode 100644 index 0000000..5109d78 --- /dev/null +++ b/stream_compaction/radix.cu @@ -0,0 +1,188 @@ +#include +#include +#include "common.h" +#include "radix.h" + +#include + +#define blockSize 128 + +namespace StreamCompaction { + namespace Radix { + using StreamCompaction::Common::PerformanceTimer; + PerformanceTimer& timer() + { + static PerformanceTimer timer; + return timer; + } + + // populate dev_b with current bit, but reversed + __global__ void kernIsolateBit(int n, int bit, int* i, int* b) { + + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n) { + return; + } else { + + int bitVal = (i[idx] >> bit) & 1; + b[idx] = bitVal ^ 1; + } + } + + // Kernel function for up-sweep + __global__ void kernUpSweep(int n, int d, int* idata) { + + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + k *= (1 << (d + 1)); + + if (k >= n) { + return; + } + else { + // from notes: x[k + 2^(d+1) - 1] += x[k + 2^(d) -1] + idata[k + (1 << (d + 1)) - 1] += idata[k + (1 << d) - 1]; + } + } + + // Kernal function for down-sweep + __global__ void kernDownSweep(int n, int d, int* data) { + + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + k *= (1 << (d + 1)); + + if (k >= n) { + return; + } + else { + int t = data[k + (1 << d) - 1]; // save left child + data[k + (1 << d) - 1] = data[k + (1 << (d + 1)) - 1]; // set left child to this node's val + data[k + (1 << (d + 1)) - 1] += t; // set right child to old left val + this node's val + } + } + + // Kernal function for getting address for writing true keys + __global__ void kernCalcAddress(int n, int tf, int* f, int* t) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n) { + return; + } + else { + t[idx] = idx - f[idx] + tf; + } + } + + // Kernal function for getting address for writing true keys + __global__ void kernScatter(int n, int* d, int* b, int* t, int* f) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n) { + return; + } + else { + d[idx] = b[idx] ? f[idx] : t[idx]; + } + } + + // kernel function to reorder original numbers using index d array + __global__ void kernReorder(int n, int* d, int* i, int* out) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n) { + return; + } + else { + int newIdx = d[idx]; + out[newIdx] = i[idx]; + } + } + + // radix sort + void sort(int n, int *odata, const int *idata) { + + // labels are from parallel radix sort slides + int* dev_i; + int* dev_b; + int* dev_f; + int* dev_d; + int* dev_o; + + int tf; // total falses + + cudaMalloc((void**)&dev_i, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_i failed!"); + + cudaMalloc((void**)&dev_b, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_b failed!"); + + cudaMalloc((void**)&dev_f, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_f failed!"); + + cudaMalloc((void**)&dev_d, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_d failed!"); + + cudaMalloc((void**)&dev_o, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_o failed!"); + + cudaMemcpy(dev_i, idata, n * sizeof(int), cudaMemcpyHostToDevice); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + timer().startGpuTimer(); // -------------- + + for (int i = 0; i < 32; i++) { + + // isolate current bit to get i array + kernIsolateBit << > > (n, i, dev_i, dev_b); + + int temp; + cudaMemcpy(&temp, dev_b + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(dev_f, dev_b, n * sizeof(int), cudaMemcpyDeviceToDevice); + + // get f array by running scan on e + + // up-sweep + for (int d = 0; d < ilog2ceil(n); d++) { + + int n_adj = n / (1 << (d + 1)); + dim3 fullBlocksPerGrid_adj((n_adj + blockSize - 1) / blockSize); + + kernUpSweep << > > (n, d, dev_f); + checkCUDAError("kernUpSweep failed!"); + } + + cudaMemset(dev_f + (n - 1), 0, sizeof(int)); + + // down-sweep + for (int d = ilog2ceil(n) - 1; d >= 0; d--) { + + int n_adj = n / (1 << (d + 1)); + dim3 fullBlocksPerGrid_adj((n_adj + blockSize - 1) / blockSize); + + kernDownSweep << > > (n, d, dev_f); + checkCUDAError("kernDownSweep failed!"); + } + + // calculate total falses + cudaMemcpy(&tf, dev_f + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); + tf += temp; + + // t array (formula) + kernCalcAddress <<>> (n, tf, dev_f, dev_d); + + // scatter based on address to get d + kernScatter<<>> (n, dev_d, dev_b, dev_d, dev_f); + + // match indices back + kernReorder<<> > (n, dev_d, dev_i, dev_o); + } + + timer().endGpuTimer(); // ---------------- + + cudaMemcpy(odata, dev_o, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_i); + cudaFree(dev_b); + cudaFree(dev_f); + cudaFree(dev_d); + cudaFree(dev_o); + + } + } +} diff --git a/stream_compaction/radix.h b/stream_compaction/radix.h new file mode 100644 index 0000000..b81733c --- /dev/null +++ b/stream_compaction/radix.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common.h" + +namespace StreamCompaction { + namespace Radix { + StreamCompaction::Common::PerformanceTimer& timer(); + + void sort(int n, int* odata, const int* idata); + } +} \ No newline at end of file From 3d00504181eec3111ae90cce2c0140026edd4fd7 Mon Sep 17 00:00:00 2001 From: Yuhan Liu <02yuhanliu@gmail.com> Date: Wed, 18 Sep 2024 17:08:04 +0200 Subject: [PATCH 04/12] silly radix testing and bug fixes --- src/main.cpp | 81 ++++++++++++++++++++++++++++++++++---- stream_compaction/radix.cu | 28 +++++++------ 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 0e090ea..60b3f8c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,7 @@ #include "testing_helpers.hpp" #include -const int SIZE = 1 << 3; // feel free to change the size of array +const int SIZE = 1 << 19; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; @@ -149,11 +149,6 @@ int main(int argc, char* argv[]) { //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); - //system("pause"); // stop Win32 console from closing on exit - delete[] a; - delete[] b; - delete[] c; - // radix sort tests -------------------------------------- @@ -162,7 +157,12 @@ int main(int argc, char* argv[]) { printf("***** RADIX SORT TESTS ******\n"); printf("*****************************\n"); - genArray(SIZE, a, SIZE); + // pow2 test + + zeroArray(SIZE, a); + std::cout << " " << std::endl; + std::cout << "Radix Sort, pow2 consecutive ints" << std::endl; + std::cout << " " << std::endl; for (int i = 0; i < SIZE; i++) { a[i] = i; @@ -180,7 +180,72 @@ int main(int argc, char* argv[]) { printDesc("radix sort"); StreamCompaction::Radix::sort(SIZE, c, a); printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - printArray(SIZE, c, false); + printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); + + // npot test + + zeroArray(SIZE, a); + std::cout << " " << std::endl; + std::cout << "Radix Sort, non-pow2 consecutive ints" << std::endl; + std::cout << " " << std::endl; + + for (int i = 0; i < NPOT; i++) { + a[i] = i; + } + + printArray(NPOT, a, true); + + zeroArray(NPOT, b); + printDesc("cpu sort (std::sort)"); + StreamCompaction::CPU::sort(NPOT, b, a); + printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + printArray(NPOT, b, true); + + zeroArray(NPOT, c); + printDesc("radix sort"); + StreamCompaction::Radix::sort(NPOT, c, a); + printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + printArray(NPOT, c, true); + printCmpResult(NPOT, b, c); + + // npot shuffled + + genArray(SIZE, a, SIZE); + std::cout << " " << std::endl; + std::cout << "Radix Sort, non-pow2 shuffled ints" << std::endl; + std::cout << " " << std::endl; + + // example from slides for debugging + /*a[0] = 4; + a[1] = 7; + a[2] = 2; + a[3] = 6; + a[4] = 3; + a[5] = 5; + a[6] = 1; + a[7] = 0;*/ + + printArray(SIZE, a, true); + + zeroArray(SIZE, b); + printDesc("cpu sort (std::sort)"); + StreamCompaction::CPU::sort(SIZE, b, a); + printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + printArray(SIZE, b, true); + + zeroArray(SIZE, c); + printDesc("radix sort"); + StreamCompaction::Radix::sort(SIZE, c, a); + printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); + + // END TESTS ---------------------------------------- + + //system("pause"); // stop Win32 console from closing on exit + delete[] a; + delete[] b; + delete[] c; } diff --git a/stream_compaction/radix.cu b/stream_compaction/radix.cu index 5109d78..d67a1fe 100644 --- a/stream_compaction/radix.cu +++ b/stream_compaction/radix.cu @@ -106,19 +106,21 @@ namespace StreamCompaction { int tf; // total falses - cudaMalloc((void**)&dev_i, n * sizeof(int)); + int nextPowSpace = 1 << ilog2ceil(n); + + cudaMalloc((void**)&dev_i, nextPowSpace * sizeof(int)); checkCUDAError("cudaMalloc dev_i failed!"); - cudaMalloc((void**)&dev_b, n * sizeof(int)); + cudaMalloc((void**)&dev_b, nextPowSpace * sizeof(int)); checkCUDAError("cudaMalloc dev_b failed!"); - cudaMalloc((void**)&dev_f, n * sizeof(int)); + cudaMalloc((void**)&dev_f, nextPowSpace * sizeof(int)); checkCUDAError("cudaMalloc dev_f failed!"); - cudaMalloc((void**)&dev_d, n * sizeof(int)); + cudaMalloc((void**)&dev_d, nextPowSpace * sizeof(int)); checkCUDAError("cudaMalloc dev_d failed!"); - cudaMalloc((void**)&dev_o, n * sizeof(int)); + cudaMalloc((void**)&dev_o, nextPowSpace * sizeof(int)); checkCUDAError("cudaMalloc dev_o failed!"); cudaMemcpy(dev_i, idata, n * sizeof(int), cudaMemcpyHostToDevice); @@ -129,33 +131,33 @@ namespace StreamCompaction { for (int i = 0; i < 32; i++) { // isolate current bit to get i array - kernIsolateBit << > > (n, i, dev_i, dev_b); + kernIsolateBit << > > (nextPowSpace, i, dev_i, dev_b); int temp; cudaMemcpy(&temp, dev_b + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); - cudaMemcpy(dev_f, dev_b, n * sizeof(int), cudaMemcpyDeviceToDevice); + cudaMemcpy(dev_f, dev_b, nextPowSpace * sizeof(int), cudaMemcpyDeviceToDevice); // get f array by running scan on e // up-sweep for (int d = 0; d < ilog2ceil(n); d++) { - int n_adj = n / (1 << (d + 1)); + int n_adj = nextPowSpace / (1 << (d + 1)); dim3 fullBlocksPerGrid_adj((n_adj + blockSize - 1) / blockSize); - kernUpSweep << > > (n, d, dev_f); + kernUpSweep <<>> (nextPowSpace, d, dev_f); checkCUDAError("kernUpSweep failed!"); } - cudaMemset(dev_f + (n - 1), 0, sizeof(int)); + cudaMemset(dev_f + (nextPowSpace - 1), 0, sizeof(int)); // down-sweep for (int d = ilog2ceil(n) - 1; d >= 0; d--) { - int n_adj = n / (1 << (d + 1)); + int n_adj = nextPowSpace / (1 << (d + 1)); dim3 fullBlocksPerGrid_adj((n_adj + blockSize - 1) / blockSize); - kernDownSweep << > > (n, d, dev_f); + kernDownSweep << > > (nextPowSpace, d, dev_f); checkCUDAError("kernDownSweep failed!"); } @@ -171,6 +173,8 @@ namespace StreamCompaction { // match indices back kernReorder<<> > (n, dev_d, dev_i, dev_o); + + cudaMemcpy(dev_i, dev_o, nextPowSpace * sizeof(int), cudaMemcpyDeviceToDevice); } timer().endGpuTimer(); // ---------------- From 1bc881945726d9c040008db18f1b30b9522e394f Mon Sep 17 00:00:00 2001 From: Yuhan Liu <02yuhanliu@gmail.com> Date: Wed, 18 Sep 2024 17:13:29 +0200 Subject: [PATCH 05/12] add testing updates --- src/main.cpp | 60 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 60b3f8c..c56172d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -159,6 +159,38 @@ int main(int argc, char* argv[]) { // pow2 test + zeroArray(8, a); + std::cout << " " << std::endl; + std::cout << "Radix Sort, hard-coded lecture example test" << std::endl; + std::cout << " " << std::endl; + + // example from slides for debugging + a[0] = 4; + a[1] = 7; + a[2] = 2; + a[3] = 6; + a[4] = 3; + a[5] = 5; + a[6] = 1; + a[7] = 0; + + printArray(8, a, true); + + zeroArray(8, b); + printDesc("cpu sort (std::sort)"); + StreamCompaction::CPU::sort(8, b, a); + printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + printArray(8, b, true); + + zeroArray(8, c); + printDesc("radix sort"); + StreamCompaction::Radix::sort(8, c, a); + printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + printArray(8, c, true); + printCmpResult(8, b, c); + + // pow2 test + zeroArray(SIZE, a); std::cout << " " << std::endl; std::cout << "Radix Sort, pow2 consecutive ints" << std::endl; @@ -211,35 +243,25 @@ int main(int argc, char* argv[]) { // npot shuffled - genArray(SIZE, a, SIZE); + genArray(NPOT, a, NPOT); std::cout << " " << std::endl; std::cout << "Radix Sort, non-pow2 shuffled ints" << std::endl; std::cout << " " << std::endl; - // example from slides for debugging - /*a[0] = 4; - a[1] = 7; - a[2] = 2; - a[3] = 6; - a[4] = 3; - a[5] = 5; - a[6] = 1; - a[7] = 0;*/ - - printArray(SIZE, a, true); + printArray(NPOT, a, true); - zeroArray(SIZE, b); + zeroArray(NPOT, b); printDesc("cpu sort (std::sort)"); - StreamCompaction::CPU::sort(SIZE, b, a); + StreamCompaction::CPU::sort(NPOT, b, a); printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); - printArray(SIZE, b, true); + printArray(NPOT, b, true); - zeroArray(SIZE, c); + zeroArray(NPOT, c); printDesc("radix sort"); - StreamCompaction::Radix::sort(SIZE, c, a); + StreamCompaction::Radix::sort(NPOT, c, a); printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); + printArray(NPOT, c, true); + printCmpResult(NPOT, b, c); // END TESTS ---------------------------------------- From 9b0dfeed7984aeb7ea1b530af8e935fe50f4d136 Mon Sep 17 00:00:00 2001 From: Yuhan Liu <42754447+yuhanliu-tech@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:24:44 +0200 Subject: [PATCH 06/12] Begin writing README.md --- README.md | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0e38ddb..34c60f2 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,38 @@ CUDA Stream Compaction **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE - * (TODO) [LinkedIn](), [personal website](), [twitter](), etc. -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Yuhan Liu + * [LinkedIn](https://www.linkedin.com/in/yuhan-liu-), [personal website](https://liuyuhan.me/), [twitter](https://x.com/yuhanl_?lang=en), etc. +* Tested on: Windows 11 Pro, Ultra 7 155H @ 1.40 GHz 32GB, RTX 4060 8192MB (Personal Laptop) -### (TODO: Your README) +## README! -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +### Project Description +* XXXX + +### Performance Analysis + +**[Note] Block Size:** + +#### Comparison of GPU Scan Implementations + +| CPU | Naive | Work-Efficient | Thrust | +| :------------------------------: |:------------------------------: |:-----------------------------------------------: |:-----------------------------------------------:| +| xxxxx | xxxxxx |xxxxxx |xxxxxx | + +(Plot a graph of the comparison (with array size on the independent axis) + +**Explaining of Phenomena in the Graph** + +* Can you find the performance bottlenecks? Is it memory I/O? Computation? Is it different for each implementation? + +#### Output of Testing + +[box here] + +#### Additional Feature: Radix Sort + +* For radix sort, show how it is called and an example of its output. + +* Additional tests for radix sort From 07f08db00d2d6f1889d329233595b661b214564c Mon Sep 17 00:00:00 2001 From: Yuhan Liu <42754447+yuhanliu-tech@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:19:56 +0200 Subject: [PATCH 07/12] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 34c60f2..708bad5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ CUDA Stream Compaction ### Performance Analysis -**[Note] Block Size:** +**Optimizing Block Size** #### Comparison of GPU Scan Implementations @@ -29,9 +29,12 @@ CUDA Stream Compaction * Can you find the performance bottlenecks? Is it memory I/O? Computation? Is it different for each implementation? -#### Output of Testing +#### Output of Testing +(test array) SIZE: 2^19, blockSize: 128 -[box here] +``` + +``` #### Additional Feature: Radix Sort From 6f13030f19b167a3b922a1d081a835f72077c01b Mon Sep 17 00:00:00 2001 From: Yuhan Liu <02yuhanliu@gmail.com> Date: Wed, 18 Sep 2024 20:20:32 +0200 Subject: [PATCH 08/12] add graphs --- img/block_opt.png | Bin 0 -> 36303 bytes img/radix_perf.png | Bin 0 -> 54688 bytes img/scan_perf.png | Bin 0 -> 59432 bytes src/main.cpp | 2 +- stream_compaction/efficient.cu | 7 +++---- stream_compaction/naive.cu | 2 +- stream_compaction/radix.cu | 2 +- 7 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 img/block_opt.png create mode 100644 img/radix_perf.png create mode 100644 img/scan_perf.png diff --git a/img/block_opt.png b/img/block_opt.png new file mode 100644 index 0000000000000000000000000000000000000000..31dc9d86ae0d635ec436db53ab4fd89fa2bc8a02 GIT binary patch literal 36303 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 DjVh$< literal 0 HcmV?d00001 diff --git a/img/radix_perf.png b/img/radix_perf.png new file mode 100644 index 0000000000000000000000000000000000000000..137876e2f6da60c7cb5d2223bbc5911691b3b68f GIT binary patch literal 54688 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 literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index c56172d..7e7526b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,7 @@ #include "testing_helpers.hpp" #include -const int SIZE = 1 << 19; // feel free to change the size of array +const int SIZE = 1 << 12; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index f46281f..f9d9491 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -5,7 +5,7 @@ #include -#define blockSize 128 +#define blockSize 256 namespace StreamCompaction { namespace Efficient { @@ -42,8 +42,7 @@ namespace StreamCompaction { if (k >= n) { return; - } - else { + } else { int t = data[k + (1 << d) - 1]; // save left child data[k + (1 << d) - 1] = data[k + (1 << (d + 1)) - 1]; // set left child to this node's val data[k + (1 << (d + 1)) - 1] += t; // set right child to old left val + this node's val @@ -160,7 +159,7 @@ namespace StreamCompaction { timer().endGpuTimer(); // ---------------- - int count; + int count = 0; cudaMemcpy(&count, dev_scanned + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); count += (idata[n - 1] != 0); diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 84f21ea..ae8b408 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -5,7 +5,7 @@ #include -#define blockSize 128 +#define blockSize 256 namespace StreamCompaction { namespace Naive { diff --git a/stream_compaction/radix.cu b/stream_compaction/radix.cu index d67a1fe..4964c26 100644 --- a/stream_compaction/radix.cu +++ b/stream_compaction/radix.cu @@ -5,7 +5,7 @@ #include -#define blockSize 128 +#define blockSize 256 namespace StreamCompaction { namespace Radix { From 1a092ccb6f9d96958617c88839bbe43d7f509e0b Mon Sep 17 00:00:00 2001 From: Yuhan Liu <42754447+yuhanliu-tech@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:24:50 +0200 Subject: [PATCH 09/12] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 708bad5..33a06cc 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,16 @@ CUDA Stream Compaction **Optimizing Block Size** + + #### Comparison of GPU Scan Implementations | CPU | Naive | Work-Efficient | Thrust | | :------------------------------: |:------------------------------: |:-----------------------------------------------: |:-----------------------------------------------:| | xxxxx | xxxxxx |xxxxxx |xxxxxx | -(Plot a graph of the comparison (with array size on the independent axis) + + **Explaining of Phenomena in the Graph** @@ -41,3 +44,7 @@ CUDA Stream Compaction * For radix sort, show how it is called and an example of its output. * Additional tests for radix sort + +* Performance Evaluation + + From b5c8be92b245bbafaf9b99f5153371a911380975 Mon Sep 17 00:00:00 2001 From: Yuhan Liu <42754447+yuhanliu-tech@users.noreply.github.com> Date: Wed, 18 Sep 2024 21:31:14 +0200 Subject: [PATCH 10/12] Update README.md --- README.md | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 33a06cc..0132f1a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ CUDA Stream Compaction ### Project Description -* XXXX +* This project investigates parallel algorithms by implementing various versions of scan (prefix-sum), stream compaction (remove unwated elements), and sort algorithms. We begin with CPU implementations of all the aforementioned algorithms before developing GPU versions, including naive and work-efficient approaches, for comparison purposes. ### Performance Analysis @@ -19,13 +19,14 @@ CUDA Stream Compaction +* To decide on one block size for all parallel algorithm implementations, I assessed the naive and work-efficient runtimes with increasing block sizes. In the previous project, we established a tradeoff concerning block size between increasing GPU utilization and limiting resource availability for each block. Thus, we can choose an optimal block size by finding the dip in runtimes, which in this case (although close) we choose and proceed with a block size of 256. + #### Comparison of GPU Scan Implementations | CPU | Naive | Work-Efficient | Thrust | | :------------------------------: |:------------------------------: |:-----------------------------------------------: |:-----------------------------------------------:| | xxxxx | xxxxxx |xxxxxx |xxxxxx | - **Explaining of Phenomena in the Graph** @@ -33,18 +34,125 @@ CUDA Stream Compaction * Can you find the performance bottlenecks? Is it memory I/O? Computation? Is it different for each implementation? #### Output of Testing -(test array) SIZE: 2^19, blockSize: 128 +test array SIZE: 2^18, blockSize: 256 ``` - +**************** +** SCAN TESTS ** +**************** + [ 35 49 36 24 1 48 46 6 49 24 44 47 5 ... 2 0 ] +==== cpu scan, power-of-two ==== + elapsed time: 0.4305ms (std::chrono Measured) + [ 0 35 84 120 144 145 193 239 245 294 318 362 409 ... 6412415 6412417 ] +==== cpu scan, non-power-of-two ==== + elapsed time: 0.5392ms (std::chrono Measured) + [ 0 35 84 120 144 145 193 239 245 294 318 362 409 ... 6412308 6412343 ] + passed +==== naive scan, power-of-two ==== + elapsed time: 0.368768ms (CUDA Measured) + passed +==== naive scan, non-power-of-two ==== + elapsed time: 0.219936ms (CUDA Measured) + passed +==== work-efficient scan, power-of-two ==== + elapsed time: 0.333856ms (CUDA Measured) + passed +==== work-efficient scan, non-power-of-two ==== + elapsed time: 0.366688ms (CUDA Measured) + passed +==== thrust scan, power-of-two ==== + elapsed time: 1.55728ms (CUDA Measured) + passed +==== thrust scan, non-power-of-two ==== + elapsed time: 0.344288ms (CUDA Measured) + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 1 3 0 0 3 2 0 2 1 2 0 1 3 ... 2 1 ] +==== cpu compact without scan, power-of-two ==== + elapsed time: 0.6932ms (std::chrono Measured) + [ 1 3 3 2 2 1 2 1 3 3 2 2 3 ... 2 1 ] + passed +==== cpu compact without scan, non-power-of-two ==== + elapsed time: 0.7058ms (std::chrono Measured) + [ 1 3 3 2 2 1 2 1 3 3 2 2 3 ... 1 1 ] + passed +==== cpu compact with scan ==== + elapsed time: 1.3968ms (std::chrono Measured) + [ 1 3 3 2 2 1 2 1 3 3 2 2 3 ... 2 1 ] + passed +==== work-efficient compact, power-of-two ==== + elapsed time: 0.445152ms (CUDA Measured) + passed +==== work-efficient compact, non-power-of-two ==== + elapsed time: 0.665728ms (CUDA Measured) + passed + +***************************** +***** RADIX SORT TESTS ****** +***************************** + +Radix Sort, hard-coded lecture example test + + [ 4 7 2 6 3 5 1 0 ] +==== cpu sort (std::sort) ==== + elapsed time: 0.0002ms (std::chrono Measured) + [ 0 1 2 3 4 5 6 7 ] +==== radix sort ==== + elapsed time: 5.5552ms (CUDA Measured) + [ 0 1 2 3 4 5 6 7 ] + passed + +Radix Sort, pow2 consecutive ints + + [ 0 1 2 3 4 5 6 7 8 9 10 11 12 ... 262142 262143 ] +==== cpu sort (std::sort) ==== + elapsed time: 1.4753ms (std::chrono Measured) + [ 0 1 2 3 4 5 6 7 8 9 10 11 12 ... 262142 262143 ] +==== radix sort ==== + elapsed time: 13.777ms (CUDA Measured) + [ 0 1 2 3 4 5 6 7 8 9 10 11 12 ... 262142 262143 ] + passed + +Radix Sort, non-pow2 consecutive ints + + [ 0 1 2 3 4 5 6 7 8 9 10 11 12 ... 262139 262140 ] +==== cpu sort (std::sort) ==== + elapsed time: 1.3184ms (std::chrono Measured) + [ 0 1 2 3 4 5 6 7 8 9 10 11 12 ... 262139 262140 ] +==== radix sort ==== + elapsed time: 13.37ms (CUDA Measured) + [ 0 1 2 3 4 5 6 7 8 9 10 11 12 ... 262139 262140 ] + passed + +Radix Sort, non-pow2 shuffled ints + + [ 4785 499 16736 27824 14951 31998 8696 23806 7549 30974 31344 14697 29955 ... 21785 7497 ] +==== cpu sort (std::sort) ==== + elapsed time: 14.9359ms (std::chrono Measured) + [ 0 0 0 0 0 0 0 0 1 1 1 1 2 ... 32767 32767 ] +==== radix sort ==== + elapsed time: 18.8853ms (CUDA Measured) + [ 0 0 0 0 0 0 0 0 1 1 1 1 2 ... 32767 32767 ] + passed ``` #### Additional Feature: Radix Sort -* For radix sort, show how it is called and an example of its output. +* I implemented parallel radix sort as an additional module to the `stream_compaction` subproject (and additionally, a CPU std::sort function for comparison). + +* The radix sort function takes as input, the size of the array, along with pointers to the input and output arrays. Below is a code snippet from a radix test in the main method, showing how it is called: + +``` +StreamCompaction::Radix::sort(SIZE, c, a); // a is the array to be sorted, which is already-existing +printArray(SIZE, c, true); // print the output sorted array, which is saved in c +``` -* Additional tests for radix sort +* I wrote several tests for radix sort, in which I compare both the correctness and runtime of my implementation to the CPU equivalent (std::sort). First, I compared radix sort with CPU sort on the fixed array [4, 7, 2, 6, 3, 5, 1, 0] from lecture. Then, I evaluated radix sort on sequential arrays of size power-of-two and non-power-of-two sizes. Lastly, I tested radix sort on a shuffled arrays. For each test, I compared the results and performance of radix sort with CPU sort, measuring CUDA performance where applicable. -* Performance Evaluation +**Radix sort Performance Evaluation** +* Radix sort performs worse than std::sort for smaller array sizes due to overhead and initialization costs associated with GPU processing and memory. However, as the array size grows, radix sort's performance improves relative to std::sort. This makes sense with the performance graph as radix sort has a complexity of O(n⋅k), where k is the number of bits, while standard CPU sort has a complexity of O(n*log(n)). From b1601ff2175f94655527a4d7cde1364c36bc910a Mon Sep 17 00:00:00 2001 From: Yuhan Liu <02yuhanliu@gmail.com> Date: Wed, 18 Sep 2024 22:16:18 +0200 Subject: [PATCH 11/12] fix scatter bug --- src/main.cpp | 3 +-- src/testing_helpers.hpp | 1 - stream_compaction/efficient.cu | 13 +++++++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7e7526b..1848896 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,7 @@ #include "testing_helpers.hpp" #include -const int SIZE = 1 << 12; // feel free to change the size of array +const int SIZE = 1 << 22; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; @@ -269,5 +269,4 @@ int main(int argc, char* argv[]) { delete[] a; delete[] b; delete[] c; - } diff --git a/src/testing_helpers.hpp b/src/testing_helpers.hpp index 025e94a..8f08d88 100644 --- a/src/testing_helpers.hpp +++ b/src/testing_helpers.hpp @@ -51,7 +51,6 @@ void onesArray(int n, int *a) { void genArray(int n, int *a, int maxval) { srand(time(nullptr)); - for (int i = 0; i < n; i++) { a[i] = rand() % maxval; } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index f9d9491..fd4355c 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -73,6 +73,7 @@ namespace StreamCompaction { kernUpSweep<<>>(nextPowSpace, d, dev_idata); checkCUDAError("kernUpSweep failed!"); + cudaDeviceSynchronize(); } cudaMemset(dev_idata + (nextPowSpace - 1), 0, sizeof(int)); @@ -85,6 +86,7 @@ namespace StreamCompaction { kernDownSweep<<>>(nextPowSpace, d, dev_idata); checkCUDAError("kernDownSweep failed!"); + cudaDeviceSynchronize(); } timer().endGpuTimer(); // -------------------------------- @@ -107,6 +109,7 @@ namespace StreamCompaction { int* dev_idata; int* dev_scanned; int* dev_bools; + int* dev_odata; int nextPowSpace = 1 << ilog2ceil(n); @@ -120,6 +123,9 @@ namespace StreamCompaction { cudaMalloc((void**)&dev_scanned, nextPowSpace * sizeof(int)); checkCUDAError("cudaMalloc dev_scattered failed!"); + cudaMalloc((void**)&dev_odata, nextPowSpace * sizeof(int)); + checkCUDAError("cudaMalloc dev_odata failed!"); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); timer().startGpuTimer(); // -------------- @@ -138,6 +144,7 @@ namespace StreamCompaction { kernUpSweep << > > (nextPowSpace, d, dev_scanned); checkCUDAError("kernUpSweep failed!"); + cudaDeviceSynchronize(); } cudaMemset(dev_scanned + (nextPowSpace - 1), 0, sizeof(int)); @@ -150,12 +157,13 @@ namespace StreamCompaction { kernDownSweep <<>> (nextPowSpace, d, dev_scanned); checkCUDAError("kernDownSweep failed!"); + cudaDeviceSynchronize(); } // end scan stuff ------------------| // scatter - StreamCompaction::Common::kernScatter <<>> (n, dev_idata, dev_idata, dev_bools, dev_scanned); + StreamCompaction::Common::kernScatter<<>> (n, dev_odata, dev_idata, dev_bools, dev_scanned); timer().endGpuTimer(); // ---------------- @@ -163,11 +171,12 @@ namespace StreamCompaction { cudaMemcpy(&count, dev_scanned + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); count += (idata[n - 1] != 0); - cudaMemcpy(odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(odata, dev_odata, n * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_idata); cudaFree(dev_bools); cudaFree(dev_scanned); + cudaFree(dev_odata); return count; } From 40f6cbd15b3c70dc9244d39eef352a8cb36cf41c Mon Sep 17 00:00:00 2001 From: Yuhan Liu <42754447+yuhanliu-tech@users.noreply.github.com> Date: Wed, 18 Sep 2024 22:55:12 +0200 Subject: [PATCH 12/12] Finish README.md --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0132f1a..03288a6 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,10 @@ CUDA Stream Compaction | CPU | Naive | Work-Efficient | Thrust | | :------------------------------: |:------------------------------: |:-----------------------------------------------: |:-----------------------------------------------:| -| xxxxx | xxxxxx |xxxxxx |xxxxxx | +| For smaller datasets, the overhead of managing parallel execution on a GPU (e.g., kernel launches, thread synchronization) offsets the parallel advantage, making the CPU more efficient. However, as data scales, the complexity of scanning on the CPU increases linearly. In the graph below where array sizes increase exponentially, the complexity of CPU scanning does so as well. | The naive parallel approach rivals the work-efficient approach for most of the array sizes below. However, this method has a memory latency bottleneck: its excessive use of global memory in the summation causes the runtime to swell as the array size grows. | The work-efficient GPU scan starts with more overhead than the naive implementation for smaller arrays, but as they grow larger, it outperforms both the CPU and naive versions. The work-efficient binary tree structure reduces unnecessary memory operations and optimizes thread usage by performing operations in place. The bottleneck here is thread usage: the binary tree structure leaves threads underutilized as the number of operations at each step in the algorithm reduces. Because of this, more primitive methods like naive and CPU come close in runtime for smaller array sizes. | Referencing the NVIDIA developer [documentation](https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda) for exclusive scan, we notice that the Thrust implementation employs techniques to improve performance, including optimizing shared memory and load balancing. Setting up these optimizations, like bankconflict avoidance and loop unrolling, introduces overhead, which make more primitive methods more efficient for small array sizes. However, even in the graph below, the thrust scan implementation clearly does not scale as significantly as any of the other scan implementations. | -**Explaining of Phenomena in the Graph** - -* Can you find the performance bottlenecks? Is it memory I/O? Computation? Is it different for each implementation? - #### Output of Testing test array SIZE: 2^18, blockSize: 256 @@ -139,9 +135,9 @@ Radix Sort, non-pow2 shuffled ints passed ``` -#### Additional Feature: Radix Sort +### Additional Feature: Radix Sort -* I implemented parallel radix sort as an additional module to the `stream_compaction` subproject (and additionally, a CPU std::sort function for comparison). +* I implemented parallel radix sort as an additional module to the `stream_compaction` subproject (and additionally, a CPU std::sort function for comparison). The implementation can be found in `radix.cu` and `radix.h`. * The radix sort function takes as input, the size of the array, along with pointers to the input and output arrays. Below is a code snippet from a radix test in the main method, showing how it is called: