From bde7ee0ad4893916548668f3e94d365ea4c42af8 Mon Sep 17 00:00:00 2001 From: giaosame Date: Tue, 22 Sep 2020 15:53:43 -0400 Subject: [PATCH 1/7] add CPU Scan & Stream Compaction --- stream_compaction/cpu.cu | 45 +++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 719fa11..d2d167a 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -19,7 +19,14 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + + int prefixSum = 0; + for (int i = 0; i < n; i++) + { + odata[i] = prefixSum; + prefixSum += idata[i]; + } + timer().endCpuTimer(); } @@ -30,9 +37,16 @@ namespace StreamCompaction { */ int compactWithoutScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + + int cnt = 0; + for (int i = 0; i < n; i++) + { + if (idata[i] != 0) + odata[cnt++] = idata[i]; + } + timer().endCpuTimer(); - return -1; + return cnt; } /** @@ -42,9 +56,30 @@ namespace StreamCompaction { */ int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + + // Compute temporary array + int* tdata = new int[n]; + for (int i = 0; i < n; i++) + { + tdata[i] = idata[i] != 0 ? 1 : 0; + } + + // Run exclusive scan on temporary array + int* sdata = new int[n]; + scan(n, sdata, tdata); + + // Scatter + int idx = 0; + for (int i = 0; i < n; i++) + { + if (tdata[i]) + { + idx = sdata[i]; + odata[idx] = idata[i]; + } + } timer().endCpuTimer(); - return -1; + return idx + 1; } } } From ec448ed04a4d53f809fc9395e7e29d9b61c8498e Mon Sep 17 00:00:00 2001 From: giaosame Date: Tue, 22 Sep 2020 19:08:35 -0400 Subject: [PATCH 2/7] add Naive GPU Scan Algorithm --- src/main.cpp | 2 +- stream_compaction/cpu.cu | 10 +++++----- stream_compaction/naive.cu | 41 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 896ac2b..e49e1f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -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]; diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index d2d167a..780b873 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -57,17 +57,17 @@ namespace StreamCompaction { int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // Compute temporary array + // Compute temporary array and run exclusive scan on temporary array + int prefixSum = 0; int* tdata = new int[n]; + int* sdata = new int[n]; for (int i = 0; i < n; i++) { tdata[i] = idata[i] != 0 ? 1 : 0; + sdata[i] = prefixSum; + prefixSum += tdata[i]; } - // Run exclusive scan on temporary array - int* sdata = new int[n]; - scan(n, sdata, tdata); - // Scatter int idx = 0; for (int i = 0; i < n; i++) diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 4308876..782c6d3 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,6 +3,10 @@ #include "common.h" #include "naive.h" +// Block size used for CUDA kernel launch +#define blockSize 128 +dim3 threadsPerBlock(blockSize); + namespace StreamCompaction { namespace Naive { using StreamCompaction::Common::PerformanceTimer; @@ -11,14 +15,47 @@ namespace StreamCompaction { static PerformanceTimer timer; return timer; } - // TODO: __global__ + + __global__ void kernParallelScan(int n, int* odata, const int* idata, int d) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + return; + + int offset = 1 << (d - 1); + if (index >= offset) + { + odata[index] = idata[index - offset] + 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) { timer().startGpuTimer(); - // TODO + + dim3 blocksPerGrid((n + blockSize - 1) / blockSize); + int* dev_tempData; + int* dev_outputData; + cudaMalloc((void**)&dev_tempData, n * sizeof(int)); + cudaMalloc((void**)&dev_outputData, n * sizeof(int)); + cudaMemcpy(dev_outputData, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + int depth = ilog2ceil(n) + 1; + for (int d = 1; d <= depth; d++) + { + kernParallelScan<<>>(n, dev_tempData, dev_outputData, d); + std::swap(dev_tempData, dev_outputData); + } + + // Do a right shift when copying data from gpu to cpu, to convert inclusive scan to exclusive scan + odata[0] = 0; + cudaMemcpy(odata + 1, dev_outputData, (n - 1) * sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToHost); timer().endGpuTimer(); } } From 0d2c5b6ab13f4109a1b5398b3ae5edf3d016c80b Mon Sep 17 00:00:00 2001 From: giaosame Date: Wed, 23 Sep 2020 11:40:24 -0400 Subject: [PATCH 3/7] add Work-Efficient GPU Scan & Stream Compaction --- src/testing_helpers.hpp | 2 + stream_compaction/common.cu | 26 +++++-- stream_compaction/common.h | 4 +- stream_compaction/efficient.cu | 138 +++++++++++++++++++++++++++++++-- stream_compaction/naive.cu | 27 ++++--- 5 files changed, 172 insertions(+), 25 deletions(-) diff --git a/src/testing_helpers.hpp b/src/testing_helpers.hpp index 025e94a..cd35ba1 100644 --- a/src/testing_helpers.hpp +++ b/src/testing_helpers.hpp @@ -25,6 +25,8 @@ template void printCmpResult(int n, T *a, T *b) { printf(" %s \n", cmpArrays(n, a, b) ? "FAIL VALUE" : "passed"); + + printArray(n, b); } template diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 2ed6d63..eb627f5 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -14,7 +14,6 @@ void checkCUDAErrorFn(const char *msg, const char *file, int line) { exit(EXIT_FAILURE); } - namespace StreamCompaction { namespace Common { @@ -23,17 +22,30 @@ 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 index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + { + return; + } + + bools[index] = idata[index] != 0 ? 1 : 0; } /** * Performs scatter on an array. That is, for each element in idata, - * if bools[idx] == 1, it copies idata[idx] to odata[indices[idx]]. + * if bools[index] == 1, it copies idata[index] to odata[indices[index]]. */ - __global__ void kernScatter(int n, int *odata, - const int *idata, const int *bools, const int *indices) { - // TODO - } + __global__ void kernScatter(int n, int* odata, const int* idata, const int* bools, const int* indices) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + { + return; + } + if (bools[index]) + { + odata[indices[index]] = idata[index]; + } + } } } diff --git a/stream_compaction/common.h b/stream_compaction/common.h index d2c1fed..b5ffd51 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -2,7 +2,6 @@ #include #include - #include #include #include @@ -13,6 +12,9 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) +// Block size used for CUDA kernel launch +#define blockSize 128 + /** * Check for CUDA errors; print and exit if there was a problem. */ diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 2db346e..376f92b 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,6 +3,15 @@ #include "common.h" #include "efficient.h" +#include +using namespace std; + +int* dev_tempData; +int* dev_inputData; +int* dev_boolData; +int* dev_idxData; +int* dev_outputData; + namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; @@ -12,13 +21,87 @@ namespace StreamCompaction { return timer; } + // Initialize array in gpu + __global__ void kernInitializeArray(int n, int* a, int value) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) + { + a[index] = value; + } + } + + __global__ void kernUpSweep(int n, int* tdata, int d) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + { + return; + } + + int leftChildOffset = 1 << d; // 2^d + int rightChildOffset = leftChildOffset << 1; // 2^(d+1) + if (index % rightChildOffset == 0) + { + tdata[index + rightChildOffset - 1] += tdata[index + leftChildOffset - 1]; + } + } + + __global__ void kernDownSweep(int n, int* odata, int d) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + { + return; + } + + int leftChildOffset = 1 << d; // 2^d + int rightChildOffset = leftChildOffset << 1; // 2^(d+1) + if (index % rightChildOffset == 0) + { + // Save left child value + int preLeftChildVal = odata[index + leftChildOffset - 1]; + // Set the left child of the next round as the current node's value + odata[index + leftChildOffset - 1] = odata[index + rightChildOffset - 1]; + // Set the right child (the node itself) of the next round as the current node's value + previous left child value + odata[index + rightChildOffset - 1] += preLeftChildVal; + } + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ - void scan(int n, int *odata, const int *idata) { + void scan(int n, int *odata, const int *idata) + { + int depth = ilog2ceil(n); + int size = 1 << depth; // sizes of arrays will are rounded to the next power of two + dim3 threadsPerBlock(blockSize); + dim3 blocksPerGrid((size + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_tempData, size * sizeof(int)); + kernInitializeArray << > > (size, dev_tempData, 0); + cudaMemcpy(dev_tempData, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + // ------------------------------------- Performance Measurement ------------------------------------------- timer().startGpuTimer(); - // TODO + + for (int d = 0; d < depth; d++) + { + kernUpSweep<<>>(size, dev_tempData, d); + } + + // Set root to zero + cudaMemset(dev_tempData + size - 1, 0, sizeof(int)); + for (int d = depth - 1; d >= 0; d--) + { + kernDownSweep<<>>(size, dev_tempData, d); + } + timer().endGpuTimer(); + // -------------------------------------------------------------------------------------------------------- + + cudaMemcpy(odata, dev_tempData, n * sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToHost); + cudaFree(dev_tempData); } /** @@ -30,11 +113,56 @@ namespace StreamCompaction { * @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) { + int compact(int n, int *odata, const int *idata) + { + int depth = ilog2ceil(n); + int size = 1 << depth; // sizes of arrays will are rounded to the next power of two + dim3 threadsPerBlock(blockSize); + dim3 blocksPerGrid((size + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_inputData, size * sizeof(int)); + cudaMalloc((void**)&dev_boolData, size * sizeof(int)); + cudaMalloc((void**)&dev_idxData, size * sizeof(int)); + cudaMalloc((void**)&dev_outputData, n * sizeof(int)); + kernInitializeArray<<>>(size, dev_inputData, 0); + kernInitializeArray<<>>(size, dev_boolData, 0); + kernInitializeArray<<>>(size, dev_idxData, 0); + kernInitializeArray<<>>(n, dev_outputData, 0); + cudaMemcpy(dev_inputData, idata, n * sizeof(int), cudaMemcpyKind::cudaMemcpyHostToDevice); + + // ------------------------------------- Performance Measurement ------------------------------------------- timer().startGpuTimer(); - // TODO + // Step 1: Compute temporary array + Common::kernMapToBoolean<<>>(n, dev_boolData, dev_inputData); + + // Step 2: Run exclusive scan on temporary array + cudaMemcpy(dev_idxData, dev_boolData, n * sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToDevice); + for (int d = 0; d < depth; d++) + { + kernUpSweep<<>>(size, dev_idxData, d); + } + + cudaMemset(dev_idxData + size - 1, 0, sizeof(int)); + for (int d = depth - 1; d >= 0; d--) + { + kernDownSweep<<>>(size, dev_idxData, d); + } + + // Step 3: Scatter + Common::kernScatter<<>>(n, dev_outputData, dev_inputData, dev_boolData, dev_idxData); + timer().endGpuTimer(); - return -1; + // -------------------------------------------------------------------------------------------------------- + + int compactedSize = -1; + cudaMemcpy(&compactedSize, dev_idxData + size - 1, sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToHost); + cudaMemcpy(odata, dev_outputData, n * sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToHost); + + cudaFree(dev_inputData); + cudaFree(dev_boolData); + cudaFree(dev_idxData); + cudaFree(dev_outputData); + return compactedSize; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 782c6d3..d6a94ac 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,10 +3,6 @@ #include "common.h" #include "naive.h" -// Block size used for CUDA kernel launch -#define blockSize 128 -dim3 threadsPerBlock(blockSize); - namespace StreamCompaction { namespace Naive { using StreamCompaction::Common::PerformanceTimer; @@ -20,9 +16,11 @@ namespace StreamCompaction { { int index = (blockIdx.x * blockDim.x) + threadIdx.x; if (index >= n) + { return; + } - int offset = 1 << (d - 1); + int offset = 1 << d; if (index >= offset) { odata[index] = idata[index - offset] + idata[index]; @@ -36,9 +34,9 @@ namespace StreamCompaction { /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ - void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); - + void scan(int n, int *odata, const int *idata) + { + dim3 threadsPerBlock(blockSize); dim3 blocksPerGrid((n + blockSize - 1) / blockSize); int* dev_tempData; int* dev_outputData; @@ -46,17 +44,22 @@ namespace StreamCompaction { cudaMalloc((void**)&dev_outputData, n * sizeof(int)); cudaMemcpy(dev_outputData, idata, n * sizeof(int), cudaMemcpyHostToDevice); - int depth = ilog2ceil(n) + 1; - for (int d = 1; d <= depth; d++) + // ------------------------------------- Performance Measurement ------------------------------------------ + timer().startGpuTimer(); + int depth = ilog2ceil(n); + for (int d = 0; d < depth; d++) { kernParallelScan<<>>(n, dev_tempData, dev_outputData, d); std::swap(dev_tempData, dev_outputData); } - + timer().endGpuTimer(); + // -------------------------------------------------------------------------------------------------------- + // Do a right shift when copying data from gpu to cpu, to convert inclusive scan to exclusive scan odata[0] = 0; cudaMemcpy(odata + 1, dev_outputData, (n - 1) * sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToHost); - timer().endGpuTimer(); + cudaFree(dev_tempData); + cudaFree(dev_outputData); } } } From 0d2b2a3a60fd8929ad71486885275ff22e371ac7 Mon Sep 17 00:00:00 2001 From: giaosame Date: Wed, 23 Sep 2020 12:07:50 -0400 Subject: [PATCH 4/7] add Thrust scan implementation --- stream_compaction/thrust.cu | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 1def45e..0c123ee 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -17,12 +17,17 @@ namespace StreamCompaction { /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ - void scan(int n, int *odata, const int *idata) { + void scan(int n, int *odata, const int *idata) + { + thrust::host_vector host_idata(idata, idata + n); + thrust::device_vector dev_idata = host_idata; // copy host_vector to device_vector + thrust::device_vector dev_odata(n); + 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()); + thrust::exclusive_scan(dev_idata.begin(), dev_idata.end(), dev_odata.begin()); timer().endGpuTimer(); + + thrust::copy(dev_odata.begin(), dev_odata.end(), odata); } } } From b4d26507f1793a822e30f53f8dda1bd0c8775b8c Mon Sep 17 00:00:00 2001 From: giaosame Date: Wed, 23 Sep 2020 19:29:38 -0400 Subject: [PATCH 5/7] add radix sort --- src/main.cpp | 51 +++++++---- src/testing_helpers.hpp | 2 - stream_compaction/CMakeLists.txt | 2 + stream_compaction/common.cu | 10 +++ stream_compaction/common.h | 2 + stream_compaction/cpu.cu | 9 ++ stream_compaction/cpu.h | 2 + stream_compaction/efficient.cu | 32 +++---- stream_compaction/efficient.h | 4 + stream_compaction/radix.cu | 150 +++++++++++++++++++++++++++++++ stream_compaction/radix.h | 8 ++ 11 files changed, 234 insertions(+), 38 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 e49e1f8..5eea139 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,10 +10,11 @@ #include #include #include +#include #include #include "testing_helpers.hpp" -const int SIZE = 1 << 3; // feel free to change the size of array +const int SIZE = 1 << 4; // 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]; @@ -81,19 +82,19 @@ int main(int argc, char* argv[]) { //printArray(NPOT, c, true); printCmpResult(NPOT, b, c); - zeroArray(SIZE, c); - printDesc("thrust scan, power-of-two"); - StreamCompaction::Thrust::scan(SIZE, c, a); - printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); + //zeroArray(SIZE, c); + //printDesc("thrust scan, power-of-two"); + //StreamCompaction::Thrust::scan(SIZE, c, a); + //printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + ////printArray(SIZE, c, true); + //printCmpResult(SIZE, b, c); - zeroArray(SIZE, c); - printDesc("thrust scan, non-power-of-two"); - StreamCompaction::Thrust::scan(NPOT, c, a); - printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(NPOT, c, true); - printCmpResult(NPOT, b, c); + //zeroArray(SIZE, c); + //printDesc("thrust scan, non-power-of-two"); + //StreamCompaction::Thrust::scan(NPOT, c, a); + //printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + ////printArray(NPOT, c, true); + //printCmpResult(NPOT, b, c); printf("\n"); printf("*****************************\n"); @@ -137,16 +138,36 @@ int main(int argc, char* argv[]) { printDesc("work-efficient compact, power-of-two"); count = StreamCompaction::Efficient::compact(SIZE, c, a); printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //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); printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); + printf("\n"); + printf("**********************\n"); + printf("** RADIX SORT TESTS **\n"); + printf("**********************\n"); + + genArray(SIZE, a, 100); // Leave a 0 at the end to test that edge case + printArray(SIZE, a); + + zeroArray(SIZE, c); + printDesc("radix sort, power-of-two"); + StreamCompaction::CPU::sort(SIZE, b, a); + StreamCompaction::Radix::sort(SIZE, c, a); + printArray(SIZE, c); + printCmpResult(SIZE, b, c); + + zeroArray(SIZE, c); + printDesc("radix sort, non-power-of-two"); + StreamCompaction::CPU::sort(NPOT, b, a); + StreamCompaction::Radix::sort(NPOT, c, a); + printArray(NPOT, c); + printCmpResult(NPOT, b, c); + system("pause"); // stop Win32 console from closing on exit delete[] a; delete[] b; diff --git a/src/testing_helpers.hpp b/src/testing_helpers.hpp index cd35ba1..025e94a 100644 --- a/src/testing_helpers.hpp +++ b/src/testing_helpers.hpp @@ -25,8 +25,6 @@ template void printCmpResult(int n, T *a, T *b) { printf(" %s \n", cmpArrays(n, a, b) ? "FAIL VALUE" : "passed"); - - printArray(n, b); } template diff --git a/stream_compaction/CMakeLists.txt b/stream_compaction/CMakeLists.txt index 567795b..eb71175 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/common.cu b/stream_compaction/common.cu index eb627f5..6483339 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -17,6 +17,16 @@ void checkCUDAErrorFn(const char *msg, const char *file, int line) { namespace StreamCompaction { namespace Common { + // Initialize array in gpu + __global__ void kernInitializeArray(int n, int* a, int value) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) + { + a[index] = value; + } + } + /** * Maps an array to an array of 0s and 1s for stream compaction. Elements * which map to 0 will be removed, and elements which map to 1 will be kept. diff --git a/stream_compaction/common.h b/stream_compaction/common.h index b5ffd51..4ac9219 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -34,6 +34,8 @@ inline int ilog2ceil(int x) { namespace StreamCompaction { namespace Common { + __global__ void kernInitializeArray(int n, int* a, int value); + __global__ void kernMapToBoolean(int n, int *bools, const int *idata); __global__ void kernScatter(int n, int *odata, diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 780b873..ba1e1db 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -81,5 +81,14 @@ namespace StreamCompaction { timer().endCpuTimer(); return idx + 1; } + + void sort(int n, int* odata, const int* idata) + { + for (int i = 0; i < n; i++) + { + odata[i] = idata[i]; + } + std::sort(odata, odata + n); + } } } diff --git a/stream_compaction/cpu.h b/stream_compaction/cpu.h index 873c047..8a6efa9 100644 --- a/stream_compaction/cpu.h +++ b/stream_compaction/cpu.h @@ -8,6 +8,8 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata); + void sort(int n, int* odata, const int* idata); + int compactWithoutScan(int n, int *odata, const int *idata); int compactWithScan(int n, int *odata, const int *idata); diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 376f92b..ed783ab 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -6,14 +6,14 @@ #include using namespace std; -int* dev_tempData; -int* dev_inputData; -int* dev_boolData; -int* dev_idxData; -int* dev_outputData; - namespace StreamCompaction { namespace Efficient { + int* dev_tempData; + int* dev_inputData; + int* dev_boolData; + int* dev_idxData; + int* dev_outputData; + using StreamCompaction::Common::PerformanceTimer; PerformanceTimer& timer() { @@ -21,16 +21,6 @@ namespace StreamCompaction { return timer; } - // Initialize array in gpu - __global__ void kernInitializeArray(int n, int* a, int value) - { - int index = (blockIdx.x * blockDim.x) + threadIdx.x; - if (index < n) - { - a[index] = value; - } - } - __global__ void kernUpSweep(int n, int* tdata, int d) { int index = (blockIdx.x * blockDim.x) + threadIdx.x; @@ -79,7 +69,7 @@ namespace StreamCompaction { dim3 blocksPerGrid((size + blockSize - 1) / blockSize); cudaMalloc((void**)&dev_tempData, size * sizeof(int)); - kernInitializeArray << > > (size, dev_tempData, 0); + Common::kernInitializeArray<<>>(size, dev_tempData, 0); cudaMemcpy(dev_tempData, idata, n * sizeof(int), cudaMemcpyHostToDevice); // ------------------------------------- Performance Measurement ------------------------------------------- @@ -124,10 +114,10 @@ namespace StreamCompaction { cudaMalloc((void**)&dev_boolData, size * sizeof(int)); cudaMalloc((void**)&dev_idxData, size * sizeof(int)); cudaMalloc((void**)&dev_outputData, n * sizeof(int)); - kernInitializeArray<<>>(size, dev_inputData, 0); - kernInitializeArray<<>>(size, dev_boolData, 0); - kernInitializeArray<<>>(size, dev_idxData, 0); - kernInitializeArray<<>>(n, dev_outputData, 0); + Common::kernInitializeArray<<>>(size, dev_inputData, 0); + Common::kernInitializeArray<<>>(size, dev_boolData, 0); + Common::kernInitializeArray<<>>(size, dev_idxData, 0); + Common::kernInitializeArray<<>>(n, dev_outputData, 0); cudaMemcpy(dev_inputData, idata, n * sizeof(int), cudaMemcpyKind::cudaMemcpyHostToDevice); // ------------------------------------- Performance Measurement ------------------------------------------- diff --git a/stream_compaction/efficient.h b/stream_compaction/efficient.h index 803cb4f..77891fb 100644 --- a/stream_compaction/efficient.h +++ b/stream_compaction/efficient.h @@ -6,6 +6,10 @@ namespace StreamCompaction { namespace Efficient { StreamCompaction::Common::PerformanceTimer& timer(); + __global__ void kernUpSweep(int n, int* tdata, int d); + + __global__ void kernDownSweep(int n, int* odata, int d); + void scan(int n, int *odata, const int *idata); int compact(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..931e0ec --- /dev/null +++ b/stream_compaction/radix.cu @@ -0,0 +1,150 @@ +#include +#include +#include "common.h" +#include "radix.h" +#include "efficient.h" + +#include +using namespace std; + + + +namespace StreamCompaction { + namespace Radix { + int* dev_tempData; + int* dev_inputData; + int* dev_boolData; + int* dev_notBoolData; + int* dev_scanData; + int* dev_outputData; + + // Returns the position of the most significant bit + int getMSB(int x) + { + int bit = 1 << 31; + for (int i = 31; i >= 0; i--, bit >>= 1) + { + if (x & bit) + return i + 1; + } + return 0; + } + + // Returns the maximum of the array + int getMax(int n, const int* a) + { + int maximum = a[0]; + for (int i = 1; i < n; i++) + { + maximum = std::max(maximum, a[i]); + } + return maximum; + } + + // Maps an array to 2 arrays only contains 1s and 0s. + // _bools_ is just the logic NOT of _notBools_ + __global__ void kernMapTo2Bools(int n, int bit, int* bools, int* notBools, const int* idata) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + { + return; + } + + bool b = idata[index] & bit; + bools[index] = b; + notBools[index] = !b; + } + + // Computes the temp array _temps_ which stores address for writing true keys + __global__ void kernComputeAddressOfTrueKeys(int n, int* temps, const int* notBools, const int* scanData) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + { + return; + } + + int totalFalses = notBools[n - 1] + scanData[n - 1]; + temps[index] = index - scanData[index] + totalFalses; + } + + // Scatters based on address _temps_ + __global__ void kernRadixScatter(int n, int* odata, const int* temps, const int* bools, const int* scanData, const int* idata) + { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) + { + return; + } + + int newIdx = bools[index] ? temps[index] : scanData[index]; + odata[newIdx] = idata[index]; + } + + /** + * Performs radix sort on idata, storing the result into odata. + * + * @param n The number of elements in idata. + * @param odata The array into which to store elements. + * @param idata The array of elements to sort. + */ + void sort(int n, int* odata, const int* idata) + { + int depth = ilog2ceil(n); + int size = 1 << depth; // sizes of arrays will are rounded to the next power of two + int maximum = getMax(n, idata); + int bits = getMSB(maximum); + + dim3 threadsPerBlock(blockSize); + dim3 blocksPerGrid((n + blockSize - 1) / blockSize); + dim3 scanBlocksPerGrid((n + blockSize - 1) / blockSize); + + cudaMalloc((void**)&dev_inputData, n * sizeof(int)); + cudaMalloc((void**)&dev_boolData, n * sizeof(int)); + cudaMalloc((void**)&dev_notBoolData, n * sizeof(int)); + cudaMalloc((void**)&dev_scanData, size * sizeof(int)); + cudaMalloc((void**)&dev_tempData, n * sizeof(int)); + cudaMalloc((void**)&dev_outputData, n * sizeof(int)); + Common::kernInitializeArray<<>>(size, dev_scanData, 0); + cudaMemcpy(dev_inputData, idata, n * sizeof(int), cudaMemcpyKind::cudaMemcpyHostToDevice); + + // Do radix sort for _bits_ times + for (int i = 0, bit = 1; i <= bits; i++, bit <<= 1) + { + // Step 1: Compute the bool array and notBool array + kernMapTo2Bools<<>>(n, bit, dev_boolData, dev_notBoolData, dev_inputData); + + // Step 2: Exclusive scan array + cudaMemcpy(dev_scanData, dev_notBoolData, n * sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToDevice); + for (int d = 0; d < depth; d++) + { + Efficient::kernUpSweep<<>>(size, dev_scanData, d); + } + + cudaMemset(dev_scanData + size - 1, 0, sizeof(int)); + for (int d = depth - 1; d >= 0; d--) + { + Efficient::kernDownSweep<<>>(size, dev_scanData, d); + } + + // Step 3: Compute temp array _dev_tempData_ + kernComputeAddressOfTrueKeys<<>>(n, dev_tempData, dev_notBoolData, dev_scanData); + + // Step 4: Scatter + kernRadixScatter<<>>(n, dev_outputData, dev_tempData, dev_boolData, dev_scanData, dev_inputData); + + // Swap for next round of radix sort + std::swap(dev_outputData, dev_inputData); + } + + cudaMemcpy(odata, dev_inputData, n * sizeof(int), cudaMemcpyKind::cudaMemcpyDeviceToHost); + cudaFree(dev_inputData); + cudaFree(dev_boolData); + cudaFree(dev_notBoolData); + cudaFree(dev_scanData); + cudaFree(dev_tempData); + cudaFree(dev_outputData); + } + } +} diff --git a/stream_compaction/radix.h b/stream_compaction/radix.h new file mode 100644 index 0000000..e97f7a2 --- /dev/null +++ b/stream_compaction/radix.h @@ -0,0 +1,8 @@ +#pragma once +#include "common.h" + +namespace StreamCompaction { + namespace Radix { + void sort(int n, int* odata, const int* idata); + } +} From 580e6486f6d9b6702bb6ad52c0c7732db02c48d7 Mon Sep 17 00:00:00 2001 From: giaosame Date: Wed, 23 Sep 2020 23:30:48 -0400 Subject: [PATCH 6/7] add perf analysis images --- img/different_arraysize_compact_perf.png | Bin 0 -> 12537 bytes img/different_arraysize_scan_perf.png | Bin 0 -> 54178 bytes img/different_blocksize_perf.png | Bin 0 -> 45736 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 img/different_arraysize_compact_perf.png create mode 100644 img/different_arraysize_scan_perf.png create mode 100644 img/different_blocksize_perf.png diff --git a/img/different_arraysize_compact_perf.png b/img/different_arraysize_compact_perf.png new file mode 100644 index 0000000000000000000000000000000000000000..98c7f6da560d9876633260e0f7d565bb27e11b61 GIT binary patch literal 12537 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�^||XOu>I+(gAze`N0sHXJTk>o^j~0E~2i^Clt0< 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 bh+FCU>@!C3voZR6;ODu5s(g{GQQ-dva;!j@ literal 0 HcmV?d00001 diff --git a/img/different_blocksize_perf.png b/img/different_blocksize_perf.png new file mode 100644 index 0000000000000000000000000000000000000000..332234ae31878e335fb633de3dc0903210cbb7d2 GIT binary patch literal 45736 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%0DG1= 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 NYdVJ7A1_;m{0|(~12q5u literal 0 HcmV?d00001 From b25a3f742903098e7c9b0b92858078fd4bd69726 Mon Sep 17 00:00:00 2001 From: giaosame Date: Wed, 23 Sep 2020 23:56:53 -0400 Subject: [PATCH 7/7] add perf analysis --- README.md | 145 +++++++++++++++++++++++++++++++-- src/main.cpp | 32 ++++---- stream_compaction/common.h | 2 +- stream_compaction/cpu.cu | 4 +- stream_compaction/efficient.cu | 3 - stream_compaction/radix.cu | 9 +- 6 files changed, 160 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 0e38ddb..ccff2d0 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,145 @@ 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) +* Qiaosen Chen + * [LinkedIn](https://www.linkedin.com/in/qiaosen-chen-725699141/), etc. +* Tested on: Windows 10, i5-9400 @ 2.90GHz 16GB, GeForce RTX 2060 6GB (personal computer). -### (TODO: Your README) +## Implemented Features -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +This project includes several scan and stream compaction algorithms, and most of them are implemented with parallelism in CUDA. With the serial version of algorithms run on CPU as comparison, we can have a better view on the performance of parallel algorithm run on GPU. + +- CPU Scan & Stream Compaction + +- Naive GPU Scan Algorithm + +- Work-Efficient GPU Scan & Stream Compaction + +- Thrust Scan + +- Radix Sort (Extra Credit) + + Please see the details in the last part of the report. + +## Performance Analysis + +### Performances under Different Block Size + +![Different Block Size](https://github.com/giaosame/Project2-Stream-Compaction/blob/master/img/different_blocksize_perf.png) + +In general, when ```blockSize = 128```, the parallel version of scan algorithms and compact algorithms could achieve a relative optimized performance. + +### Scan Algorithms Performances + +![Scan Algorithms Performances](https://github.com/giaosame/Project2-Stream-Compaction/blob/master/img/different_arraysize_scan_perf.png) + +This performance analysis tested when ```blockSize = 256```, and all algorithms taken into accounts are given the input with an power-of-two array size. + +When the input array size is small (```SIZE < 2^15```), the difference of performances is small and not obvious for all the scan algorithms, but the serial version of algorithm run on CPU performs better than those parallel version of algorithms run on GPU. + +When the input array size is large enough (```SIZE > 2^17```), the difference of performances becomes larger and larger, and apparently at this time, ```Thrust::Scan``` performs best among all algorithms. As expected, the ```CPU::Scan``` algorithm performs much worse than ```Thrust::Scan```, it is even worse than ```Naive::Scan```algorithm. However, it is quite weird that, the naive scan algorithm always runs faster than the work-efficient scan algorithm, because the so-called "efficient" work-efficient scan algorithm can still get optimized. + +### Compact Algorithm Performances + +![Compact Algorithm Performances](https://github.com/giaosame/Project2-Stream-Compaction/blob/master/img/different_arraysize_compact_perf.png) + +When the input array size is large enough (```SIZE > 2^17```), the difference of performances become more and more obvious, and both of the compact algorithms perform much worse as the input size increases, as expected. The serial version of compact algorithm run on CPU performs better than the parallel version run in GPU when the input size is small, however, when the input size is quite huge, such as ```SIZE = 2^20```, there is no doubt that the work-efficient compact algorithm run in parallel on GPU perform much better than the CPU version. + +### Output + +This output tests were based on an array ```SIZE = 1024``` and ```blockSize = 128```: + +```bash +**************** +** SCAN TESTS ** +**************** + [ 46 46 37 6 29 0 28 22 25 23 3 11 29 ... 20 0 ] +==== cpu scan, power-of-two ==== + elapsed time: 0.0013ms (std::chrono Measured) + [ 0 46 92 129 135 164 164 192 214 239 262 265 276 ... 24775 24795 ] +==== cpu scan, non-power-of-two ==== + elapsed time: 0.0007ms (std::chrono Measured) + [ 0 46 92 129 135 164 164 192 214 239 262 265 276 ... 24725 24751 ] + passed +==== naive scan, power-of-two ==== + elapsed time: 0.02192ms (CUDA Measured) + passed +==== naive scan, non-power-of-two ==== + elapsed time: 0.021952ms (CUDA Measured) + passed +==== work-efficient scan, power-of-two ==== + elapsed time: 0.046304ms (CUDA Measured) + passed +==== work-efficient scan, non-power-of-two ==== + elapsed time: 0.045856ms (CUDA Measured) + passed +==== thrust scan, power-of-two ==== + elapsed time: 0.038912ms (CUDA Measured) + passed +==== thrust scan, non-power-of-two ==== + elapsed time: 0.038176ms (CUDA Measured) + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 2 0 1 2 3 2 0 0 1 1 3 1 1 ... 2 0 ] +==== cpu compact without scan, power-of-two ==== + elapsed time: 0.0026ms (std::chrono Measured) + [ 2 1 2 3 2 1 1 3 1 1 1 2 1 ... 2 2 ] + passed +==== cpu compact without scan, non-power-of-two ==== + elapsed time: 0.0024ms (std::chrono Measured) + [ 2 1 2 3 2 1 1 3 1 1 1 2 1 ... 1 2 ] + passed +==== cpu compact with scan ==== + elapsed time: 0.0054ms (std::chrono Measured) + [ 2 1 2 3 2 1 1 3 1 1 1 2 1 ... 2 2 ] + passed +==== work-efficient compact, power-of-two ==== + elapsed time: 0.062976ms (CUDA Measured) + passed +==== work-efficient compact, non-power-of-two ==== + elapsed time: 0.052992ms (CUDA Measured) + passed +``` + +## Extra Credit + +- **Radix sort** + + I defined the several function used by Radix Sort in [radix.h](https://github.com/giaosame/Project2-Stream-Compaction/blob/master/stream_compaction/radix.h) and implemented it in [radix.cu](https://github.com/giaosame/Project2-Stream-Compaction/blob/master/stream_compaction/radix.cu) under the directory [/stream_compaction](https://github.com/giaosame/Project2-Stream-Compaction/tree/master/stream_compaction). In [main.cpp](https://github.com/giaosame/Project2-Stream-Compaction/blob/master/src/main.cpp), I called this function ```StreamCompaction::Radix::sort``` in the last of the ```main``` function. + + ```c++ + zeroArray(SIZE, c); + printDesc("radix sort, power-of-two"); + StreamCompaction::CPU::sort(SIZE, b, a); + StreamCompaction::Radix::sort(SIZE, c, a); + printCmpResult(SIZE, b, c); + + zeroArray(SIZE, c); + printDesc("radix sort, non-power-of-two"); + StreamCompaction::CPU::sort(NPOT, b, a); + StreamCompaction::Radix::sort(NPOT, c, a); + printCmpResult(NPOT, b, c); + ``` + + Examples of output of Radix Sort: + + ```bash + ********************** + ** RADIX SORT TESTS ** + ********************** + [ 10 31 19 93 79 96 60 46 46 85 44 56 52 53 85 39 ] + ==== radix sort, power-of-two ==== + [ 10 19 31 39 44 46 46 52 53 56 60 79 85 85 93 96 ] + passed + ==== radix sort, non-power-of-two ==== + [ 10 19 31 44 46 46 52 56 60 79 85 93 96 ] + passed + ``` + + + + diff --git a/src/main.cpp b/src/main.cpp index 5eea139..33c28c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 4; // feel free to change the size of array +const int SIZE = 1 << 5; // 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]; @@ -82,19 +82,19 @@ int main(int argc, char* argv[]) { //printArray(NPOT, c, true); printCmpResult(NPOT, b, c); - //zeroArray(SIZE, c); - //printDesc("thrust scan, power-of-two"); - //StreamCompaction::Thrust::scan(SIZE, c, a); - //printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - ////printArray(SIZE, c, true); - //printCmpResult(SIZE, b, c); + zeroArray(SIZE, c); + printDesc("thrust scan, power-of-two"); + StreamCompaction::Thrust::scan(SIZE, c, a); + printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + //printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); - //zeroArray(SIZE, c); - //printDesc("thrust scan, non-power-of-two"); - //StreamCompaction::Thrust::scan(NPOT, c, a); - //printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); - ////printArray(NPOT, c, true); - //printCmpResult(NPOT, b, c); + zeroArray(SIZE, c); + printDesc("thrust scan, non-power-of-two"); + StreamCompaction::Thrust::scan(NPOT, c, a); + printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + //printArray(NPOT, c, true); + printCmpResult(NPOT, b, c); printf("\n"); printf("*****************************\n"); @@ -152,20 +152,20 @@ int main(int argc, char* argv[]) { printf("**********************\n"); genArray(SIZE, a, 100); // Leave a 0 at the end to test that edge case - printArray(SIZE, a); + printArray(SIZE, a, true); zeroArray(SIZE, c); printDesc("radix sort, power-of-two"); StreamCompaction::CPU::sort(SIZE, b, a); StreamCompaction::Radix::sort(SIZE, c, a); - printArray(SIZE, c); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); printDesc("radix sort, non-power-of-two"); StreamCompaction::CPU::sort(NPOT, b, a); StreamCompaction::Radix::sort(NPOT, c, a); - printArray(NPOT, c); + printArray(NPOT, c, true); printCmpResult(NPOT, b, c); system("pause"); // stop Win32 console from closing on exit diff --git a/stream_compaction/common.h b/stream_compaction/common.h index 4ac9219..734bdb9 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -13,7 +13,7 @@ #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) // Block size used for CUDA kernel launch -#define blockSize 128 +#define blockSize 256 /** * Check for CUDA errors; print and exit if there was a problem. diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index ba1e1db..d6ba6c9 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -55,12 +55,12 @@ namespace StreamCompaction { * @returns the number of elements remaining after compaction. */ int compactWithScan(int n, int *odata, const int *idata) { - timer().startCpuTimer(); - // Compute temporary array and run exclusive scan on temporary array int prefixSum = 0; int* tdata = new int[n]; int* sdata = new int[n]; + + timer().startCpuTimer(); for (int i = 0; i < n; i++) { tdata[i] = idata[i] != 0 ? 1 : 0; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index ed783ab..4c21385 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,9 +3,6 @@ #include "common.h" #include "efficient.h" -#include -using namespace std; - namespace StreamCompaction { namespace Efficient { int* dev_tempData; diff --git a/stream_compaction/radix.cu b/stream_compaction/radix.cu index 931e0ec..34653d8 100644 --- a/stream_compaction/radix.cu +++ b/stream_compaction/radix.cu @@ -4,11 +4,6 @@ #include "radix.h" #include "efficient.h" -#include -using namespace std; - - - namespace StreamCompaction { namespace Radix { int* dev_tempData; @@ -94,7 +89,7 @@ namespace StreamCompaction { int depth = ilog2ceil(n); int size = 1 << depth; // sizes of arrays will are rounded to the next power of two int maximum = getMax(n, idata); - int bits = getMSB(maximum); + int highestBit = getMSB(maximum); dim3 threadsPerBlock(blockSize); dim3 blocksPerGrid((n + blockSize - 1) / blockSize); @@ -110,7 +105,7 @@ namespace StreamCompaction { cudaMemcpy(dev_inputData, idata, n * sizeof(int), cudaMemcpyKind::cudaMemcpyHostToDevice); // Do radix sort for _bits_ times - for (int i = 0, bit = 1; i <= bits; i++, bit <<= 1) + for (int i = 0, bit = 1; i < highestBit; i++, bit <<= 1) { // Step 1: Compute the bool array and notBool array kernMapTo2Bools<<>>(n, bit, dev_boolData, dev_notBoolData, dev_inputData);