From 4e3655aaa080e760d48e513936c2601004fd5b83 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 18 Sep 2016 09:53:05 -0400 Subject: [PATCH 1/3] CPU scan implementation --- src/main.cpp | 2 +- stream_compaction/common.cu | 4 ++-- stream_compaction/cpu.cu | 40 +++++++++++++++++++++++++++++++------ stream_compaction/naive.cu | 1 + 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 675da35..dc0b849 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ #include "testing_helpers.hpp" int main(int argc, char* argv[]) { - const int SIZE = 1 << 8; + const int SIZE = 1 << 3; const int NPOT = SIZE - 3; int a[SIZE], b[SIZE], c[SIZE]; diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index fe872d4..06966d4 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -23,7 +23,7 @@ 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 + } /** @@ -32,7 +32,7 @@ __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 + } } diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index e600c29..891391f 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -1,4 +1,5 @@ #include +#include #include "cpu.h" namespace StreamCompaction { @@ -8,8 +9,11 @@ namespace CPU { * CPU scan (prefix sum). */ void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); + int sum = 0; + for (int i = 0; i < n; i++) { + odata[i] = sum; + sum += idata[i]; + } } /** @@ -18,8 +22,14 @@ 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; + int j = 0; + for (int i = 0; i < n; i++) { + if (idata[i] != 0) { + odata[j] = idata[i]; + j++; + } + } + return j; } /** @@ -28,8 +38,26 @@ 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; + // Map elements to boolean array + std::vector bools(n); + for (int i = 0; i < n; i++) { + bools[i] = (idata[i] != 0); + } + + // Perform exclusive scan on temp array + std::vector indices(n); + scan(n, indices.data(), bools.data()); + + // Scatter + int elementCount; + for (int i = 0; i < n; i++) { + if (bools[i] == 1) { + odata[indices[i]] = idata[i]; + elementCount = indices[i] + 1; + } + } + + return elementCount; } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 3d86b60..dce79ea 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -14,6 +14,7 @@ namespace Naive { void scan(int n, int *odata, const int *idata) { // TODO printf("TODO\n"); + } } From dec83bf088db26a8936a2420042598d21429e184 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 19 Sep 2016 20:08:44 -0400 Subject: [PATCH 2/3] Implemented parts 2 through 4 --- stream_compaction/common.cu | 12 ++- stream_compaction/cpu.cu | 2 +- stream_compaction/efficient.cu | 141 +++++++++++++++++++++++++++++++-- stream_compaction/naive.cu | 45 ++++++++++- stream_compaction/thrust.cu | 9 ++- 5 files changed, 195 insertions(+), 14 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 06966d4..df241ba 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) { - + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + bools[index] = (idata[index] != 0); + } } /** @@ -32,7 +35,12 @@ __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) { - + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + if (bools[index]) { + odata[indices[index]] = idata[index]; + } + } } } diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 891391f..e6edf3c 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -51,7 +51,7 @@ int compactWithScan(int n, int *odata, const int *idata) { // Scatter int elementCount; for (int i = 0; i < n; i++) { - if (bools[i] == 1) { + if (bools[i]) { odata[indices[i]] = idata[i]; elementCount = indices[i] + 1; } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index b2f739b..8bf404a 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -6,14 +6,79 @@ namespace StreamCompaction { namespace Efficient { -// TODO: __global__ +__global__ void upSweep(int n, int d, int *data) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + int prevOffset = d == 0 ? 1 : 2 << (d - 1); + int offset = prevOffset * 2; + + if (index < n && index % offset == 0) { + data[index + offset - 1] += data[index + prevOffset - 1]; + } +} + +__global__ void downSweep(int n, int d, int *data) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + int prevOffset = d == 0 ? 1 : 2 << (d - 1); + int offset = prevOffset * 2; + + if (index < n && index % offset == 0) { + int t = data[index + prevOffset - 1]; + data[index + prevOffset - 1] = data[index + offset - 1]; + data[index + offset - 1] += t; + } +} /** * 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"); + int blockSize = 128; + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + int nearestPow = 2 << (ilog2ceil(n) - 1); //assume n > 0 + + int* dev_data; + cudaMalloc((void**)&dev_data, nearestPow * sizeof(int)); + checkCUDAError("cudaMalloc dev_data failed!"); + cudaMemcpy(dev_data, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + + // Up-sweep + int numLevels = ilog2ceil(nearestPow) - 1; + for (int d = 0; d <= numLevels; d++) { + upSweep << > >(nearestPow, d, dev_data); + } + + cudaMemcpy(odata, dev_data, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); + //printf("AFTER UPSWEEP: [\n"); + //for (int i = 0; i < nearestPow; i++) { + // printf("%d\n", odata[i]); + //} + //printf("]\n"); + odata[nearestPow - 1] = 0; + cudaMemcpy(dev_data, odata, sizeof(int) * nearestPow, cudaMemcpyHostToDevice); + + //Down-sweep + for (int d = numLevels; d >= 0; d--) { + //printf("LEVEL: %d\n", d); + //cudaMemcpy(odata, dev_data, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); + //printf("[ "); + //for (int i = 0; i < nearestPow; i++) { + // printf("%d ", odata[i]); + //} + //printf("]\n"); + downSweep << > >(nearestPow, d, dev_data); + } + + cudaMemcpy(odata, dev_data, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); + //printf("AFTER DOWNSWEEP: [\n"); + //for (int i = 0; i < nearestPow; i++) { + // printf("%d\n", odata[i]); + //} + //printf("]\n"); + + cudaFree(dev_data); } /** @@ -26,8 +91,74 @@ void scan(int n, int *odata, const int *idata) { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { - // TODO - return -1; + int blockSize = 128; + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + int nearestPow = 2 << (ilog2ceil(n) - 1); //assume n > 0 + + int* dev_idata; + int* dev_odata; + int* dev_bools; + int* dev_indices; + int* bools; + int* indices; + + cudaMalloc((void**)&dev_idata, nearestPow * sizeof(int)); + checkCUDAError("cudaMalloc dev_idata failed!"); + cudaMemset(dev_idata, 0, nearestPow); + cudaMemcpy(dev_idata, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + + cudaMalloc((void**)&dev_odata, nearestPow * sizeof(int)); + checkCUDAError("cudaMalloc dev_odata failed!"); + + cudaMalloc((void**)&dev_bools, nearestPow * sizeof(int)); + checkCUDAError("cudaMalloc dev_bools failed!"); + bools = (int*)malloc(nearestPow * sizeof(int)); + + cudaMalloc((void**)&dev_indices, nearestPow * sizeof(int)); + checkCUDAError("cudaMalloc dev_indices failed!"); + indices = (int*)malloc(nearestPow * sizeof(int)); + + StreamCompaction::Common::kernMapToBoolean << > >(nearestPow, dev_bools, dev_idata); + cudaMemcpy(bools, dev_bools, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); + //printf("BOOLS: [\n"); + //for (int i = 0; i < nearestPow; i++) { + // printf("%d\n", bools[i]); + //} + //printf("]\n"); + + scan(n, indices, bools); + //printf("INDICES: [\n"); + //for (int i = 0; i < nearestPow; i++) { + // printf("%d\n", indices[i]); + //} + //printf("]\n"); + cudaMemcpy(dev_indices, indices, sizeof(int) * nearestPow, cudaMemcpyHostToDevice); + + StreamCompaction::Common::kernScatter << > >(nearestPow, dev_odata, dev_idata, dev_bools, dev_indices); + + cudaMemcpy(indices, dev_indices, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); + int j = nearestPow - 1; + do { + j--; + } while (indices[j] == indices[j + 1]); + int compactLength = indices[j] + 1; + //printf("COMPACT LENGTH:%d\n", compactLength); + cudaMemcpy(odata, dev_odata, sizeof(int) * compactLength, cudaMemcpyDeviceToHost); + //printf("RESULT: [\n"); + //for (int i = 0; i < compactLength; i++) { + // printf("%d\n", odata[i]); + //} + //printf("]\n"); + + cudaFree(dev_idata); + cudaFree(dev_odata); + cudaFree(dev_bools); + cudaFree(dev_indices); + free(bools); + free(indices); + + return compactLength; } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index dce79ea..157dace 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -1,20 +1,59 @@ #include #include +#include + #include "common.h" #include "naive.h" namespace StreamCompaction { namespace Naive { -// TODO: __global__ +__global__ void sum(int n, int startIndex, int *odata, const int *idata) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (index < n && index >= startIndex) { + odata[index] = idata[index - startIndex] + idata[index]; + } +} + +__global__ void inclusiveToExclusiveScan(int n, int *odata, const int *idata) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (index < n) { + odata[index] = index == 0 ? 0 : idata[index - 1]; + } +} /** * 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"); + int blockSize = 128; + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + 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, sizeof(int) * n, cudaMemcpyHostToDevice); + cudaMemcpy(dev_odata, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + + int numLevels = ilog2ceil(n); + for (int d = 1; d <= numLevels; d++) { + int startIndex = d == 1 ? 1 : 2 << (d - 2); + sum << > >(n, startIndex, dev_odata, dev_idata); + cudaMemcpy(dev_idata, dev_odata, sizeof(int) * n, cudaMemcpyDeviceToDevice); + } + + inclusiveToExclusiveScan << > >(n, dev_odata, dev_idata); + cudaMemcpy(odata, dev_odata, sizeof(int) * n, cudaMemcpyDeviceToHost); + cudaFree(dev_idata); + cudaFree(dev_odata); } } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index d8dbb32..06c486e 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -13,9 +13,12 @@ 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()); + thrust::device_vector dv_idata(idata, idata + n); + thrust::device_vector dv_odata(odata, odata + n); + + thrust::exclusive_scan(dv_idata.begin(), dv_idata.end(), dv_odata.begin()); + + thrust::copy(dv_odata.begin(), dv_odata.end(), odata); } } From 0ff618fcb4223dbdc83eb6be595d3aab6c2a344f Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 27 Sep 2016 23:57:18 -0400 Subject: [PATCH 3/3] Added readme and performance analysis --- README.md | 78 ++++++++++++++++++-- images/compactPerformance.png | Bin 0 -> 33540 bytes images/scanPerformance.png | Bin 0 -> 34888 bytes src/main.cpp | 130 ++++++++++++++++++++++++++++++++- stream_compaction/efficient.cu | 111 ++++++++++++++-------------- stream_compaction/efficient.h | 4 +- stream_compaction/naive.cu | 27 +++++-- stream_compaction/naive.h | 2 +- stream_compaction/thrust.cu | 15 +++- stream_compaction/thrust.h | 2 +- 10 files changed, 293 insertions(+), 76 deletions(-) create mode 100755 images/compactPerformance.png create mode 100755 images/scanPerformance.png diff --git a/README.md b/README.md index b71c458..b4d2e7a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,77 @@ -CUDA Stream Compaction +CIS 565 Project 2 - CUDA Stream Compaction ====================== +* Richard Lee +* Tested on: Windows 7, i7-3720QM @ 2.60GHz 8GB, GT 650M 4GB (Personal Computer) -**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** +## Performance analysis +Performance testing was done on each implementation across a range of array sizes, averaged across 100 iterations each. +![](images/scanPerformance.png) +![](images/compactPerformance.png) +Overall, the CPU implementation for both the scan and stream compaction algorithms far outperformed their GPU counterparts. This was most likely due to the fact that they were able to deal with the input array and access memory much more efficiently than the GPU. In addition, I was only able to run the algorithms on inputs up to 2^16 in size, due to hardware restrictions - if run on even larger inputs, the GPU may have been able to take advantage of the parallel algorithms and gain a computational advantage over the CPU implementations. -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +One performance bottleneck I encountered was memory, as I was unable to allocate enough memory for an array of size greater than 2^16 on the GPU. I also found that the work-efficient scan was less performant than the naive scan, which could have been due to the fact that the number of threads allocated was not adjusted at runtime based on the level of up-sweep and down-sweep, which would be an additional feature to implement. -### (TODO: Your README) +## Test Output +``` +**************** +** SCAN TESTS ** +**************** + [ 38 19 38 37 5 47 15 35 0 12 3 0 42 ... 7 0 ] +==== cpu scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 803684 803691 ] +==== cpu scan, non-power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 803630 803660 ] + passed +==== naive scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 803684 803691 ] + passed +==== naive scan, non-power-of-two ==== + passed +==== work-efficient scan, power-of-two ==== + passed +==== work-efficient scan, non-power-of-two ==== + passed +==== thrust scan, power-of-two ==== + passed +==== thrust scan, non-power-of-two ==== + passed -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +**************************** +** SCAN PERFORMANCE TESTS ** +**************************** +CPU POW SCAN TIME ELAPSED : 0.060004 milliseconds. +CPU NPOT SCAN TIME ELAPSED : 0.060004 milliseconds. +NAIVE POW SCAN TIME ELAPSED : 0.419879 milliseconds. +NAIVE NPOT SCAN TIME ELAPSED : 0.361572 milliseconds. +EFFICIENT POW SCAN TIME ELAPSED : 0.492805 milliseconds. +EFFICIENT NPOT SCAN TIME ELAPSED : 0.493135 milliseconds. +THRUST POW SCAN TIME ELAPSED : 1.0536 milliseconds. +THRUST NPOT SCAN TIME ELAPSED : 1.06989 milliseconds. +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 2 3 2 1 3 1 1 1 2 0 1 0 2 ... 3 0 ] +==== cpu compact without scan, power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 2 3 ] + passed +==== cpu compact without scan, non-power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 2 2 ] + passed +==== cpu compact with scan ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 2 3 ] + passed +==== work-efficient compact, power-of-two ==== + passed +==== work-efficient compact, non-power-of-two ==== + passed + +***************************************** +** STREAM COMPACTION PERFORMANCE TESTS ** +***************************************** +CPU COMPACT NOSCAN POW TIME ELAPSED : 0.230013 milliseconds. +CPU COMPACT NOSCAN NPOT TIME ELAPSED : 0.230013 milliseconds. +CPU COMPACT SCAN TIME ELAPSED : 0.390022 milliseconds. +EFFICIENT POW COMPACT TIME ELAPSED : 0.530948 milliseconds. +EFFICIENT NPOT COMPACT TIME ELAPSED : 0.533724 milliseconds. +``` \ No newline at end of file diff --git a/images/compactPerformance.png b/images/compactPerformance.png new file mode 100755 index 0000000000000000000000000000000000000000..95ae7ed43ef0b2b0ad0513160c26d7ad5d132606 GIT binary patch literal 33540 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 #include #include "testing_helpers.hpp" +#include +#include int main(int argc, char* argv[]) { - const int SIZE = 1 << 3; + const int SIZE = 1 << 15; const int NPOT = SIZE - 3; int a[SIZE], b[SIZE], c[SIZE]; @@ -43,7 +45,7 @@ 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); @@ -76,6 +78,79 @@ int main(int argc, char* argv[]) { //printArray(NPOT, c, true); printCmpResult(NPOT, b, c); + printf("\n"); + printf("****************************\n"); + printf("** SCAN PERFORMANCE TESTS **\n"); + printf("****************************\n"); + uint32_t iterations = 100; + zeroArray(SIZE, c); + auto begin = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < iterations; ++i) + { + StreamCompaction::CPU::scan(SIZE, c, a); + } + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - begin).count(); + std::cout << "CPU POW SCAN TIME ELAPSED : " << (float)(duration / iterations) * 0.000001 << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + begin = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < iterations; ++i) + { + StreamCompaction::CPU::scan(NPOT, c, a); + } + end = std::chrono::high_resolution_clock::now(); + duration = std::chrono::duration_cast(end - begin).count(); + std::cout << "CPU NPOT SCAN TIME ELAPSED : " << (float)(duration / iterations) * 0.000001 << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + float timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + timer += StreamCompaction::Naive::scan(SIZE, c, a); + } + std::cout << "NAIVE POW SCAN TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + timer += StreamCompaction::Naive::scan(NPOT, c, a); + } + std::cout << "NAIVE NPOT SCAN TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + timer += StreamCompaction::Efficient::scan(SIZE, c, a); + } + std::cout << "EFFICIENT POW SCAN TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + timer += StreamCompaction::Efficient::scan(NPOT, c, a); + } + std::cout << "EFFICIENT NPOT SCAN TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + timer += StreamCompaction::Thrust::scan(SIZE, c, a); + } + std::cout << "THRUST POW SCAN TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + timer += StreamCompaction::Thrust::scan(NPOT, c, a); + } + std::cout << "THRUST NPOT SCAN TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; + printf("\n"); printf("*****************************\n"); printf("** STREAM COMPACTION TESTS **\n"); @@ -120,4 +195,55 @@ int main(int argc, char* argv[]) { count = StreamCompaction::Efficient::compact(NPOT, c, a); //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); + + printf("\n"); + printf("*****************************************\n"); + printf("** STREAM COMPACTION PERFORMANCE TESTS **\n"); + printf("*****************************************\n"); + + zeroArray(SIZE, c); + begin = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < iterations; ++i) + { + StreamCompaction::CPU::compactWithoutScan(SIZE, c, a); + } + end = std::chrono::high_resolution_clock::now(); + duration = std::chrono::duration_cast(end - begin).count(); + std::cout << "CPU COMPACT NOSCAN POW TIME ELAPSED : " << (float)(duration / iterations) * 0.000001 << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + begin = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < iterations; ++i) + { + StreamCompaction::CPU::compactWithoutScan(NPOT, c, a); + } + end = std::chrono::high_resolution_clock::now(); + duration = std::chrono::duration_cast(end - begin).count(); + std::cout << "CPU COMPACT NOSCAN NPOT TIME ELAPSED : " << (float)(duration / iterations) * 0.000001 << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + begin = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < iterations; ++i) + { + StreamCompaction::CPU::compactWithScan(SIZE, c, a); + } + end = std::chrono::high_resolution_clock::now(); + duration = std::chrono::duration_cast(end - begin).count(); + std::cout << "CPU COMPACT SCAN TIME ELAPSED : " << (float)(duration / iterations) * 0.000001 << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + StreamCompaction::Efficient::compact(SIZE, c, a, &timer); + } + std::cout << "EFFICIENT POW COMPACT TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; + + zeroArray(SIZE, c); + timer = 0.0f; + for (int i = 0; i < iterations; ++i) + { + StreamCompaction::Efficient::compact(NPOT, c, a, &timer); + } + std::cout << "EFFICIENT NPOT COMPACT TIME ELAPSED : " << timer / iterations << " milliseconds." << std::endl; } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 8bf404a..7c3796b 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -6,14 +6,20 @@ namespace StreamCompaction { namespace Efficient { -__global__ void upSweep(int n, int d, int *data) { +__global__ void upSweep(int n, int d, int *data, bool isRoot) { int index = (blockIdx.x * blockDim.x) + threadIdx.x; - - int prevOffset = d == 0 ? 1 : 2 << (d - 1); - int offset = prevOffset * 2; + if (index >= n) return; - if (index < n && index % offset == 0) { - data[index + offset - 1] += data[index + prevOffset - 1]; + if (isRoot) { + data[n - 1] = 0; + } + else { + int prevOffset = d == 0 ? 1 : 2 << (d - 1); + int offset = prevOffset * 2; + + if (index % offset == 0) { + data[index + offset - 1] += data[index + prevOffset - 1]; + } } } @@ -33,7 +39,7 @@ __global__ void downSweep(int n, int d, int *data) { /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ -void scan(int n, int *odata, const int *idata) { +float scan(int n, int *odata, const int *idata) { int blockSize = 128; dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); @@ -44,41 +50,30 @@ void scan(int n, int *odata, const int *idata) { checkCUDAError("cudaMalloc dev_data failed!"); cudaMemcpy(dev_data, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + + cudaEventRecord(start); // Up-sweep - int numLevels = ilog2ceil(nearestPow) - 1; - for (int d = 0; d <= numLevels; d++) { - upSweep << > >(nearestPow, d, dev_data); + int numLevels = ilog2ceil(nearestPow); + for (int d = 0; d < numLevels; d++) { + upSweep << > >(nearestPow, d, dev_data, d == (numLevels - 1)); } - cudaMemcpy(odata, dev_data, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); - //printf("AFTER UPSWEEP: [\n"); - //for (int i = 0; i < nearestPow; i++) { - // printf("%d\n", odata[i]); - //} - //printf("]\n"); - odata[nearestPow - 1] = 0; - cudaMemcpy(dev_data, odata, sizeof(int) * nearestPow, cudaMemcpyHostToDevice); - //Down-sweep for (int d = numLevels; d >= 0; d--) { - //printf("LEVEL: %d\n", d); - //cudaMemcpy(odata, dev_data, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); - //printf("[ "); - //for (int i = 0; i < nearestPow; i++) { - // printf("%d ", odata[i]); - //} - //printf("]\n"); downSweep << > >(nearestPow, d, dev_data); } + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); cudaMemcpy(odata, dev_data, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); - //printf("AFTER DOWNSWEEP: [\n"); - //for (int i = 0; i < nearestPow; i++) { - // printf("%d\n", odata[i]); - //} - //printf("]\n"); cudaFree(dev_data); + return milliseconds; } /** @@ -90,7 +85,7 @@ void scan(int n, int *odata, const int *idata) { * @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, float* timer) { int blockSize = 128; dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); @@ -100,7 +95,6 @@ int compact(int n, int *odata, const int *idata) { int* dev_odata; int* dev_bools; int* dev_indices; - int* bools; int* indices; cudaMalloc((void**)&dev_idata, nearestPow * sizeof(int)); @@ -113,29 +107,42 @@ int compact(int n, int *odata, const int *idata) { cudaMalloc((void**)&dev_bools, nearestPow * sizeof(int)); checkCUDAError("cudaMalloc dev_bools failed!"); - bools = (int*)malloc(nearestPow * sizeof(int)); cudaMalloc((void**)&dev_indices, nearestPow * sizeof(int)); checkCUDAError("cudaMalloc dev_indices failed!"); indices = (int*)malloc(nearestPow * sizeof(int)); + cudaEvent_t start, stop; + if (timer) { + cudaEventCreate(&start); + cudaEventCreate(&stop); + + cudaEventRecord(start); + } + StreamCompaction::Common::kernMapToBoolean << > >(nearestPow, dev_bools, dev_idata); - cudaMemcpy(bools, dev_bools, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); - //printf("BOOLS: [\n"); - //for (int i = 0; i < nearestPow; i++) { - // printf("%d\n", bools[i]); - //} - //printf("]\n"); - - scan(n, indices, bools); - //printf("INDICES: [\n"); - //for (int i = 0; i < nearestPow; i++) { - // printf("%d\n", indices[i]); - //} - //printf("]\n"); - cudaMemcpy(dev_indices, indices, sizeof(int) * nearestPow, cudaMemcpyHostToDevice); + cudaMemcpy(dev_indices, dev_bools, sizeof(int) * nearestPow, cudaMemcpyDeviceToDevice); + + // Up-sweep + int numLevels = ilog2ceil(nearestPow); + for (int d = 0; d < numLevels; d++) { + upSweep << > >(nearestPow, d, dev_indices, d == (numLevels - 1)); + } + + //Down-sweep + for (int d = numLevels; d >= 0; d--) { + downSweep << > >(nearestPow, d, dev_indices); + } StreamCompaction::Common::kernScatter << > >(nearestPow, dev_odata, dev_idata, dev_bools, dev_indices); + + if (timer) { + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + (*timer) += milliseconds; + } cudaMemcpy(indices, dev_indices, sizeof(int) * nearestPow, cudaMemcpyDeviceToHost); int j = nearestPow - 1; @@ -143,19 +150,13 @@ int compact(int n, int *odata, const int *idata) { j--; } while (indices[j] == indices[j + 1]); int compactLength = indices[j] + 1; - //printf("COMPACT LENGTH:%d\n", compactLength); + cudaMemcpy(odata, dev_odata, sizeof(int) * compactLength, cudaMemcpyDeviceToHost); - //printf("RESULT: [\n"); - //for (int i = 0; i < compactLength; i++) { - // printf("%d\n", odata[i]); - //} - //printf("]\n"); cudaFree(dev_idata); cudaFree(dev_odata); cudaFree(dev_bools); cudaFree(dev_indices); - free(bools); free(indices); return compactLength; diff --git a/stream_compaction/efficient.h b/stream_compaction/efficient.h index 395ba10..9cb9a87 100644 --- a/stream_compaction/efficient.h +++ b/stream_compaction/efficient.h @@ -2,8 +2,8 @@ namespace StreamCompaction { namespace Efficient { - void scan(int n, int *odata, const int *idata); + float scan(int n, int *odata, const int *idata); - int compact(int n, int *odata, const int *idata); + int compact(int n, int *odata, const int *idata, float* timer = NULL); } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 157dace..a71066e 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -10,10 +10,13 @@ namespace Naive { __global__ void sum(int n, int startIndex, int *odata, const int *idata) { int index = (blockIdx.x * blockDim.x) + threadIdx.x; - - if (index < n && index >= startIndex) { + if (index >= n) return; + if (index >= startIndex) { odata[index] = idata[index - startIndex] + idata[index]; } + else { + odata[index] = idata[index]; + } } __global__ void inclusiveToExclusiveScan(int n, int *odata, const int *idata) { @@ -27,7 +30,7 @@ __global__ void inclusiveToExclusiveScan(int n, int *odata, const int *idata) { /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ -void scan(int n, int *odata, const int *idata) { +float scan(int n, int *odata, const int *idata) { int blockSize = 128; dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); @@ -42,18 +45,30 @@ void scan(int n, int *odata, const int *idata) { cudaMemcpy(dev_idata, idata, sizeof(int) * n, cudaMemcpyHostToDevice); cudaMemcpy(dev_odata, idata, sizeof(int) * n, cudaMemcpyHostToDevice); + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + int numLevels = ilog2ceil(n); - for (int d = 1; d <= numLevels; d++) { - int startIndex = d == 1 ? 1 : 2 << (d - 2); + for (int startIndex = 1; startIndex <= (1 << (numLevels - 1)); startIndex *= 2) { sum << > >(n, startIndex, dev_odata, dev_idata); - cudaMemcpy(dev_idata, dev_odata, sizeof(int) * n, cudaMemcpyDeviceToDevice); + std::swap(dev_idata, dev_odata); } inclusiveToExclusiveScan << > >(n, dev_odata, dev_idata); + + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + cudaMemcpy(odata, dev_odata, sizeof(int) * n, cudaMemcpyDeviceToHost); cudaFree(dev_idata); cudaFree(dev_odata); + + return milliseconds; } } diff --git a/stream_compaction/naive.h b/stream_compaction/naive.h index 21152d6..7090b46 100644 --- a/stream_compaction/naive.h +++ b/stream_compaction/naive.h @@ -2,6 +2,6 @@ namespace StreamCompaction { namespace Naive { - void scan(int n, int *odata, const int *idata); + float scan(int n, int *odata, const int *idata); } } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 06c486e..617e506 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -12,13 +12,24 @@ namespace Thrust { /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ -void scan(int n, int *odata, const int *idata) { +float scan(int n, int *odata, const int *idata) { thrust::device_vector dv_idata(idata, idata + n); thrust::device_vector dv_odata(odata, odata + n); + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + + cudaEventRecord(start); thrust::exclusive_scan(dv_idata.begin(), dv_idata.end(), dv_odata.begin()); - + cudaEventRecord(stop); + cudaEventSynchronize(stop); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + thrust::copy(dv_odata.begin(), dv_odata.end(), odata); + + return milliseconds; } } diff --git a/stream_compaction/thrust.h b/stream_compaction/thrust.h index 06707f3..d6182b2 100644 --- a/stream_compaction/thrust.h +++ b/stream_compaction/thrust.h @@ -2,6 +2,6 @@ namespace StreamCompaction { namespace Thrust { - void scan(int n, int *odata, const int *idata); + float scan(int n, int *odata, const int *idata); } }