From e1b4e1f5370ecad5316ac17dcc67a31cdbe070dd Mon Sep 17 00:00:00 2001 From: nischalkn Date: Tue, 20 Sep 2016 00:24:08 -0400 Subject: [PATCH 01/13] CPU scan and compaction implemented --- stream_compaction/cpu.cu | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index e600c29..ff7bd2c 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -8,8 +8,11 @@ namespace CPU { * CPU scan (prefix sum). */ void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); + odata[0] = 0; + for (int i = 1; i < n; i++) + { + odata[i] = odata[i - 1] + idata[i - 1]; + } } /** @@ -18,8 +21,16 @@ void scan(int n, int *odata, const int *idata) { * @returns the number of elements remaining after compaction. */ int compactWithoutScan(int n, int *odata, const int *idata) { - // TODO - return -1; + long j = 0; + for (int i = 0; i < n; i++) + { + if (idata[i] != 0) + { + odata[j] = idata[i]; + j++; + } + } + return j; } /** @@ -28,8 +39,25 @@ int compactWithoutScan(int n, int *odata, const int *idata) { * @returns the number of elements remaining after compaction. */ int compactWithScan(int n, int *odata, const int *idata) { - // TODO - return -1; + int *temporal; + int *pscan; + long j = 0; + temporal = (int*)malloc(sizeof(int)*n); + pscan = (int*)malloc(sizeof(int)*n); + for (int i = 0; i < n; i++) + { + temporal[i] = idata[i] ? 1 : 0; + } + scan(n, pscan, temporal); + for (int i = 0; i < n; i++) + { + if (temporal[i] == 1) + { + odata[pscan[i]] = idata[i]; + j++; + } + } + return j; } } From cadc12269fde648963d6e79e76f92955eaa258fb Mon Sep 17 00:00:00 2001 From: nischalkn Date: Thu, 22 Sep 2016 03:51:14 -0400 Subject: [PATCH 02/13] Implemented Niave scan --- stream_compaction/naive.cu | 62 +++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 3d86b60..6e75f5e 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,18 +3,64 @@ #include "common.h" #include "naive.h" +#define blockSize 128 + namespace StreamCompaction { namespace Naive { -// TODO: __global__ + __global__ void naiveScan(int n, int *odata, int *idata, int val) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) { + return; + } + if (index >= val) { + odata[index] = idata[index - val] + idata[index]; + } + else { + odata[index] = idata[index]; + } + } -/** - * Performs prefix-sum (aka scan) on idata, storing the result into odata. - */ -void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); -} + /** + * Performs prefix-sum (aka scan) on idata, storing the result into odata. + */ + void scan(int n, int *odata, const int *idata) { + int *dev_odata; + int *dev_idata; + int flag = 1; + int val; + + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + + cudaMemcpy(dev_idata, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + for (int d = 1; d <= ilog2ceil(n); d++) { //ilog2ceil(n) + val = pow(2, d - 1); + if (flag == 1) { + naiveScan << > >(n, dev_odata, dev_idata, val); + flag = 0; + } + else { + naiveScan << > >(n, dev_idata, dev_odata, val); + flag = 1; + } + cudaThreadSynchronize(); + } + + odata[0] = 0; + if (flag == 0) { + cudaMemcpy(odata+1, dev_odata, (n-1)*sizeof(int), cudaMemcpyDeviceToHost); + } + else { + cudaMemcpy(odata+1, dev_idata, (n-1)*sizeof(int), cudaMemcpyDeviceToHost); + } + + cudaFree(dev_idata); + cudaFree(dev_odata); + } } } From 49de67d5136833302bc886ff46dddc5fab9c1d8c Mon Sep 17 00:00:00 2001 From: nischalkn Date: Thu, 22 Sep 2016 03:52:03 -0400 Subject: [PATCH 03/13] implemented efficient scan and compaction --- stream_compaction/efficient.cu | 146 ++++++++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 21 deletions(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index b2f739b..c20d6f7 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,32 +3,136 @@ #include "common.h" #include "efficient.h" +#define blockSize 128 + namespace StreamCompaction { namespace Efficient { -// TODO: __global__ + __global__ void upSweep(int n, int *idata, int d) { + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + if (k >= n) + return; + if (k % (d * 2) == (d * 2) - 1) { + idata[k] += idata[k - d]; + } -/** - * Performs prefix-sum (aka scan) on idata, storing the result into odata. - */ -void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); -} + } -/** - * Performs stream compaction on idata, storing the result into odata. - * All zeroes are discarded. - * - * @param n The number of elements in idata. - * @param odata The array into which to store elements. - * @param idata The array of elements to compact. - * @returns The number of elements remaining after compaction. - */ -int compact(int n, int *odata, const int *idata) { - // TODO - return -1; -} + __global__ void downSweep(int n, int *idata, int d) { + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + if (k >= n) + return; + int temp; + if (k % (d * 2) == (d * 2) - 1) { + //printf("kernel: %d", k); + temp = idata[k - d]; + idata[k - d] = idata[k]; // Set left child to this node’s value + idata[k] += temp; + } + + } + + __global__ void makeElementZero(int *data, int index) { + int k = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index == k) { + data[k] = 0; + } + } + + __global__ void copyElements(int n, int *src, int *dest) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + dest[index] = src[index]; + } + + /** + * 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 paddedArraySize = 1 << ilog2ceil(n); + + dim3 fullBlocksPerGrid((paddedArraySize + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_idata, paddedArraySize * sizeof(int)); + checkCUDAError("Cannot allocate memory for idata"); + + cudaMemcpy(dev_idata, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + for (int d = 0; d < ilog2ceil(paddedArraySize); d++) { + upSweep << > >(paddedArraySize, dev_idata, 1<> >(dev_idata, paddedArraySize - 1); + + for (int d = ilog2ceil(paddedArraySize) - 1; d >= 0; d--) { + downSweep << > >(paddedArraySize, dev_idata, 1<> >(n, dev_boolean, dev_idata); + + copyElements << > >(n, dev_boolean, dev_indices); + + for (int d = 0; d < ilog2ceil(paddedArraySize); d++) { + upSweep << > >(paddedArraySize, dev_indices, 1 << d); + } + + makeElementZero << > >(dev_indices, paddedArraySize - 1); + + for (int d = ilog2ceil(paddedArraySize) - 1; d >= 0; d--) { + downSweep << > >(paddedArraySize, dev_indices, 1 << d); + } + + StreamCompaction::Common::kernScatter << > >(n, dev_odata, dev_idata, dev_boolean, dev_indices); + + cudaMemcpy(odata, dev_odata, n*sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&count, dev_indices+n-1, sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_idata); + cudaFree(dev_odata); + cudaFree(dev_boolean); + cudaFree(dev_indices); + return count; + } } } From a29fb3350d6cf287518a3ce56397300f83ba8c87 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Thu, 22 Sep 2016 03:52:42 -0400 Subject: [PATCH 04/13] trust scan added --- stream_compaction/thrust.cu | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index d8dbb32..68a898d 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -9,14 +9,19 @@ namespace StreamCompaction { namespace Thrust { -/** - * Performs prefix-sum (aka scan) on idata, storing the result into odata. - */ -void scan(int n, int *odata, const int *idata) { - // 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()); -} + /** + * Performs prefix-sum (aka scan) on idata, storing the result into odata. + */ + void scan(int n, int *odata, const int *idata) { + // 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()); + thrust::device_vector dv_in(n); + thrust::device_vector dv_out(n); + thrust::copy(idata, idata + n - 1, dv_in.begin()); + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + thrust::copy(dv_out.begin(), dv_out.end(), odata); + } } } From 87fbe9e26248e4ff9b2d340845ef803e021fbedd Mon Sep 17 00:00:00 2001 From: nischalkn Date: Thu, 22 Sep 2016 03:53:36 -0400 Subject: [PATCH 05/13] common functions added --- stream_compaction/common.cu | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index fe872d4..4e0d3e6 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -23,7 +23,10 @@ namespace Common { * 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 index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + bools[index] = idata[index] ? 1 : 0; } /** @@ -32,7 +35,11 @@ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { */ __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { - // TODO + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + if (bools[index] == 1) + odata[indices[index]] = idata[index]; } } From c35b823f2df5a8dcca5e1ebc10f244f9852d67b6 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Thu, 22 Sep 2016 06:08:37 -0400 Subject: [PATCH 06/13] Added radix sort test --- src/main.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 675da35..c80b983 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "testing_helpers.hpp" int main(int argc, char* argv[]) { @@ -112,12 +113,23 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("work-efficient compact, power-of-two"); count = StreamCompaction::Efficient::compact(SIZE, c, a); - //printArray(count, c, true); + printArray(count, c, true); printCmpLenResult(count, expectedCount, b, c); zeroArray(SIZE, c); printDesc("work-efficient compact, non-power-of-two"); count = StreamCompaction::Efficient::compact(NPOT, c, a); - //printArray(count, c, true); + printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); + + printf("\n"); + printf("*****************************\n"); + printf("***** RADIX SORT TESTS ******\n"); + printf("*****************************\n"); + + int t1[10] = { 4, 1, 6, 3, 8, 9, 2, 0, 5, 7 }; + int t2[10]; + printDesc("Radix Sort"); + StreamCompaction::radixSort::sort(10, t2, t1); + printArray(10, t2, true); } From a8e39ca10f7b93f49dbd952e422cee8a529ccff7 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Thu, 22 Sep 2016 06:08:53 -0400 Subject: [PATCH 07/13] Radix sort implemented --- stream_compaction/CMakeLists.txt | 2 + stream_compaction/radixSort.cu | 105 +++++++++++++++++++++++++++++++ stream_compaction/radixSort.h | 7 +++ 3 files changed, 114 insertions(+) create mode 100644 stream_compaction/radixSort.cu create mode 100644 stream_compaction/radixSort.h diff --git a/stream_compaction/CMakeLists.txt b/stream_compaction/CMakeLists.txt index cdbef77..3dfb146 100644 --- a/stream_compaction/CMakeLists.txt +++ b/stream_compaction/CMakeLists.txt @@ -9,6 +9,8 @@ set(SOURCE_FILES "efficient.cu" "thrust.h" "thrust.cu" + "radixSort.h" + "radixSort.cu" ) cuda_add_library(stream_compaction diff --git a/stream_compaction/radixSort.cu b/stream_compaction/radixSort.cu new file mode 100644 index 0000000..1528a9f --- /dev/null +++ b/stream_compaction/radixSort.cu @@ -0,0 +1,105 @@ +#include +#include +#include "common.h" +#include "efficient.h" + +#define blockSize 128 + +namespace StreamCompaction { + namespace radixSort { + + __global__ void makeBarray(int n, int *odata, int *idata, int d) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + int val = idata[index] & d; + odata[index] = val ? 1 : 0; + } + + __global__ void makeEarray(int n, int *odata, int *idata) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + odata[index] = idata[index] ? 0 : 1; + } + + __global__ void makeTarray(int n, int *odata, int *idata,int totalFalse) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + odata[index] = index - idata[index] + totalFalse; + } + + __global__ void makeDarray(int n, int *odata, int *b, int *t, int *f) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + odata[index] = b[index] ? t[index] : f[index]; + } + + __global__ void reorder(int n, int *odata, int *idata, int *d) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + odata[d[index]] = idata[index]; + } + + void sort(int n, int *odata, const int *idata) { + int *dev_idata; + int *dev_odata; + int *dev_b; + int *dev_f; + int *dev_t; + int *dev_d; + + int last_e; + int last_f; + + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + checkCUDAError("Cannot allocate memory for idata"); + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + checkCUDAError("Cannot allocate memory for odata"); + cudaMalloc((void**)&dev_b, n * sizeof(int)); + checkCUDAError("Cannot allocate memory for odata"); + cudaMalloc((void**)&dev_f, n * sizeof(int)); + checkCUDAError("Cannot allocate memory for odata"); + cudaMalloc((void**)&dev_t, n * sizeof(int)); + checkCUDAError("Cannot allocate memory for odata"); + cudaMalloc((void**)&dev_d, n * sizeof(int)); + checkCUDAError("Cannot allocate memory for odata"); + + cudaMemcpy(dev_idata, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + for (int i = 0; i < ilog2ceil(n); i++){ + makeBarray << > >(n, dev_b, dev_idata, 1 << i); + makeEarray << > >(n, dev_f, dev_b); + StreamCompaction::Efficient::scan(n, dev_f, dev_f); + + cudaMemcpy(&last_e, dev_b + n - 1, sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&last_f, dev_f + n - 1, sizeof(int), cudaMemcpyDeviceToHost); + + int totalFalses = (last_e ? 0 : 1) + last_f; + + makeTarray << > >(n, dev_t, dev_f, totalFalses); + makeDarray << > >(n, dev_d, dev_b, dev_t, dev_f); + + reorder << > >(n, dev_odata, dev_idata, dev_d); + + int *temp = dev_odata; + dev_odata = dev_idata; + dev_idata = temp; + } + cudaMemcpy(odata, dev_idata, n*sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_idata); + cudaFree(dev_odata); + cudaFree(dev_b); + cudaFree(dev_f); + cudaFree(dev_t); + cudaFree(dev_d); + } + + } +} diff --git a/stream_compaction/radixSort.h b/stream_compaction/radixSort.h new file mode 100644 index 0000000..5184533 --- /dev/null +++ b/stream_compaction/radixSort.h @@ -0,0 +1,7 @@ +#pragma once + +namespace StreamCompaction { + namespace radixSort { + void sort(int n, int *odata, const int *idata); + } +} From 9d0676779dae7e1705e768cb712cef2d7985b427 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Tue, 27 Sep 2016 08:59:02 -0400 Subject: [PATCH 08/13] thrust implementation for sorting --- stream_compaction/thrust.cu | 42 +++++++++++++++++++++++++++++++++++++ stream_compaction/thrust.h | 1 + 2 files changed, 43 insertions(+) diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 68a898d..3922eaf 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -3,6 +3,7 @@ #include #include #include +#include #include "common.h" #include "thrust.h" @@ -19,7 +20,48 @@ namespace Thrust { thrust::device_vector dv_in(n); thrust::device_vector dv_out(n); thrust::copy(idata, idata + n - 1, dv_in.begin()); + + #if PROFILE + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + #endif + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + + #if PROFILE + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + std::cout << "Time Elapsed for trust scan (size " << n << "): " << milliseconds << std::endl; + #endif + + thrust::copy(dv_out.begin(), dv_out.end(), odata); + } + + void sort(int n, int *odata, const int *idata) { + thrust::device_vector dv_out(n); + thrust::copy(idata, idata + n, dv_out.begin()); + + #if PROFILE + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + #endif + + thrust::sort(dv_out.begin(), dv_out.end()); + + #if PROFILE + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + std::cout << "Time Elapsed for trust sort (size " << n << "): " << milliseconds << std::endl; + #endif + thrust::copy(dv_out.begin(), dv_out.end(), odata); } diff --git a/stream_compaction/thrust.h b/stream_compaction/thrust.h index 06707f3..6187545 100644 --- a/stream_compaction/thrust.h +++ b/stream_compaction/thrust.h @@ -3,5 +3,6 @@ namespace StreamCompaction { namespace Thrust { void scan(int n, int *odata, const int *idata); + void sort(int n, int *odata, const int *idata); } } From 562100061954e7ba8affa63d38ced16c57f26344 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Tue, 27 Sep 2016 08:59:55 -0400 Subject: [PATCH 09/13] Added tests for sorting --- src/main.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c80b983..2f67a25 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,7 @@ #include "testing_helpers.hpp" int main(int argc, char* argv[]) { - const int SIZE = 1 << 8; + const int SIZE = 1 << 16; const int NPOT = SIZE - 3; int a[SIZE], b[SIZE], c[SIZE]; @@ -126,10 +126,28 @@ int main(int argc, char* argv[]) { printf("*****************************\n"); printf("***** RADIX SORT TESTS ******\n"); printf("*****************************\n"); - + int t1[10] = { 4, 1, 6, 3, 8, 9, 2, 0, 5, 7 }; int t2[10]; - printDesc("Radix Sort"); - StreamCompaction::radixSort::sort(10, t2, t1); + int t3[10]; + printDesc("thrust sort small array"); + StreamCompaction::Thrust::sort(10, t2, t1); printArray(10, t2, true); + + printDesc("Radix Sort small array"); + StreamCompaction::radixSort::sort(10, t3, t1); + printArray(10, t3, true); + printCmpResult(10, t2, t3); + + genArray(SIZE, a, 200); + zeroArray(SIZE, c); + printDesc("thrust sort large array"); + StreamCompaction::Thrust::sort(SIZE, c, a); + printArray(SIZE, c, true); + + zeroArray(SIZE, b); + printDesc("Radix Sort large array"); + StreamCompaction::radixSort::sort(SIZE, b, a); + printArray(SIZE, b, true); + printCmpResult(10, c, b); } From be3fa6fb877564db33e4e4c4dd89a21fbf07fd58 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Tue, 27 Sep 2016 09:00:12 -0400 Subject: [PATCH 10/13] Added profiling flag --- stream_compaction/common.h | 5 +++++ stream_compaction/cpu.cu | 32 ++++++++++++++++++++++++++++++++ stream_compaction/efficient.cu | 32 +++++++++++++++++++++++++++++++- stream_compaction/naive.cu | 19 +++++++++++++++++++ stream_compaction/radixSort.cu | 16 ++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) diff --git a/stream_compaction/common.h b/stream_compaction/common.h index 4f52663..afb4101 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -3,6 +3,11 @@ #include #include #include +#include +#include +#include + +#define PROFILE 0 #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index ff7bd2c..f4c0dac 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -1,5 +1,6 @@ #include #include "cpu.h" +#include "common.h" namespace StreamCompaction { namespace CPU { @@ -9,10 +10,20 @@ namespace CPU { */ void scan(int n, int *odata, const int *idata) { odata[0] = 0; + + #if PROFILE + auto begin = std::chrono::high_resolution_clock::now(); + #endif + for (int i = 1; i < n; i++) { odata[i] = odata[i - 1] + idata[i - 1]; } + + #if PROFILE + auto end = std::chrono::high_resolution_clock::now(); + std::cout << "Time Elapsed for cpu scan(size " << n << "): " << std::chrono::duration_cast(end - begin).count() << "ns" << std::endl; + #endif } /** @@ -22,6 +33,11 @@ void scan(int n, int *odata, const int *idata) { */ int compactWithoutScan(int n, int *odata, const int *idata) { long j = 0; + + #if PROFILE + auto begin = std::chrono::high_resolution_clock::now(); + #endif + for (int i = 0; i < n; i++) { if (idata[i] != 0) @@ -30,6 +46,12 @@ int compactWithoutScan(int n, int *odata, const int *idata) { j++; } } + + #if PROFILE + auto end = std::chrono::high_resolution_clock::now(); + std::cout << "Time Elapsed for compact without scan(size " << n << "): " << std::chrono::duration_cast(end - begin).count() << "ns" << std::endl; + #endif + return j; } @@ -44,6 +66,10 @@ int compactWithScan(int n, int *odata, const int *idata) { long j = 0; temporal = (int*)malloc(sizeof(int)*n); pscan = (int*)malloc(sizeof(int)*n); + + #if PROFILE + auto begin = std::chrono::high_resolution_clock::now(); + #endif for (int i = 0; i < n; i++) { temporal[i] = idata[i] ? 1 : 0; @@ -57,6 +83,12 @@ int compactWithScan(int n, int *odata, const int *idata) { j++; } } + + #if PROFILE + auto end = std::chrono::high_resolution_clock::now(); + std::cout << "Time Elapsed for compact with scan(size " << n << "): " << std::chrono::duration_cast(end - begin).count() << "ns" << std::endl; + #endif + return j; } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index c20d6f7..9595e72 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -61,6 +61,13 @@ namespace Efficient { cudaMemcpy(dev_idata, idata, n*sizeof(int), cudaMemcpyHostToDevice); + #if PROFILE + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + #endif + for (int d = 0; d < ilog2ceil(paddedArraySize); d++) { upSweep << > >(paddedArraySize, dev_idata, 1<= 0; d--) { downSweep << > >(paddedArraySize, dev_idata, 1<> >(n, dev_boolean, dev_idata); copyElements << > >(n, dev_boolean, dev_indices); @@ -124,8 +146,16 @@ namespace Efficient { StreamCompaction::Common::kernScatter << > >(n, dev_odata, dev_idata, dev_boolean, dev_indices); + #if PROFILE + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + std::cout << "Time Elapsed for Efficient compact (size " << n << "): " << milliseconds << std::endl; + #endif + cudaMemcpy(odata, dev_odata, n*sizeof(int), cudaMemcpyDeviceToHost); - cudaMemcpy(&count, dev_indices+n-1, sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&count, dev_indices + paddedArraySize - 1, sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_idata); cudaFree(dev_odata); diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 6e75f5e..7af9804 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -5,6 +5,10 @@ #define blockSize 128 +#if PROFILE +cudaEvent_t start, stop; +#endif + namespace StreamCompaction { namespace Naive { @@ -37,6 +41,12 @@ namespace Naive { cudaMemcpy(dev_idata, idata, n*sizeof(int), cudaMemcpyHostToDevice); + #if PROFILE + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + #endif + for (int d = 1; d <= ilog2ceil(n); d++) { //ilog2ceil(n) val = pow(2, d - 1); if (flag == 1) { @@ -51,6 +61,15 @@ namespace Naive { } odata[0] = 0; + + #if PROFILE + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + std::cout << "Time Elapsed for Naive scan (size " << n << "): " << milliseconds << std::endl; + #endif + if (flag == 0) { cudaMemcpy(odata+1, dev_odata, (n-1)*sizeof(int), cudaMemcpyDeviceToHost); } diff --git a/stream_compaction/radixSort.cu b/stream_compaction/radixSort.cu index 1528a9f..1d21aa2 100644 --- a/stream_compaction/radixSort.cu +++ b/stream_compaction/radixSort.cu @@ -71,6 +71,13 @@ namespace StreamCompaction { checkCUDAError("Cannot allocate memory for odata"); cudaMemcpy(dev_idata, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + #if PROFILE + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + #endif for (int i = 0; i < ilog2ceil(n); i++){ makeBarray << > >(n, dev_b, dev_idata, 1 << i); @@ -91,6 +98,15 @@ namespace StreamCompaction { dev_odata = dev_idata; dev_idata = temp; } + + #if PROFILE + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + std::cout << "Time Elapsed for radix sort (size " << n << "): " << milliseconds << std::endl; + #endif + cudaMemcpy(odata, dev_idata, n*sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_idata); From d6a0efab83886214a2acc9ac2b501b586de44587 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Tue, 27 Sep 2016 18:16:17 -0400 Subject: [PATCH 11/13] Printed output array for all tests --- src/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 2f67a25..45c0dee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,37 +44,37 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("naive scan, power-of-two"); StreamCompaction::Naive::scan(SIZE, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); printDesc("naive scan, non-power-of-two"); StreamCompaction::Naive::scan(NPOT, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(NPOT, b, c); zeroArray(SIZE, c); printDesc("work-efficient scan, power-of-two"); StreamCompaction::Efficient::scan(SIZE, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); printDesc("work-efficient scan, non-power-of-two"); StreamCompaction::Efficient::scan(NPOT, c, a); - //printArray(NPOT, c, true); + printArray(NPOT, c, true); printCmpResult(NPOT, b, c); zeroArray(SIZE, c); printDesc("thrust scan, power-of-two"); StreamCompaction::Thrust::scan(SIZE, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); printDesc("thrust scan, non-power-of-two"); StreamCompaction::Thrust::scan(NPOT, c, a); - //printArray(NPOT, c, true); + printArray(NPOT, c, true); printCmpResult(NPOT, b, c); printf("\n"); From 195cc05cfced62111ca89e27d2ec48e855804b87 Mon Sep 17 00:00:00 2001 From: nischalkn Date: Tue, 27 Sep 2016 18:16:32 -0400 Subject: [PATCH 12/13] updated readme --- README.md | 101 +++++++++++++++++++++++++++++++++++++-- images/compact_array.png | Bin 0 -> 21934 bytes images/compact_block.png | Bin 0 -> 19027 bytes images/scan_array.png | Bin 0 -> 27119 bytes images/scan_block.png | Bin 0 -> 23047 bytes images/sort.png | Bin 0 -> 22602 bytes 6 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 images/compact_array.png create mode 100644 images/compact_block.png create mode 100644 images/scan_array.png create mode 100644 images/scan_block.png create mode 100644 images/sort.png diff --git a/README.md b/README.md index b71c458..3660bd5 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,102 @@ CUDA Stream Compaction **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Nischal K N +* Tested on: Windows 10, i7-2670QM @ 2.20GHz 8GB, GTX 540 2GB (Personal) -### (TODO: Your README) +### SUMMARY +This project implements scan, stream compaction and sorting algorithms implemented on GPU and its performance compared against a CPU implementation. Two kinds of exclusive scans are implemented, viz., naive and efficient. Stream compaction is implemented using the efficient GPU scan implementation. Additionally Radix sort is also implemented using efficient stream compaction. The performance of these algorithms are measured against CPU and thrust implementations and are documented in Analysis section. -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +### BUILD +* To setup cuda development environment use [this guide](https://github.com/nischalkn/Project0-CUDA-Getting-Started/blob/master/INSTRUCTION.md#part-1-setting-up-your-development-environment) +* Once the environment is set up, to Build and run the project follow [this](https://github.com/nischalkn/Project0-CUDA-Getting-Started/blob/master/INSTRUCTION.md#part-3-build--run) +### PARAMETERS +* `SIZE` - Size of array to be scanned, compacted and sorted +* `PROFILE` - `1` or `0`, Print execution times + +### PERFORMANCE ANALYSIS +The time taken to perform exclusive scans on arrays of different sizes ranging from `2^8` or `2^16` were recorded with a fixed block size of `128` (program crashed for array sizes beyond `2^16`). It was seen that the time taken by the CPU was very small to be recorded accurately for small array sizes but would increase exponentially as the array size increases. It is also seen that the naive scan outperforms work-efficient scan at larger array sizes because of the overhead of upsweep and downsweep steps which increase logarithmically with increase in array size. Most of the threads do not do any work in the extreme stages of the upsweep and downsweep. Many threads are launched but do not perform any work. This overhead causes a dramatic increase in execution time. It was also observed that the thrust implementation consistently was the slowest. +![](images/scan_array.png) +An other experiment was conducted with various block sizes for a constant array length of `2^16`. It is seen that the best performance is obtained with a block size of `128`. +![](images/scan_block.png) +CPU compactions for array sizes less than `2^14` take negligible amount of time. The work-efficient compactions is the slowest because of the same reason as mentioned above. A number of threads are launched which do not do any work. +![](images/compact_array.png) +Again running the compactions on different block sizes result in a best block size of `128`. +![](images/compact_block.png) +A radix sort was implemented using the work-efficient scan and its performance was compared with the `thrust::sort`. It was seen that for small array sizes, the radix sort outperforms thrust sort. But however as the array size increases, the work-efficient scan becomes slow as explained above and reduces the effciency of sorting algorithm. +![](images/sort.png) + +### FUTURE IMPROVEMENTS +* Work-efficient scan can be optimized to reduce the number of threads launched at each stage of upsweep and downsweep process. +* Early termination of threads would also improve the scan performance. + +### OUTPUT +``` + +**************** +** SCAN TESTS ** +**************** + [ 38 19 38 37 5 47 15 35 0 12 3 0 42 ... 35 0 ] +==== cpu scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] +==== cpu scan, non-power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604305 1604316 ] + passed +==== naive scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] + passed +==== naive scan, non-power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 0 0 ] + passed +==== work-efficient scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] + passed +==== work-efficient scan, non-power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604305 1604316 ] + passed +==== thrust scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] + passed +==== thrust scan, non-power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604305 1604316 ] + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 2 3 2 1 3 1 1 1 2 0 1 0 2 ... 1 0 ] +==== cpu compact without scan, power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] + passed +==== cpu compact without scan, non-power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 3 1 ] + passed +==== cpu compact with scan ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] + passed +==== work-efficient compact, power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] + passed +==== work-efficient compact, non-power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 3 1 ] + passed + +***************************** +***** RADIX SORT TESTS ****** +***************************** +==== thrust sort small array ==== + [ 0 1 2 3 4 5 6 7 8 9 ] +==== Radix Sort small array ==== + [ 0 1 2 3 4 5 6 7 8 9 ] + passed +==== thrust sort large array ==== + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 199 199 ] +==== Radix Sort large array ==== + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 199 199 ] + passed +``` + +### MODIFICATIONS +* 4 tests are added to the main function for sorting. 2 test to sort an array using `thrust::sort` and 2 tests to sort the array using `StreamCompaction::radixSort`. +* CMakeLists.txt of StreamCompaction was edited to include `radixSort.h` and `radixSort.cu`. diff --git a/images/compact_array.png b/images/compact_array.png new file mode 100644 index 0000000000000000000000000000000000000000..f5a6a4a223006ac7fe7440463f9efb5632518516 GIT binary patch literal 21934 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 literal 0 HcmV?d00001 diff --git a/images/compact_block.png b/images/compact_block.png new file mode 100644 index 0000000000000000000000000000000000000000..f5d8499e39e8bd87f1511e9da168c0d4bc5624b4 GIT binary patch literal 19027 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

4j}eOkyQO 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 literal 0 HcmV?d00001 diff --git a/images/sort.png b/images/sort.png new file mode 100644 index 0000000000000000000000000000000000000000..b4f819ec3a817e2b76b28a808492ea4a1d667031 GIT binary patch literal 22602 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 Date: Tue, 27 Sep 2016 18:31:47 -0400 Subject: [PATCH 13/13] Added data tables --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3660bd5..1de268a 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,34 @@ This project implements scan, stream compaction and sorting algorithms implement * `PROFILE` - `1` or `0`, Print execution times ### PERFORMANCE ANALYSIS -The time taken to perform exclusive scans on arrays of different sizes ranging from `2^8` or `2^16` were recorded with a fixed block size of `128` (program crashed for array sizes beyond `2^16`). It was seen that the time taken by the CPU was very small to be recorded accurately for small array sizes but would increase exponentially as the array size increases. It is also seen that the naive scan outperforms work-efficient scan at larger array sizes because of the overhead of upsweep and downsweep steps which increase logarithmically with increase in array size. Most of the threads do not do any work in the extreme stages of the upsweep and downsweep. Many threads are launched but do not perform any work. This overhead causes a dramatic increase in execution time. It was also observed that the thrust implementation consistently was the slowest. +The time taken to perform exclusive scans on arrays of different sizes ranging from `2^8` or `2^16` were recorded with a fixed block size of `128` (program crashed for array sizes beyond `2^16`). It was seen that the time taken by the CPU was very small to be recorded accurately for small array sizes but would increase exponentially as the array size increases. It is also seen that the naive scan outperforms work-efficient scan at larger array sizes because of the overhead of upsweep and downsweep steps which increase logarithmically with increase in array size. Most of the threads do not do any work in the extreme stages of the upsweep and downsweep. Many threads are launched but do not perform any work. This overhead causes a dramatic increase in execution time. It was also observed that the thrust implementation consistently was the slowest. The following are the time taken for different kind of scans in milliseconds. + +|Scan | 2^8 | 2^10 | 2^12 | 2^14 | 2^16 | +|:--------------------------------|-----|-------|-------|-------|-------| +|cpu scan, power-of-two | 0 | 0 | 0 | 0 | 0 | +|cpu scan, non-power-of-two | 0 |0 | 0 |0 | 0.5004| +|naive scan, power-of-two | 0.5357 |0.7699| 0.8006| 1.2946| 2.4013| +|naive scan, non-power-of-two | 0.6124| 0.6252| 0.6991| 1.0961| 2.4055| +|work-efficient scan, power-of-two| 0.0750| 0.1054| 0.2469| 0.8709| 3.5367| +|work-efficient scan, non-power-of-two| 0.0745| 0.1050| 0.2465| 0.8685| 3.5334| +|thrust scan, power-of-two | 6.7073| 6.8454| 7.4349| 10.7565| 28.5635| +|thrust scan, non-power-of-two | 0.7849| 0.8866| 1.6581| 4.8936| 16.7807| + ![](images/scan_array.png) + An other experiment was conducted with various block sizes for a constant array length of `2^16`. It is seen that the best performance is obtained with a block size of `128`. ![](images/scan_block.png) CPU compactions for array sizes less than `2^14` take negligible amount of time. The work-efficient compactions is the slowest because of the same reason as mentioned above. A number of threads are launched which do not do any work. + +|Compact | 2^8 | 2^10 | 2^12 | 2^14 | 2^16 | +|:----------------------------------------|-----|-------|-------|-------|-----| +|cpu compact without scan, power of two |0 |0 |0 |0.5029 |0.500066667| +|cpu compact without scan, non-power of two |0 |0 |0 |0.5019 |0.50035| +|cpu compact with scan |0 |0 |0 |0.4998 |0.999733333| +|work-efficient compact, power-of-two |0.089568 |0.123530667 |0.280234667| 0.946997333 |3.793076667| +|work-efficient compact, non-power-of-two |0.086741333 |0.118101333| 0.276192 |0.947157333 |3.79866| + + ![](images/compact_array.png) Again running the compactions on different block sizes result in a best block size of `128`. ![](images/compact_block.png) @@ -100,5 +123,7 @@ A radix sort was implemented using the work-efficient scan and its performance w ``` ### MODIFICATIONS +* radixSort was added to StreamCompaction module containing the sort function. +* sort function was also added to thrust.cu module to perform `thrust::sort`. * 4 tests are added to the main function for sorting. 2 test to sort an array using `thrust::sort` and 2 tests to sort the array using `StreamCompaction::radixSort`. * CMakeLists.txt of StreamCompaction was edited to include `radixSort.h` and `radixSort.cu`.