From c14b80b3782bb9c597ea7278635be6aa42fcd2ca Mon Sep 17 00:00:00 2001 From: Li Zheng Date: Mon, 21 Sep 2020 00:06:16 +0800 Subject: [PATCH 1/9] part2 --- stream_compaction/common.h | 2 +- stream_compaction/cpu.cu | 112 ++++++++++++++++++++++++------------- stream_compaction/naive.cu | 60 ++++++++++++++------ 3 files changed, 116 insertions(+), 58 deletions(-) diff --git a/stream_compaction/common.h b/stream_compaction/common.h index d2c1fed..729fae9 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -12,7 +12,7 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) - +#define blockSize 128 /** * 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 719fa11..3d3b669 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -4,47 +4,79 @@ #include "common.h" namespace StreamCompaction { - namespace CPU { - using StreamCompaction::Common::PerformanceTimer; - PerformanceTimer& timer() - { - static PerformanceTimer timer; - return timer; - } + namespace CPU { + using StreamCompaction::Common::PerformanceTimer; + PerformanceTimer& timer() + { + static PerformanceTimer timer; + return timer; + } - /** - * CPU scan (prefix sum). - * For performance analysis, this is supposed to be a simple for loop. - * (Optional) For better understanding before starting moving to GPU, you can simulate your GPU scan in this function first. - */ - void scan(int n, int *odata, const int *idata) { - timer().startCpuTimer(); - // TODO - timer().endCpuTimer(); - } + /** + * CPU scan (prefix sum). + * For performance analysis, this is supposed to be a simple for loop. + * (Optional) For better understanding before starting moving to GPU, you can simulate your GPU scan in this function first. + */ + void scan(int n, int *odata, const int *idata) { + timer().startCpuTimer(); + // TODO + odata[0] = 0; + for (int k = 1; k < n; ++k) { + odata[k] = odata[k - 1] + idata[k - 1]; + } + timer().endCpuTimer(); + } - /** - * CPU stream compaction without using the scan function. - * - * @returns the number of elements remaining after compaction. - */ - int compactWithoutScan(int n, int *odata, const int *idata) { - timer().startCpuTimer(); - // TODO - timer().endCpuTimer(); - return -1; - } + /** + * CPU stream compaction without using the scan function. + * + * @returns the number of elements remaining after compaction. + */ + int compactWithoutScan(int n, int *odata, const int *idata) { + timer().startCpuTimer(); + // TODO + int ptr = 0; + for (int i = 0; i < n; i++) { + if (idata[i] != 0) { + odata[ptr] = idata[i]; + ptr++; + } + } + timer().endCpuTimer(); + return ptr; + } - /** - * CPU stream compaction using scan and scatter, like the parallel version. - * - * @returns the number of elements remaining after compaction. - */ - int compactWithScan(int n, int *odata, const int *idata) { - timer().startCpuTimer(); - // TODO - timer().endCpuTimer(); - return -1; - } - } + /** + * CPU stream compaction using scan and scatter, like the parallel version. + * + * @returns the number of elements remaining after compaction. + */ + int compactWithScan(int n, int *odata, const int *idata) { + timer().startCpuTimer(); + // TODO + int count = 0; + int *tmp = new int[n]; + for (int i = 0; i < n; i++) { + if (idata[i] == 0) { + tmp[i] = 0; + } + else { + tmp[i] = 1; + count++; + } + } + int *tmpScan = new int[n]; + tmpScan[0] = 0; + for (int k = 1; k < n; ++k) { + tmpScan[k] = tmpScan[k - 1] + tmp[k - 1]; + } + for (int i = 0; i < n; i++) { + odata[tmpScan[i]] = idata[i]; + } + timer().endCpuTimer(); + delete tmp; + delete tmpScan; + return count; + } + } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 4308876..044f1ac 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -4,22 +4,48 @@ #include "naive.h" namespace StreamCompaction { - namespace Naive { - using StreamCompaction::Common::PerformanceTimer; - PerformanceTimer& timer() - { - static PerformanceTimer timer; - return timer; - } - // TODO: __global__ + namespace Naive { + using StreamCompaction::Common::PerformanceTimer; + PerformanceTimer& timer() + { + static PerformanceTimer timer; + return timer; + } + // TODO: __global__ + __global__ void kernNaiveScan(int n, int *odata, int *tmp, int d) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + tmp[index] = odata[index]; + int offset = (int)pow(2.0, (double)(d - 1)); + if (index >= offset) { + tmp[index] = odata[index - offset] + odata[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 - timer().endGpuTimer(); - } - } + /** + * Performs prefix-sum (aka scan) on idata, storing the result into odata. + */ + void scan(int n, int *odata, const int *idata) { + // TODO + int *dev_odata, *dev_idata, *dev_tmp; + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + int *zero = 0; + cudaMemcpy(dev_odata, zero, sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy(dev_odata + 1, idata, (n - 1) * sizeof(int), cudaMemcpyHostToDevice); + cudaMalloc((void**)&dev_tmp, n * sizeof(int)); + + timer().startGpuTimer(); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + for (int d = 1; d <= ilog2ceil(n); d++) { + kernNaiveScan << > > (n, dev_odata, dev_tmp, d); + int *tmpPtr = dev_tmp; + dev_tmp = dev_odata; + dev_odata = tmpPtr; + } + timer().endGpuTimer(); + cudaMemcpy(odata, dev_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + } + } } From b7dd4395c57cd9d6955ab5745cb9df84dd695471 Mon Sep 17 00:00:00 2001 From: Li Zheng Date: Mon, 21 Sep 2020 02:03:37 +0800 Subject: [PATCH 2/9] 3.1 power of two --- stream_compaction/efficient.cu | 45 ++++++++++++++++++++++++++++++++-- stream_compaction/naive.cu | 2 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 2db346e..f77785c 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -12,13 +12,54 @@ namespace StreamCompaction { return timer; } + __global__ void kernEfficientScanUpSweep(int n, int *odata, int d) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + int interval = (int)pow(2.0, (double)(d + 1)); + if ((index + 1) % interval == 0) { + odata[index] += odata[index - interval / 2]; + } + } + + __global__ void kernEfficientScanDownSweep(int n, int *odata, int d, int topLayer) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + if (d == topLayer && index == n - 1) { + odata[index] = 0; + } + int interval = (int)pow(2.0, (double)(d + 1)); + if ((index + 1) % interval == 0) { + int tmp = odata[index - interval / 2]; + odata[index - interval / 2] = odata[index]; + odata[index] += tmp; + } + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); // TODO - timer().endGpuTimer(); + int *dev_odata, *dev_tmp; + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + cudaMemcpy(dev_odata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + timer().startGpuTimer(); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + int topLayer = ilog2ceil(n) - 1; + for (int d = 0; d <= topLayer; d++) { + kernEfficientScanUpSweep << > > (n, dev_odata, d); + } + + for (int d = topLayer; d >= 0; d--) { + kernEfficientScanDownSweep << > > (n, dev_odata, d, topLayer); + } + + timer().endGpuTimer(); + cudaMemcpy(odata, dev_odata, n * sizeof(int), cudaMemcpyDeviceToHost); } /** diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 044f1ac..7f3c809 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -29,7 +29,7 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { // TODO - int *dev_odata, *dev_idata, *dev_tmp; + int *dev_odata, *dev_tmp; cudaMalloc((void**)&dev_odata, n * sizeof(int)); int *zero = 0; cudaMemcpy(dev_odata, zero, sizeof(int), cudaMemcpyHostToDevice); From e496c3122ed8db9a446cd7dabf5170e588b93eff Mon Sep 17 00:00:00 2001 From: Li Zheng Date: Mon, 21 Sep 2020 12:48:09 +0800 Subject: [PATCH 3/9] efficient scan non-power-of-two --- stream_compaction/efficient.cu | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index f77785c..377978d 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -43,19 +43,26 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { // TODO + int N = pow(2, ilog2ceil(n)); int *dev_odata, *dev_tmp; - cudaMalloc((void**)&dev_odata, n * sizeof(int)); + cudaMalloc((void**)&dev_odata, N * sizeof(int)); cudaMemcpy(dev_odata, idata, n * sizeof(int), cudaMemcpyHostToDevice); - + if (N > n) { + int *zeroArray = new int[N - n]; + for (int i = 0; i < N - n; i++) { + zeroArray[i] = 0; + } + cudaMemcpy(dev_odata + n, zeroArray, (N - n) * sizeof(int), cudaMemcpyHostToDevice); + } timer().startGpuTimer(); dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); int topLayer = ilog2ceil(n) - 1; for (int d = 0; d <= topLayer; d++) { - kernEfficientScanUpSweep << > > (n, dev_odata, d); + kernEfficientScanUpSweep << > > (N, dev_odata, d); } for (int d = topLayer; d >= 0; d--) { - kernEfficientScanDownSweep << > > (n, dev_odata, d, topLayer); + kernEfficientScanDownSweep << > > (N, dev_odata, d, topLayer); } timer().endGpuTimer(); From 62430ca8bfa29d973ba99ce1aab49467537aa21c Mon Sep 17 00:00:00 2001 From: Li Zheng Date: Mon, 21 Sep 2020 16:39:23 +0800 Subject: [PATCH 4/9] part3.2 --- stream_compaction/common.cu | 17 +++++++++++ stream_compaction/common.h | 1 + stream_compaction/efficient.cu | 52 +++++++++++++++++++++++++++++++--- stream_compaction/naive.cu | 2 ++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 2ed6d63..2e35147 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -24,6 +24,16 @@ namespace StreamCompaction { */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { // TODO + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index >= n) { + return; + } + if (idata[index] == 0) { + bools[index] = 0; + } + else { + bools[index] = 1; + } } /** @@ -33,6 +43,13 @@ namespace StreamCompaction { __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]; + } } } diff --git a/stream_compaction/common.h b/stream_compaction/common.h index 729fae9..eb1daf2 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -13,6 +13,7 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) #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 377978d..7d72278 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -44,7 +44,7 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata) { // TODO int N = pow(2, ilog2ceil(n)); - int *dev_odata, *dev_tmp; + int *dev_odata; cudaMalloc((void**)&dev_odata, N * sizeof(int)); cudaMemcpy(dev_odata, idata, n * sizeof(int), cudaMemcpyHostToDevice); if (N > n) { @@ -67,8 +67,10 @@ namespace StreamCompaction { timer().endGpuTimer(); cudaMemcpy(odata, dev_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_odata); } + /** * Performs stream compaction on idata, storing the result into odata. * All zeroes are discarded. @@ -79,10 +81,52 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { - timer().startGpuTimer(); // TODO - timer().endGpuTimer(); - return -1; + int N = pow(2, ilog2ceil(n)); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + int topLayer = ilog2ceil(n) - 1; + int *dev_idata, *dev_odata, *dev_bools, *dev_indices; + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + cudaMalloc((void**)&dev_bools, N * sizeof(int)); + cudaMalloc((void**)&dev_indices, N * sizeof(int)); + + timer().startGpuTimer(); + Common::kernMapToBoolean << > > (n, dev_bools, dev_idata); + if (N > n) { + int *zeroArray = new int[N - n]; + for (int i = 0; i < N - n; i++) { + zeroArray[i] = 0; + } + cudaMemcpy(dev_bools + n, zeroArray, (N - n) * sizeof(int), cudaMemcpyHostToDevice); + } + cudaMemcpy(dev_indices, dev_bools, N * sizeof(int), cudaMemcpyDeviceToDevice); + + int countScatter = 0; + for (int i = 0; i < n; i++) { + if (idata[i] != 0) { + countScatter++; + } + } + for (int d = 0; d <= topLayer; d++) { + kernEfficientScanUpSweep << > > (N, dev_indices, d); + } + + for (int d = topLayer; d >= 0; d--) { + kernEfficientScanDownSweep << > > (N, dev_indices, d, topLayer); + } + + Common::kernScatter << > > (n, dev_odata, + dev_idata, dev_bools, dev_indices); + timer().endGpuTimer(); + + cudaMemcpy(odata, dev_odata, countScatter * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_idata); + cudaFree(dev_odata); + cudaFree(dev_bools); + cudaFree(dev_indices); + return countScatter; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 7f3c809..04ef3c5 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -46,6 +46,8 @@ namespace StreamCompaction { } timer().endGpuTimer(); cudaMemcpy(odata, dev_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_odata); + cudaFree(dev_tmp); } } } From 13ce89c7468410ca62038c3fae4192fac97489e8 Mon Sep 17 00:00:00 2001 From: Li Zheng Date: Tue, 22 Sep 2020 01:04:07 +0800 Subject: [PATCH 5/9] thrust --- src/main.cpp | 2 +- stream_compaction/efficient.cu | 12 +++++++----- stream_compaction/naive.cu | 7 +++++-- stream_compaction/thrust.cu | 11 ++++++++++- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 896ac2b..2fce82c 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 << 16; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 7d72278..c97f2c0 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -17,9 +17,10 @@ namespace StreamCompaction { if (index >= n) { return; } - int interval = (int)pow(2.0, (double)(d + 1)); + int interval = 1 << (d + 1); + int halfInterval = 1 << d; if ((index + 1) % interval == 0) { - odata[index] += odata[index - interval / 2]; + odata[index] += odata[index - halfInterval]; } } @@ -31,10 +32,11 @@ namespace StreamCompaction { if (d == topLayer && index == n - 1) { odata[index] = 0; } - int interval = (int)pow(2.0, (double)(d + 1)); + int interval = 1 << (d + 1); + int halfInterval = 1 << d; if ((index + 1) % interval == 0) { - int tmp = odata[index - interval / 2]; - odata[index - interval / 2] = odata[index]; + int tmp = odata[index - halfInterval]; + odata[index - halfInterval] = odata[index]; odata[index] += tmp; } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 04ef3c5..f857c3a 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -17,11 +17,14 @@ namespace StreamCompaction { if (index >= n) { return; } - tmp[index] = odata[index]; - int offset = (int)pow(2.0, (double)(d - 1)); + + int offset = (int)pow(float(2), (float)(d - 1)); if (index >= offset) { tmp[index] = odata[index - offset] + odata[index]; } + else { + tmp[index] = odata[index]; + } } /** diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 1def45e..3e101de 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -18,11 +18,20 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + int *dev_idata, *dev_odata; + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + thrust::device_ptr dev_thrust_odata(dev_odata); + thrust::device_ptr dev_thrust_idata(dev_idata); + 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_thrust_idata, dev_thrust_idata + n, dev_thrust_odata); timer().endGpuTimer(); + + cudaMemcpy(odata, thrust::raw_pointer_cast(dev_thrust_odata), n * sizeof(int), cudaMemcpyDeviceToHost); } } } From 69aa9dc5f484887de6ea80947e1e05b5784787e7 Mon Sep 17 00:00:00 2001 From: Li Zheng Date: Tue, 22 Sep 2020 01:08:28 +0800 Subject: [PATCH 6/9] thrust --- src/main.cpp | 2 +- stream_compaction/naive.cu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 2fce82c..600c29f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 16; // feel free to change the size of array +const int SIZE = 1 << 20; // 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/naive.cu b/stream_compaction/naive.cu index f857c3a..b9584fe 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -18,7 +18,7 @@ namespace StreamCompaction { return; } - int offset = (int)pow(float(2), (float)(d - 1)); + int offset = 1 << (d - 1); if (index >= offset) { tmp[index] = odata[index - offset] + odata[index]; } From 0d7cff1eee440a115f984241fe823b4367178b5a Mon Sep 17 00:00:00 2001 From: Li Zheng Date: Tue, 22 Sep 2020 15:45:26 +0800 Subject: [PATCH 7/9] thrust and analysis --- img/blockSize.PNG | Bin 0 -> 14199 bytes img/nonPowerOfTwo.PNG | Bin 0 -> 18123 bytes img/performance analysis.xlsx | Bin 0 -> 25806 bytes img/powerOfTwo.PNG | Bin 0 -> 17677 bytes src/main.cpp | 2 +- stream_compaction/common.h | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 img/blockSize.PNG create mode 100644 img/nonPowerOfTwo.PNG create mode 100644 img/performance analysis.xlsx create mode 100644 img/powerOfTwo.PNG diff --git a/img/blockSize.PNG b/img/blockSize.PNG new file mode 100644 index 0000000000000000000000000000000000000000..01b595d7e48f3af4d20bf50dd89c6de078fdc6da GIT binary patch literal 14199 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 F{{dxKw>kg- literal 0 HcmV?d00001 diff --git a/img/performance analysis.xlsx b/img/performance analysis.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..dca8dd3b7d2f10ef0117b04c47a3a8a0ab921158 GIT binary patch literal 25806 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�Ee77BYX&X@t;#HHEA5 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 a`SlQmMlk}dEeJ#q_*Dk(P<|HcNB;-$&`$IK literal 0 HcmV?d00001 diff --git a/img/powerOfTwo.PNG b/img/powerOfTwo.PNG new file mode 100644 index 0000000000000000000000000000000000000000..dd5a50deef2747c1b655f7c5f14b15f5d8771f29 GIT binary patch literal 17677 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 Lt7ExGFWvqh(rabA literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index 600c29f..2fce82c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 20; // feel free to change the size of array +const int SIZE = 1 << 16; // 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/common.h b/stream_compaction/common.h index eb1daf2..297bb4d 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -12,7 +12,7 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) -#define blockSize 128 +#define blockSize 256 /** * Check for CUDA errors; print and exit if there was a problem. From 9f56501a9e78ce479fa1149a77816c8b620772e0 Mon Sep 17 00:00:00 2001 From: zhengliii <35391271+zhengliii@users.noreply.github.com> Date: Tue, 22 Sep 2020 17:23:15 +0800 Subject: [PATCH 8/9] Update README.md --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0e38ddb..22f2c16 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,73 @@ 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) +* Li Zheng + * [LinkedIn](https://www.linkedin.com/in/li-zheng-1955ba169) +* Tested on: Windows CUDA10, i5-3600 @ 3.59GHz 16GB, RTX 2060 6GB (personal computer) -### (TODO: Your README) +This project implements different versions of scan, including CPU scan, naive scan, work-efficient scan and thrust scan. Some of these methods are used to implement stream compaction. A timer is used to measure the time cost and evaluate the performance. -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +### Performance Analysis +![blockSize](images/blockSize.PNG) +This diagram demonstrates the change of time with respect to block size. The block size of 128 and 256 have relatively good performance. +![powerOfTwo](images/powerOfTwo.PNG) +![nonePowerOfTwo](images/nonPowerOfTwo.PNG) +The diagrams show the change of time with array size increases. The first diagram is for power-of-two size array. The second one is for non-power-of-two size array. Their performance is almost the same. When the array size is small, the CPU implementation has a better performance. I think it is because most of the threads doesn't actually work at a deeper level, but just swap two device memory. Additionally, the GPU version algorithms use bit shifting to find offsets or intervals of each level, which takes extra time. With the array size increases, the GPU version algorithms have better performance, especially the work-efficient and thrust method. + +### Output of The Test Program +Here is the test result of an array of 2^16 and a block size of 128. More results are in images/performance analysis.xlsx. +``` +**************** +** SCAN TESTS ** +**************** + [ 49 29 24 15 46 49 46 8 35 40 38 18 44 ... 3 0 ] +==== cpu scan, power-of-two ==== + elapsed time: 0.1305ms (std::chrono Measured) + [ 0 49 78 102 117 163 212 258 266 301 341 379 397 ... 1603889 1603892 ] +==== cpu scan, non-power-of-two ==== + elapsed time: 0.129ms (std::chrono Measured) + [ 0 49 78 102 117 163 212 258 266 301 341 379 397 ... 1603839 1603856 ] + passed +==== naive scan, power-of-two ==== + elapsed time: 0.052416ms (CUDA Measured) + passed +==== naive scan, non-power-of-two ==== + elapsed time: 0.050752ms (CUDA Measured) + passed +==== work-efficient scan, power-of-two ==== + elapsed time: 0.11264ms (CUDA Measured) + passed +==== work-efficient scan, non-power-of-two ==== + elapsed time: 0.113344ms (CUDA Measured) + passed +==== thrust scan, power-of-two ==== + elapsed time: 0.073344ms (CUDA Measured) + passed +==== thrust scan, non-power-of-two ==== + elapsed time: 0.055296ms (CUDA Measured) + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 3 3 2 1 0 1 2 2 3 0 2 2 0 ... 3 0 ] +==== cpu compact without scan, power-of-two ==== + elapsed time: 0.1239ms (std::chrono Measured) + [ 3 3 2 1 1 2 2 3 2 2 1 2 2 ... 3 3 ] + passed +==== cpu compact without scan, non-power-of-two ==== + elapsed time: 0.1241ms (std::chrono Measured) + [ 3 3 2 1 1 2 2 3 2 2 1 2 2 ... 3 2 ] + passed +==== cpu compact with scan ==== + elapsed time: 0.3149ms (std::chrono Measured) + [ 3 3 2 1 1 2 2 3 2 2 1 2 2 ... 3 3 ] + passed +==== work-efficient compact, power-of-two ==== + elapsed time: 0.124928ms (CUDA Measured) + passed +==== work-efficient compact, non-power-of-two ==== + elapsed time: 0.198656ms (CUDA Measured) + passed +``` From 7363732d8fdf40a0257e024851ccfbefbf24cddd Mon Sep 17 00:00:00 2001 From: zhengliii <35391271+zhengliii@users.noreply.github.com> Date: Tue, 22 Sep 2020 17:27:31 +0800 Subject: [PATCH 9/9] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 22f2c16..c684bdb 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ CUDA Stream Compaction This project implements different versions of scan, including CPU scan, naive scan, work-efficient scan and thrust scan. Some of these methods are used to implement stream compaction. A timer is used to measure the time cost and evaluate the performance. ### Performance Analysis -![blockSize](images/blockSize.PNG) +![blockSize](img/blockSize.PNG) This diagram demonstrates the change of time with respect to block size. The block size of 128 and 256 have relatively good performance. -![powerOfTwo](images/powerOfTwo.PNG) -![nonePowerOfTwo](images/nonPowerOfTwo.PNG) +![powerOfTwo](img/powerOfTwo.PNG) +![nonePowerOfTwo](img/nonPowerOfTwo.PNG) The diagrams show the change of time with array size increases. The first diagram is for power-of-two size array. The second one is for non-power-of-two size array. Their performance is almost the same. When the array size is small, the CPU implementation has a better performance. I think it is because most of the threads doesn't actually work at a deeper level, but just swap two device memory. Additionally, the GPU version algorithms use bit shifting to find offsets or intervals of each level, which takes extra time. With the array size increases, the GPU version algorithms have better performance, especially the work-efficient and thrust method. ### Output of The Test Program -Here is the test result of an array of 2^16 and a block size of 128. More results are in images/performance analysis.xlsx. +Here is the test result of an array of 2^16 and a block size of 128. More results are in img/performance analysis.xlsx. ``` **************** ** SCAN TESTS **