From a288541d9049998b737dc7fa99fc14ee7125a3c2 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Sun, 25 Sep 2016 23:17:22 -0400 Subject: [PATCH 01/21] cpu finished --- src/main.cpp | 7 +++++-- stream_compaction/common.cu | 9 +++++++-- stream_compaction/cpu.cu | 32 ++++++++++++++++++++++++++------ stream_compaction/naive.cu | 37 +++++++++++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 675da35..dfb2fa8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,6 +13,7 @@ #include #include "testing_helpers.hpp" + int main(int argc, char* argv[]) { const int SIZE = 1 << 8; const int NPOT = SIZE - 3; @@ -43,13 +44,13 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("naive scan, power-of-two"); StreamCompaction::Naive::scan(SIZE, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); printDesc("naive scan, non-power-of-two"); StreamCompaction::Naive::scan(NPOT, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(NPOT, b, c); zeroArray(SIZE, c); @@ -120,4 +121,6 @@ int main(int argc, char* argv[]) { count = StreamCompaction::Efficient::compact(NPOT, c, a); //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); + int i = 0; + scanf("%d", i); } diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index fe872d4..3f59329 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -23,7 +23,8 @@ namespace Common { * which map to 0 will be removed, and elements which map to 1 will be kept. */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { - // TODO + int idx = threadIdx.x + (blockIdx.x * blockDim.x); + bools[idx] = idata[idx] == 0; } /** @@ -32,7 +33,11 @@ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { */ __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { - // TODO + int idx = threadIdx.x + (blockIdx.x * blockDim.x); + if (bools[idx]) { + odata[indices[idx]] = idata[idx]; + } + } } diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index e600c29..95934f8 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -8,8 +8,13 @@ namespace CPU { * CPU scan (prefix sum). */ void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); + if (n <= 0) { + return; + } + odata[0] = idata[0]; + for (int i = 1; i < n; i++) { + odata[i] = odata[i - 1] + idata[i]; + } } /** @@ -18,8 +23,13 @@ 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 k = 0; + for (int i = 0; i < n; i++) { + if (idata[i]) { + odata[k++] = idata[i]; + } + } + return k; } /** @@ -28,8 +38,18 @@ 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; + for (int i = 0; i < n; i++) { + odata[i] = idata[i] != 0; + } + scan(n, odata, odata); + int k = 0; + for (int i = 0; i < n; i++) { + if (idata[i] != 0) { + k++; + odata[odata[i]-1] = idata[i]; + } + } + return k; } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 3d86b60..c4bbe45 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -2,18 +2,51 @@ #include #include "common.h" #include "naive.h" +#include + +#define blockSize 128 namespace StreamCompaction { namespace Naive { // TODO: __global__ + +__global__ void kernelScan(int offset, int n, int *swapA, int *swapB) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) return; + if (index < offset) { + swapB[index] = swapA[index]; + } + else { + swapB[index] = swapA[index - offset] + swapA[index]; + } +} /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + int log2n = ilog2ceil(n); + int offset; + int *swapA, *swapB; + cudaMalloc((void**)&swapA, n * sizeof(int)); + checkCUDAError("cudaMalloc swapA failed!"); + cudaMalloc((void**)&swapB, n * sizeof(int)); + checkCUDAError("cudaMalloc swapB failed!"); + + cudaMemcpy(swapA, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + + for (int i = 0; i < log2n; i++) { + offset = 1 << (i - 1); + kernelScan << > >(offset, n, swapA, swapB); + std::swap(swapA, swapB); + } + cudaMemcpy(odata + 1, swapA, (n - 1) * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(swapA); + cudaFree(swapB); } } From 48a77e9a872a8728ac613eeada3ad83283f9bfd8 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Mon, 26 Sep 2016 00:04:51 -0400 Subject: [PATCH 02/21] naive done --- stream_compaction/cpu.cu | 6 +++--- stream_compaction/efficient.cu | 28 ++++++++++++++++++++++++++-- stream_compaction/naive.cu | 6 +++--- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 95934f8..541cee5 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -11,9 +11,9 @@ void scan(int n, int *odata, const int *idata) { if (n <= 0) { return; } - odata[0] = idata[0]; + odata[0] = 0; for (int i = 1; i < n; i++) { - odata[i] = odata[i - 1] + idata[i]; + odata[i] = odata[i - 1] + idata[i - 1]; } } @@ -46,7 +46,7 @@ int compactWithScan(int n, int *odata, const int *idata) { for (int i = 0; i < n; i++) { if (idata[i] != 0) { k++; - odata[odata[i]-1] = idata[i]; + odata[odata[i]] = idata[i]; } } return k; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index b2f739b..a94b403 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,17 +3,41 @@ #include "common.h" #include "efficient.h" +#define blockSize 128 namespace StreamCompaction { namespace Efficient { // TODO: __global__ +__global__ void kernUpSweep(int n, int offset, int *odata, const int *idata) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + odata[index + offset - 1] += +} + +__global__ void kernDownSweep(int n, int offset, 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) { - // TODO - printf("TODO\n"); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + int *buf; + cudaMalloc((void**)&buf, n * sizeof(int)); + checkCUDAError("cudaMalloc buf failed!"); + + int offset; + for (int i = 0; i <= ilog2(n); i++) { + kernUpSweep << > >(n, offset, odata, idata); + } + for (int i = ilog2(n); i <= 0; i--) { + kernDownSweep << > >(n, offset, odata, idata); + } + + cudaFree(buf); } /** diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index c4bbe45..403cfae 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -27,7 +27,6 @@ __global__ void kernelScan(int offset, int n, int *swapA, int *swapB) { */ void scan(int n, int *odata, const int *idata) { dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); - int log2n = ilog2ceil(n); int offset; int *swapA, *swapB; cudaMalloc((void**)&swapA, n * sizeof(int)); @@ -37,13 +36,14 @@ void scan(int n, int *odata, const int *idata) { cudaMemcpy(swapA, idata, n * sizeof(int), cudaMemcpyHostToDevice); - - for (int i = 0; i < log2n; i++) { + int log2n = ilog2ceil(n); + for (int i = 1; i <= log2n; i++) { offset = 1 << (i - 1); kernelScan << > >(offset, n, swapA, swapB); std::swap(swapA, swapB); } cudaMemcpy(odata + 1, swapA, (n - 1) * sizeof(int), cudaMemcpyDeviceToHost); + odata[0] = 0; cudaFree(swapA); cudaFree(swapB); From ec93925d27c6363b9b15f0c1fb8b9d6f3ff27ee3 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Mon, 26 Sep 2016 02:09:50 -0400 Subject: [PATCH 03/21] efficient working --- src/main.cpp | 9 +++-- stream_compaction/common.cu | 2 +- stream_compaction/cpu.cu | 8 +++-- stream_compaction/efficient.cu | 61 ++++++++++++++++++++++++++++------ 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index dfb2fa8..eb0ca3a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ int main(int argc, char* argv[]) { const int SIZE = 1 << 8; const int NPOT = SIZE - 3; int a[SIZE], b[SIZE], c[SIZE]; + // Scan tests @@ -30,6 +31,10 @@ int main(int argc, char* argv[]) { a[SIZE - 1] = 0; printArray(SIZE, a, true); + int d[] = { 0, 1, 2, 3, 4, 5}; + int dexp[] = { 0, 0, 1, 3, 6, 10}; + int e[] = { 0, 1, 2, 6, 4, 9, 6, 28 }; + zeroArray(SIZE, b); printDesc("cpu scan, power-of-two"); StreamCompaction::CPU::scan(SIZE, b, a); @@ -56,13 +61,13 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("work-efficient scan, power-of-two"); StreamCompaction::Efficient::scan(SIZE, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); printDesc("work-efficient scan, non-power-of-two"); StreamCompaction::Efficient::scan(NPOT, c, a); - //printArray(NPOT, c, true); + printArray(NPOT, c, true); printCmpResult(NPOT, b, c); zeroArray(SIZE, c); diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 3f59329..622547f 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -24,7 +24,7 @@ namespace Common { */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { int idx = threadIdx.x + (blockIdx.x * blockDim.x); - bools[idx] = idata[idx] == 0; + bools[idx] = idata[idx] != 0; } /** diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 541cee5..cd9ac25 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -38,15 +38,17 @@ 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) { + int *scanResults = new int[n]; + for (int i = 0; i < n; i++) { odata[i] = idata[i] != 0; } - scan(n, odata, odata); + scan(n, scanResults, odata); int k = 0; for (int i = 0; i < n; i++) { - if (idata[i] != 0) { + if (idata[i]) { k++; - odata[odata[i]] = idata[i]; + odata[scanResults[i]] = idata[i]; } } return k; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index a94b403..4b88ff2 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -9,13 +9,24 @@ namespace Efficient { // TODO: __global__ -__global__ void kernUpSweep(int n, int offset, int *odata, const int *idata) { +__global__ void kernUpSweep(int n, int offset, int *buf) { int index = threadIdx.x + (blockIdx.x * blockDim.x); - odata[index + offset - 1] += + int idx = (index + 1) * (offset * 2) - 1; + if (idx >= n) return; + //if ((index + 1) % (offset * 2) == 0) return; + + buf[idx] += buf[idx - offset]; + //buf[index] += buf[index - offset]; } -__global__ void kernDownSweep(int n, int offset, int *odata, const int *idata) { +__global__ void kernDownSweep(int n, int offset, int *buf) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + int idx = (index + 1) * (offset * 2) - 1; + if (idx >= n) return; + int t = buf[idx - offset]; + buf[idx - offset] = buf[idx]; + buf[idx] += t; } @@ -24,19 +35,26 @@ __global__ void kernDownSweep(int n, int offset, int *odata, const int *idata) { */ void scan(int n, int *odata, const int *idata) { dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); - int *buf; - cudaMalloc((void**)&buf, n * sizeof(int)); + int padded = 1 << ilog2ceil(n); + + cudaMalloc((void**)&buf, padded * sizeof(int)); checkCUDAError("cudaMalloc buf failed!"); + cudaMemcpy(buf, idata, padded * sizeof(int), cudaMemcpyHostToDevice); + int offset; - for (int i = 0; i <= ilog2(n); i++) { - kernUpSweep << > >(n, offset, odata, idata); + for (int i = 0; i <= ilog2(padded); i++) { + kernUpSweep << > >(padded, 1 << i, buf); } - for (int i = ilog2(n); i <= 0; i--) { - kernDownSweep << > >(n, offset, odata, idata); + + cudaMemset(buf + padded - 1, 0, sizeof(int)); + for (int i = ilog2(padded); i >= 0; i--) { + kernDownSweep << > >(padded, 1 << i, buf); } + cudaMemcpy(odata, buf, padded * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(buf); } @@ -50,8 +68,29 @@ 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; + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + int *bools, *indices, *in, *out; + + cudaMalloc((void**)&bools, n * sizeof(int)); + cudaMalloc((void**)&indices, n * sizeof(int)); + cudaMalloc((void**)&in, n * sizeof(int)); + cudaMalloc((void**)&out, n * sizeof(int)); + + cudaMemcpy(in, idata, n * sizeof(int), cudaMemcpyHostToDevice); + StreamCompaction::Common::kernMapToBoolean << > >(n, bools, in); + cudaMemcpy(odata, bools, n * sizeof(int), cudaMemcpyDeviceToHost); + scan(n, odata, odata); + int lenCompacted = odata[n - 1]; + cudaMemcpy(indices, odata, n * sizeof(int), cudaMemcpyHostToDevice); + StreamCompaction::Common::kernScatter << > >(n, out, in, bools, indices); + cudaMemcpy(odata, out, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(bools); + cudaFree(indices); + cudaFree(in); + cudaFree(out); + + return lenCompacted; } } From b839c2ec8265d61ec83659d7770fb3a7be7d7ab6 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Mon, 26 Sep 2016 02:16:12 -0400 Subject: [PATCH 04/21] thrust finished --- stream_compaction/thrust.cu | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index d8dbb32..f09c70c 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -16,6 +16,10 @@ 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 thrust_idata(idata, idata + n); + thrust::device_vector thrust_odata(odata, odata + n); + thrust::exclusive_scan(thrust_idata.begin(), thrust_idata.end(), thrust_odata.begin()); + thrust::copy(thrust_odata.begin(), thrust_odata.end(), odata); } } From b0495436701c31588eabf7d485e644882ccd7832 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Mon, 26 Sep 2016 20:00:33 -0400 Subject: [PATCH 05/21] timings --- src/main.cpp | 244 +++++++++++++++++++++------------------ stream_compaction/cpu.cu | 9 +- 2 files changed, 141 insertions(+), 112 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index eb0ca3a..ca03e84 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,120 +12,142 @@ #include #include #include "testing_helpers.hpp" +#include +using namespace std::chrono; +#define RUNS 10 + +void runTimings() { + const int SIZE = 1 << 25; + int *a = new int[SIZE]; + int *b = new int[SIZE]; + high_resolution_clock::time_point start, end; + for (int i = 0; i < RUNS; i++) { + start = high_resolution_clock::now(); + StreamCompaction::CPU::scan(SIZE, b, a); + end = high_resolution_clock::now(); + duration duration = end - start; + printf("%f\n", duration.count() * 1000.0f); + } +} +void runTests() { + const int SIZE = 1 << 8; + const int NPOT = SIZE - 3; + int *a, *b, *c; + a = new int[SIZE]; + b = new int[SIZE]; + c = new int[SIZE]; + + // Scan tests + + printf("\n"); + printf("****************\n"); + printf("** SCAN TESTS **\n"); + printf("****************\n"); + + genArray(SIZE - 1, a, 50); // Leave a 0 at the end to test that edge case + a[SIZE - 1] = 0; + printArray(SIZE, a, true); + + zeroArray(SIZE, b); + printDesc("cpu scan, power-of-two"); + StreamCompaction::CPU::scan(SIZE, b, a); + //printArray(SIZE, b, true); + //printf("%lf\n", Timer::getCPUTiming("cpu_scan")); + + zeroArray(SIZE, c); + printDesc("cpu scan, non-power-of-two"); + StreamCompaction::CPU::scan(NPOT, c, a); + //printArray(NPOT, b, true); + printCmpResult(NPOT, b, c); + + zeroArray(SIZE, c); + printDesc("naive scan, power-of-two"); + StreamCompaction::Naive::scan(SIZE, c, a); + printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); + + zeroArray(SIZE, c); + printDesc("naive scan, non-power-of-two"); + StreamCompaction::Naive::scan(NPOT, c, a); + printArray(SIZE, c, true); + printCmpResult(NPOT, b, c); + + zeroArray(SIZE, c); + printDesc("work-efficient scan, power-of-two"); + StreamCompaction::Efficient::scan(SIZE, c, a); + printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); + + zeroArray(SIZE, c); + printDesc("work-efficient scan, non-power-of-two"); + StreamCompaction::Efficient::scan(NPOT, c, a); + printArray(NPOT, c, true); + printCmpResult(NPOT, b, c); + + zeroArray(SIZE, c); + printDesc("thrust scan, power-of-two"); + StreamCompaction::Thrust::scan(SIZE, c, a); + //printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); + + zeroArray(SIZE, c); + printDesc("thrust scan, non-power-of-two"); + StreamCompaction::Thrust::scan(NPOT, c, a); + //printArray(NPOT, c, true); + printCmpResult(NPOT, b, c); + + printf("\n"); + printf("*****************************\n"); + printf("** STREAM COMPACTION TESTS **\n"); + printf("*****************************\n"); + + // Compaction tests + + genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case + a[SIZE - 1] = 0; + printArray(SIZE, a, true); + + int count, expectedCount, expectedNPOT; + + zeroArray(SIZE, b); + printDesc("cpu compact without scan, power-of-two"); + count = StreamCompaction::CPU::compactWithoutScan(SIZE, b, a); + expectedCount = count; + printArray(count, b, true); + printCmpLenResult(count, expectedCount, b, b); + + zeroArray(SIZE, c); + printDesc("cpu compact without scan, non-power-of-two"); + count = StreamCompaction::CPU::compactWithoutScan(NPOT, c, a); + expectedNPOT = count; + printArray(count, c, true); + printCmpLenResult(count, expectedNPOT, b, c); + + zeroArray(SIZE, c); + printDesc("cpu compact with scan"); + count = StreamCompaction::CPU::compactWithScan(SIZE, c, a); + printArray(count, c, true); + printCmpLenResult(count, expectedCount, b, c); + + zeroArray(SIZE, c); + printDesc("work-efficient compact, power-of-two"); + count = StreamCompaction::Efficient::compact(SIZE, c, a); + //printArray(count, c, true); + printCmpLenResult(count, expectedCount, b, c); + + zeroArray(SIZE, c); + printDesc("work-efficient compact, non-power-of-two"); + count = StreamCompaction::Efficient::compact(NPOT, c, a); + //printArray(count, c, true); + printCmpLenResult(count, expectedNPOT, b, c); +} int main(int argc, char* argv[]) { - const int SIZE = 1 << 8; - const int NPOT = SIZE - 3; - int a[SIZE], b[SIZE], c[SIZE]; - - - // Scan tests - - printf("\n"); - printf("****************\n"); - printf("** SCAN TESTS **\n"); - printf("****************\n"); - - genArray(SIZE - 1, a, 50); // Leave a 0 at the end to test that edge case - a[SIZE - 1] = 0; - printArray(SIZE, a, true); - - int d[] = { 0, 1, 2, 3, 4, 5}; - int dexp[] = { 0, 0, 1, 3, 6, 10}; - int e[] = { 0, 1, 2, 6, 4, 9, 6, 28 }; - - zeroArray(SIZE, b); - printDesc("cpu scan, power-of-two"); - StreamCompaction::CPU::scan(SIZE, b, a); - printArray(SIZE, b, true); - - zeroArray(SIZE, c); - printDesc("cpu scan, non-power-of-two"); - StreamCompaction::CPU::scan(NPOT, c, a); - printArray(NPOT, b, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("naive scan, power-of-two"); - StreamCompaction::Naive::scan(SIZE, c, a); - printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - zeroArray(SIZE, c); - printDesc("naive scan, non-power-of-two"); - StreamCompaction::Naive::scan(NPOT, c, a); - printArray(SIZE, c, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient scan, power-of-two"); - StreamCompaction::Efficient::scan(SIZE, c, a); - printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient scan, non-power-of-two"); - StreamCompaction::Efficient::scan(NPOT, c, a); - printArray(NPOT, c, true); - printCmpResult(NPOT, b, c); - - zeroArray(SIZE, c); - printDesc("thrust scan, power-of-two"); - StreamCompaction::Thrust::scan(SIZE, c, a); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); - - zeroArray(SIZE, c); - printDesc("thrust scan, non-power-of-two"); - StreamCompaction::Thrust::scan(NPOT, c, a); - //printArray(NPOT, c, true); - printCmpResult(NPOT, b, c); - - printf("\n"); - printf("*****************************\n"); - printf("** STREAM COMPACTION TESTS **\n"); - printf("*****************************\n"); - - // Compaction tests - - genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case - a[SIZE - 1] = 0; - printArray(SIZE, a, true); - - int count, expectedCount, expectedNPOT; - - zeroArray(SIZE, b); - printDesc("cpu compact without scan, power-of-two"); - count = StreamCompaction::CPU::compactWithoutScan(SIZE, b, a); - expectedCount = count; - printArray(count, b, true); - printCmpLenResult(count, expectedCount, b, b); - - zeroArray(SIZE, c); - printDesc("cpu compact without scan, non-power-of-two"); - count = StreamCompaction::CPU::compactWithoutScan(NPOT, c, a); - expectedNPOT = count; - printArray(count, c, true); - printCmpLenResult(count, expectedNPOT, b, c); - - zeroArray(SIZE, c); - printDesc("cpu compact with scan"); - count = StreamCompaction::CPU::compactWithScan(SIZE, c, a); - printArray(count, c, true); - printCmpLenResult(count, expectedCount, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient compact, power-of-two"); - count = StreamCompaction::Efficient::compact(SIZE, c, a); - //printArray(count, c, true); - printCmpLenResult(count, expectedCount, b, c); - - zeroArray(SIZE, c); - printDesc("work-efficient compact, non-power-of-two"); - count = StreamCompaction::Efficient::compact(NPOT, c, a); - //printArray(count, c, true); - printCmpLenResult(count, expectedNPOT, b, c); + runTests(); + runTimings(); + int i = 0; scanf("%d", i); + } diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index cd9ac25..78c9c1c 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -1,5 +1,6 @@ #include #include "cpu.h" +#include "timer.h" namespace StreamCompaction { namespace CPU { @@ -15,6 +16,7 @@ void scan(int n, int *odata, const int *idata) { for (int i = 1; i < n; i++) { odata[i] = odata[i - 1] + idata[i - 1]; } + } /** @@ -39,11 +41,16 @@ int compactWithoutScan(int n, int *odata, const int *idata) { */ int compactWithScan(int n, int *odata, const int *idata) { int *scanResults = new int[n]; - + + // mapping boolean function for (int i = 0; i < n; i++) { odata[i] = idata[i] != 0; } + + //scan scan(n, scanResults, odata); + + //compaction int k = 0; for (int i = 0; i < n; i++) { if (idata[i]) { From eca769c7f1da6fe808bbe59be7aa9d13d9e35b84 Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 03:27:52 -0400 Subject: [PATCH 06/21] Update README.md --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b71c458..3db99a7 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,21 @@ CUDA Stream Compaction **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* David Liao +* Tested on: Windows 7 Professional, Intel(R) Xeon(R) CPU E5-1630 v4 @ 3.70 GHz 3.70 GHz, GTX 1070 8192MB (SIG Lab) -### (TODO: Your README) +### Analysis +#### Overall +All across the board, it appears that the CPU implementation lags behind all GPU implementations. The naive implementation trails behind the CPU implementation and the efficient GPU implementation beats the naive algorithm. Out of all 4 implementations, the Thrust implementation appears to be the fastest. It scales much better than the rest when given much larger arrays. -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +#### CPU +The cpu algorithm is a serialized sum iterated across the array. It is limited in computation by the fact that it is serial in nature. +#### Naive Implementation +The naive implementation is the initial parallelized scan that does not take into account the access-sensitive nature of its computation. Thus it suffers from mostly the fact that it has to deal with two memory buffers (to swap between). It does however beat the Work-Efficient algorithm on smaller data-sets. This might be due to the fact that less kernal invocations are called, thus less memory transfers between CPU and GPU occurs and more global memory accesses. + +#### Work-Efficient Implementation +This is an upgraded algorithm that allows in place computation without the extra memory buffer. This helps speed things up greatly. The initial implementation launched n threads for every iteration of the loop when the vast majority of them in the first few iterations will not be doing anything. This caused the Work-Efficient implementation to actually perform worse than the naive implementation. Instead, a smarter implementation would only launch as many threads as needed for that iteration. This is calculated using a bitshift counter to indicate the number of threads to launch in the kernel invocation. + +#### Thrust +Thrust is the fastest implementation out of all 4. From NSight, it appears to used shared memory and is very efficient as compared to using global memory in our case which is very expensive. A quick search through thrust's git repo shows that they have multiple versions of scan that works on partitioned memory per warp or block. They also have an implementation working on global memory. Algorithm-wise, they appear to be using a similar up/down sweep algorithm. They also seem to use pick their block size very carefully based on empirical testing. Here's a link[https://github.com/thrust/thrust/blob/2ef13096187b40a35a71451d09e49b14074b0859/thrust/system/cuda/detail/scan.inl] to their repo. From 24c6418ae73e2c08b3fef6e717fca3bf5dda4f1e Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 03:28:47 -0400 Subject: [PATCH 07/21] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3db99a7..751fb6e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ CUDA Stream Compaction * Tested on: Windows 7 Professional, Intel(R) Xeon(R) CPU E5-1630 v4 @ 3.70 GHz 3.70 GHz, GTX 1070 8192MB (SIG Lab) ### Analysis + +![chart](images/chart.png) #### Overall All across the board, it appears that the CPU implementation lags behind all GPU implementations. The naive implementation trails behind the CPU implementation and the efficient GPU implementation beats the naive algorithm. Out of all 4 implementations, the Thrust implementation appears to be the fastest. It scales much better than the rest when given much larger arrays. @@ -20,4 +22,4 @@ The naive implementation is the initial parallelized scan that does not take int This is an upgraded algorithm that allows in place computation without the extra memory buffer. This helps speed things up greatly. The initial implementation launched n threads for every iteration of the loop when the vast majority of them in the first few iterations will not be doing anything. This caused the Work-Efficient implementation to actually perform worse than the naive implementation. Instead, a smarter implementation would only launch as many threads as needed for that iteration. This is calculated using a bitshift counter to indicate the number of threads to launch in the kernel invocation. #### Thrust -Thrust is the fastest implementation out of all 4. From NSight, it appears to used shared memory and is very efficient as compared to using global memory in our case which is very expensive. A quick search through thrust's git repo shows that they have multiple versions of scan that works on partitioned memory per warp or block. They also have an implementation working on global memory. Algorithm-wise, they appear to be using a similar up/down sweep algorithm. They also seem to use pick their block size very carefully based on empirical testing. Here's a link[https://github.com/thrust/thrust/blob/2ef13096187b40a35a71451d09e49b14074b0859/thrust/system/cuda/detail/scan.inl] to their repo. +Thrust is the fastest implementation out of all 4. From NSight, it appears to used shared memory and is very efficient as compared to using global memory in our case which is very expensive. A quick search through thrust's git repo shows that they have multiple versions of scan that works on partitioned memory per warp or block. They also have an implementation working on global memory. Algorithm-wise, they appear to be using a similar up/down sweep algorithm. They also seem to use pick their block size very carefully based on empirical testing. Here's a [link](https://github.com/thrust/thrust/blob/2ef13096187b40a35a71451d09e49b14074b0859/thrust/system/cuda/detail/scan.inl) to their repo. From ee63fe23983c0444cf4a5101c1126108f767b6ed Mon Sep 17 00:00:00 2001 From: David Date: Tue, 27 Sep 2016 03:30:48 -0400 Subject: [PATCH 08/21] image --- images/chart.png | Bin 0 -> 21388 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/chart.png diff --git a/images/chart.png b/images/chart.png new file mode 100644 index 0000000000000000000000000000000000000000..dcbe5f323c17d8412b095847cf8e412514b0444e GIT binary patch literal 21388 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 literal 0 HcmV?d00001 From 8ffa786744917d09f621b7bc7d0ba43e97467ed3 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Tue, 27 Sep 2016 17:33:01 -0400 Subject: [PATCH 09/21] timings --- src/main.cpp | 22 +++++++++++++++----- stream_compaction/cpu.cu | 1 - stream_compaction/efficient.cu | 36 ++++++++++++++++++++++++++++++++ stream_compaction/naive.cu | 38 +++++++++++++++++++++------------- 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ca03e84..883d29b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,19 +14,31 @@ #include "testing_helpers.hpp" #include using namespace std::chrono; -#define RUNS 10 +#define RUNS 1 void runTimings() { - const int SIZE = 1 << 25; - int *a = new int[SIZE]; - int *b = new int[SIZE]; + high_resolution_clock::time_point start, end; - for (int i = 0; i < RUNS; i++) { + for (int i = 15; i <= 29; i+=2) { + const int SIZE = 1 << i; + int *a = new int[SIZE]; + int *b = new int[SIZE]; + zeroArray(SIZE, a); + zeroArray(SIZE, b); + start = high_resolution_clock::now(); StreamCompaction::CPU::scan(SIZE, b, a); end = high_resolution_clock::now(); duration duration = end - start; printf("%f\n", duration.count() * 1000.0f); + + StreamCompaction::Naive::scan(SIZE, b, a); + + + delete a; + delete b; + + } } diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 78c9c1c..56b7313 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -1,6 +1,5 @@ #include #include "cpu.h" -#include "timer.h" namespace StreamCompaction { namespace CPU { diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 4b88ff2..2b1d704 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -43,6 +43,11 @@ void scan(int n, int *odata, const int *idata) { cudaMemcpy(buf, idata, padded * sizeof(int), cudaMemcpyHostToDevice); + cudaEvent_t start, end; + cudaEventCreate(&start); + cudaEventCreate(&end); + cudaEventRecord(start); + int offset; for (int i = 0; i <= ilog2(padded); i++) { kernUpSweep << > >(padded, 1 << i, buf); @@ -53,6 +58,12 @@ void scan(int n, int *odata, const int *idata) { kernDownSweep << > >(padded, 1 << i, buf); } + cudaEventRecord(end); + cudaEventSynchronize(end); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, end); + printf("Work-Efficient scan: %f ms\n", milliseconds); + cudaMemcpy(odata, buf, padded * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(buf); @@ -77,12 +88,37 @@ int compact(int n, int *odata, const int *idata) { cudaMalloc((void**)&out, n * sizeof(int)); cudaMemcpy(in, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + float total = 0; + float milliseconds = 0; + cudaEvent_t start, end; + cudaEventCreate(&start); + cudaEventCreate(&end); + cudaEventRecord(start); + StreamCompaction::Common::kernMapToBoolean << > >(n, bools, in); + + cudaEventRecord(end); + cudaEventSynchronize(end); + cudaEventElapsedTime(&milliseconds, start, end); + total += milliseconds; + cudaMemcpy(odata, bools, n * sizeof(int), cudaMemcpyDeviceToHost); scan(n, odata, odata); int lenCompacted = odata[n - 1]; cudaMemcpy(indices, odata, n * sizeof(int), cudaMemcpyHostToDevice); + + cudaEventCreate(&start); + cudaEventCreate(&end); + cudaEventRecord(start); + StreamCompaction::Common::kernScatter << > >(n, out, in, bools, indices); + + cudaEventRecord(end); + cudaEventSynchronize(end); + cudaEventElapsedTime(&milliseconds, start, end); + total += milliseconds; + printf("Work-Efficient Compact: %f ms\n", total); cudaMemcpy(odata, out, n * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(bools); diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 403cfae..165bec4 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -12,14 +12,14 @@ namespace Naive { // TODO: __global__ -__global__ void kernelScan(int offset, int n, int *swapA, int *swapB) { +__global__ void kernelScan(int offset, int n, int *dev_odata, int *dev_idata) { int index = threadIdx.x + (blockIdx.x * blockDim.x); if (index >= n) return; if (index < offset) { - swapB[index] = swapA[index]; + dev_idata[index] = dev_odata[index]; } else { - swapB[index] = swapA[index - offset] + swapA[index]; + dev_idata[index] = dev_odata[index - offset] + dev_odata[index]; } } /** @@ -28,25 +28,35 @@ __global__ void kernelScan(int offset, int n, int *swapA, int *swapB) { void scan(int n, int *odata, const int *idata) { dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); int offset; - int *swapA, *swapB; - cudaMalloc((void**)&swapA, n * sizeof(int)); - checkCUDAError("cudaMalloc swapA failed!"); - cudaMalloc((void**)&swapB, n * sizeof(int)); - checkCUDAError("cudaMalloc swapB failed!"); + int *dev_odata, *dev_idata; + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + cudaMalloc((void**)&dev_idata, n * sizeof(int)); - cudaMemcpy(swapA, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy(dev_odata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaEvent_t start, end; + cudaEventCreate(&start); + cudaEventCreate(&end); + cudaEventRecord(start); + int log2n = ilog2ceil(n); for (int i = 1; i <= log2n; i++) { offset = 1 << (i - 1); - kernelScan << > >(offset, n, swapA, swapB); - std::swap(swapA, swapB); + kernelScan << > >(offset, n, dev_odata, dev_idata); + std::swap(dev_odata, dev_idata); } - cudaMemcpy(odata + 1, swapA, (n - 1) * sizeof(int), cudaMemcpyDeviceToHost); + + cudaEventRecord(end); + cudaEventSynchronize(end); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, end); + printf("Naive scan: %f ms\n", milliseconds); + + cudaMemcpy(odata + 1, dev_odata, (n - 1) * sizeof(int), cudaMemcpyDeviceToHost); odata[0] = 0; - cudaFree(swapA); - cudaFree(swapB); + cudaFree(dev_odata); + cudaFree(dev_idata); } } From d06cd1d707375a3f2ee84fcd716e18e64b23f9ad Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 17:39:18 -0400 Subject: [PATCH 10/21] Add files via upload --- images/testspassing.png | Bin 0 -> 25261 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/testspassing.png diff --git a/images/testspassing.png b/images/testspassing.png new file mode 100644 index 0000000000000000000000000000000000000000..504277ff861f835e468035ef6696e95ae1f28c20 GIT binary patch literal 25261 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

ef|}WuI6ekGLIu#PqmoaSjh!3*6!{8FIqoF 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

DS?*+o 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 literal 0 HcmV?d00001 From d44c2ce8e8118acbb736549073c687260269ae94 Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 17:40:11 -0400 Subject: [PATCH 11/21] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 751fb6e..2681771 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,6 @@ This is an upgraded algorithm that allows in place computation without the extra #### Thrust Thrust is the fastest implementation out of all 4. From NSight, it appears to used shared memory and is very efficient as compared to using global memory in our case which is very expensive. A quick search through thrust's git repo shows that they have multiple versions of scan that works on partitioned memory per warp or block. They also have an implementation working on global memory. Algorithm-wise, they appear to be using a similar up/down sweep algorithm. They also seem to use pick their block size very carefully based on empirical testing. Here's a [link](https://github.com/thrust/thrust/blob/2ef13096187b40a35a71451d09e49b14074b0859/thrust/system/cuda/detail/scan.inl) to their repo. + +### Test Results +![tests](images/testspassing.png) From ce7f0246063203a0d8b9e5215b0c11bb4ad985f0 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Tue, 27 Sep 2016 18:02:51 -0400 Subject: [PATCH 12/21] running updated work efficient alg --- src/main.cpp | 7 ++++--- stream_compaction/efficient.cu | 38 +++++++++++++++------------------- stream_compaction/thrust.cu | 11 ++++++++++ 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 883d29b..6741c87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,7 @@ using namespace std::chrono; void runTimings() { high_resolution_clock::time_point start, end; - for (int i = 15; i <= 29; i+=2) { + for (int i = 15; i <= 27; i+=2) { const int SIZE = 1 << i; int *a = new int[SIZE]; int *b = new int[SIZE]; @@ -30,10 +30,11 @@ void runTimings() { StreamCompaction::CPU::scan(SIZE, b, a); end = high_resolution_clock::now(); duration duration = end - start; - printf("%f\n", duration.count() * 1000.0f); + //printf("CPU scan: %f ms\n", duration.count() * 1000.0f); StreamCompaction::Naive::scan(SIZE, b, a); - + //StreamCompaction::Efficient::scan(SIZE, b, a); + //StreamCompaction::Thrust::scan(SIZE, b, a); delete a; delete b; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 2b1d704..f9f0231 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -11,22 +11,18 @@ namespace Efficient { __global__ void kernUpSweep(int n, int offset, int *buf) { int index = threadIdx.x + (blockIdx.x * blockDim.x); - int idx = (index + 1) * (offset * 2) - 1; - if (idx >= n) return; - //if ((index + 1) % (offset * 2) == 0) return; - - buf[idx] += buf[idx - offset]; - //buf[index] += buf[index - offset]; + if (index >= (n >> offset)) return; + int idx = index << offset; + buf[idx + (1 << offset) - 1] += buf[idx + (1 << (offset - 1)) - 1]; } __global__ void kernDownSweep(int n, int offset, int *buf) { int index = threadIdx.x + (blockIdx.x * blockDim.x); - int idx = (index + 1) * (offset * 2) - 1; - if (idx >= n) return; - - int t = buf[idx - offset]; - buf[idx - offset] = buf[idx]; - buf[idx] += t; + if (index >= (n >> offset)) return; + int idx = index << offset; + int t = buf[idx + (1 << offset) - 1]; + buf[idx + (1 << offset) - 1] += buf[idx + (1 << (offset - 1)) - 1]; + buf[idx + (1 << (offset - 1)) - 1] = t; } @@ -34,14 +30,12 @@ __global__ void kernDownSweep(int n, int offset, int *buf) { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + int *buf; int padded = 1 << ilog2ceil(n); cudaMalloc((void**)&buf, padded * sizeof(int)); - checkCUDAError("cudaMalloc buf failed!"); - - cudaMemcpy(buf, idata, padded * sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy(buf, idata, n * sizeof(int), cudaMemcpyHostToDevice); cudaEvent_t start, end; cudaEventCreate(&start); @@ -49,13 +43,15 @@ void scan(int n, int *odata, const int *idata) { cudaEventRecord(start); int offset; - for (int i = 0; i <= ilog2(padded); i++) { - kernUpSweep << > >(padded, 1 << i, buf); + for (int i = 1; i <= ilog2(padded); i++) { + dim3 fullBlocksPerGrid(((padded >> i) + blockSize - 1) / blockSize); + kernUpSweep << > >(padded, i, buf); } cudaMemset(buf + padded - 1, 0, sizeof(int)); - for (int i = ilog2(padded); i >= 0; i--) { - kernDownSweep << > >(padded, 1 << i, buf); + for (int i = ilog2(padded); i >= 1; i--) { + dim3 fullBlocksPerGrid(((padded >> i) + blockSize - 1) / blockSize); + kernDownSweep << > >(padded, i, buf); } cudaEventRecord(end); @@ -64,7 +60,7 @@ void scan(int n, int *odata, const int *idata) { cudaEventElapsedTime(&milliseconds, start, end); printf("Work-Efficient scan: %f ms\n", milliseconds); - cudaMemcpy(odata, buf, padded * sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(odata, buf, n * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(buf); } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index f09c70c..913e24d 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -16,10 +16,21 @@ 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()); + cudaEvent_t start, end; + cudaEventCreate(&start); + cudaEventCreate(&end); + cudaEventRecord(start); + thrust::device_vector thrust_idata(idata, idata + n); thrust::device_vector thrust_odata(odata, odata + n); thrust::exclusive_scan(thrust_idata.begin(), thrust_idata.end(), thrust_odata.begin()); thrust::copy(thrust_odata.begin(), thrust_odata.end(), odata); + + cudaEventRecord(end); + cudaEventSynchronize(end); + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, end); + printf("Thrust scan: %f ms\n", milliseconds); } } From 5b09a7ae12a3fa271a79bb2997835b8bc11db33a Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Tue, 27 Sep 2016 18:15:38 -0400 Subject: [PATCH 13/21] timings --- src/main.cpp | 6 +++--- stream_compaction/efficient.cu | 23 +++++++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6741c87..79cdda6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,11 +30,11 @@ void runTimings() { StreamCompaction::CPU::scan(SIZE, b, a); end = high_resolution_clock::now(); duration duration = end - start; - //printf("CPU scan: %f ms\n", duration.count() * 1000.0f); + printf("CPU scan: %f ms\n", duration.count() * 1000.0f); StreamCompaction::Naive::scan(SIZE, b, a); - //StreamCompaction::Efficient::scan(SIZE, b, a); - //StreamCompaction::Thrust::scan(SIZE, b, a); + StreamCompaction::Efficient::scan(SIZE, b, a); + StreamCompaction::Thrust::scan(SIZE, b, a); delete a; delete b; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index f9f0231..682d68d 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -37,28 +37,39 @@ void scan(int n, int *odata, const int *idata) { cudaMalloc((void**)&buf, padded * sizeof(int)); cudaMemcpy(buf, idata, n * sizeof(int), cudaMemcpyHostToDevice); + int offset; + int fullBlocksPerGrid = 0; + float total = 0; + float milliseconds = 0; + cudaEvent_t start, end; cudaEventCreate(&start); cudaEventCreate(&end); cudaEventRecord(start); - - int offset; for (int i = 1; i <= ilog2(padded); i++) { - dim3 fullBlocksPerGrid(((padded >> i) + blockSize - 1) / blockSize); + fullBlocksPerGrid = ((padded >> i) + blockSize - 1) / blockSize; kernUpSweep << > >(padded, i, buf); } + cudaEventRecord(end); + cudaEventSynchronize(end); + cudaEventElapsedTime(&milliseconds, start, end); + total += milliseconds; cudaMemset(buf + padded - 1, 0, sizeof(int)); + + cudaEventCreate(&start); + cudaEventCreate(&end); + cudaEventRecord(start); for (int i = ilog2(padded); i >= 1; i--) { - dim3 fullBlocksPerGrid(((padded >> i) + blockSize - 1) / blockSize); + fullBlocksPerGrid = ((padded >> i) + blockSize - 1) / blockSize; kernDownSweep << > >(padded, i, buf); } cudaEventRecord(end); cudaEventSynchronize(end); - float milliseconds = 0; cudaEventElapsedTime(&milliseconds, start, end); - printf("Work-Efficient scan: %f ms\n", milliseconds); + total += milliseconds; + printf("Work-Efficient scan: %f ms\n", total); cudaMemcpy(odata, buf, n * sizeof(int), cudaMemcpyDeviceToHost); From 0eb734cd478d85316e620b293ae3fe6419ce22da Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 18:17:58 -0400 Subject: [PATCH 14/21] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2681771..b9f0c66 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ CUDA Stream Compaction * David Liao * Tested on: Windows 7 Professional, Intel(R) Xeon(R) CPU E5-1630 v4 @ 3.70 GHz 3.70 GHz, GTX 1070 8192MB (SIG Lab) +* Also Tested on: Windows 7 Professional, Intel(R) i7 4770 CPU @ 3.4GHz, Quadro K600 ### Analysis From 84ced557b861fd1a949861684435f7239ac57572 Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 18:19:39 -0400 Subject: [PATCH 15/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9f0c66..d5ddd6c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ CUDA Stream Compaction * David Liao * Tested on: Windows 7 Professional, Intel(R) Xeon(R) CPU E5-1630 v4 @ 3.70 GHz 3.70 GHz, GTX 1070 8192MB (SIG Lab) -* Also Tested on: Windows 7 Professional, Intel(R) i7 4770 CPU @ 3.4GHz, Quadro K600 +* Also Tested on: Windows 7 Professional, Intel(R) i7 4770 CPU @ 3.4GHz, Quadro K600 (SIG Lab) ### Analysis From 007aea7d78af0dc2a011e4b9c2463b3837bedd7a Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 18:21:48 -0400 Subject: [PATCH 16/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5ddd6c..bf24f30 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The cpu algorithm is a serialized sum iterated across the array. It is limited i The naive implementation is the initial parallelized scan that does not take into account the access-sensitive nature of its computation. Thus it suffers from mostly the fact that it has to deal with two memory buffers (to swap between). It does however beat the Work-Efficient algorithm on smaller data-sets. This might be due to the fact that less kernal invocations are called, thus less memory transfers between CPU and GPU occurs and more global memory accesses. #### Work-Efficient Implementation -This is an upgraded algorithm that allows in place computation without the extra memory buffer. This helps speed things up greatly. The initial implementation launched n threads for every iteration of the loop when the vast majority of them in the first few iterations will not be doing anything. This caused the Work-Efficient implementation to actually perform worse than the naive implementation. Instead, a smarter implementation would only launch as many threads as needed for that iteration. This is calculated using a bitshift counter to indicate the number of threads to launch in the kernel invocation. +This is an upgraded algorithm that allows in place computation without the extra memory buffer. This helps speed things up greatly. The initial implementation launched n threads for every iteration of the loop when the vast majority of them in the first few iterations will not be doing anything. This caused the Work-Efficient implementation to actually perform worse than the naive implementation. Instead, a smarter implementation would only launch as many threads as needed for that iteration. This is calculated using a bitshift counter to indicate the number of threads to launch in the kernel invocation. I could not get timing improvements for this as anything past 1 << 13 using the inefficient approach would crash the machine (Quadro K600) I was working on. But after I implemented the hack for launching the correct number of threads, everything ran smoothly. #### Thrust Thrust is the fastest implementation out of all 4. From NSight, it appears to used shared memory and is very efficient as compared to using global memory in our case which is very expensive. A quick search through thrust's git repo shows that they have multiple versions of scan that works on partitioned memory per warp or block. They also have an implementation working on global memory. Algorithm-wise, they appear to be using a similar up/down sweep algorithm. They also seem to use pick their block size very carefully based on empirical testing. Here's a [link](https://github.com/thrust/thrust/blob/2ef13096187b40a35a71451d09e49b14074b0859/thrust/system/cuda/detail/scan.inl) to their repo. From 5b5ac9d3ee359d72d34f50afc7f8e225123a3195 Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 20:26:22 -0400 Subject: [PATCH 17/21] Add files via upload --- images/nsight_efficient.PNG | Bin 0 -> 119066 bytes images/nsight_thrust.PNG | Bin 0 -> 35331 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/nsight_efficient.PNG create mode 100644 images/nsight_thrust.PNG diff --git a/images/nsight_efficient.PNG b/images/nsight_efficient.PNG new file mode 100644 index 0000000000000000000000000000000000000000..aa81871e125e79152d7c588593bda22d1a9b62cd GIT binary patch literal 119066 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

|OzeC1^OkC+pRoJt92KqR~3hJhDFMt!iHI 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%0D86L95 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 Date: Tue, 27 Sep 2016 20:27:43 -0400 Subject: [PATCH 18/21] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index bf24f30..9c6927b 100644 --- a/README.md +++ b/README.md @@ -25,5 +25,12 @@ This is an upgraded algorithm that allows in place computation without the extra #### Thrust Thrust is the fastest implementation out of all 4. From NSight, it appears to used shared memory and is very efficient as compared to using global memory in our case which is very expensive. A quick search through thrust's git repo shows that they have multiple versions of scan that works on partitioned memory per warp or block. They also have an implementation working on global memory. Algorithm-wise, they appear to be using a similar up/down sweep algorithm. They also seem to use pick their block size very carefully based on empirical testing. Here's a [link](https://github.com/thrust/thrust/blob/2ef13096187b40a35a71451d09e49b14074b0859/thrust/system/cuda/detail/scan.inl) to their repo. +### Nsight Results +#### Thrust +![thrust](images/nsight_thrust.png) + +#### Work Efficient Implementation +![work efficient](images/nsight_efficient.png) + ### Test Results ![tests](images/testspassing.png) From 2d78b4c52bad2b4c92d4a51eb70e75033659543f Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 20:28:11 -0400 Subject: [PATCH 19/21] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c6927b..38c06f5 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,10 @@ Thrust is the fastest implementation out of all 4. From NSight, it appears to us ### Nsight Results #### Thrust -![thrust](images/nsight_thrust.png) +![thrust](images/nsight_thrust.PNG) #### Work Efficient Implementation -![work efficient](images/nsight_efficient.png) +![work efficient](images/nsight_efficient.PNG) ### Test Results ![tests](images/testspassing.png) From 955c98b9cbd313c4df80793df6361ecd7df460b6 Mon Sep 17 00:00:00 2001 From: David Liao Date: Tue, 27 Sep 2016 20:38:18 -0400 Subject: [PATCH 20/21] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38c06f5..7e86f8e 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,16 @@ Thrust is the fastest implementation out of all 4. From NSight, it appears to us ### Nsight Results #### Thrust +Row 1: Accumulate Tiles +Row 2: Exclusive Scan +Row 3: Downsweep ![thrust](images/nsight_thrust.PNG) - #### Work Efficient Implementation ![work efficient](images/nsight_efficient.PNG) +#### Discussion +This is obtained on a sample input of 2^15 sized array. +Here we see thrust explicitly using dynamic shared memory. It has very efficient timings and high occupancy for Accumulate Tiles (as well as low register usage), but lower occupancy (and higher register usage) for the other two invocations. I think this is because it manages to schedule the sweeping functions in a way such that it only needs to launch a single kernel. It uses one launch for every operation as compared to the Work Efficient implementation where we see it launching 128, 64, 32, etc. kernels for each iteration of the loop. There's a lot of latency in-between the kernel launches. Despite this, each kernel invocation has a perfect occupancy percentage (yay! this means that our counting hack worked) and very low register usage. However, because it is sequential in launching the downsweep iterations and then the upsweep iterations, we take a hit in runtime. This on top shared memory usage makes thrust very fast. If we can maintain our high occupancy and low register usage, but manage to squeeze all of the kernal launches into a single one, we would be in great shape. + ### Test Results ![tests](images/testspassing.png) From 0d053642fd5bf014ba79919840d0074a200746e2 Mon Sep 17 00:00:00 2001 From: DAVID LIAO Date: Tue, 27 Sep 2016 20:39:29 -0400 Subject: [PATCH 21/21] finished and polished --- src/main.cpp | 8 ++------ stream_compaction/thrust.cu | 9 ++++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 79cdda6..1e2dd0b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,7 @@ using namespace std::chrono; void runTimings() { high_resolution_clock::time_point start, end; - for (int i = 15; i <= 27; i+=2) { + for (int i = 15; i <= 15; i+=2) { const int SIZE = 1 << i; int *a = new int[SIZE]; int *b = new int[SIZE]; @@ -158,9 +158,5 @@ void runTests() { int main(int argc, char* argv[]) { runTests(); - runTimings(); - - int i = 0; - scanf("%d", i); - + //runTimings(); } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 913e24d..71e6c10 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -16,21 +16,24 @@ 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 thrust_idata(idata, idata + n); + thrust::device_vector thrust_odata(odata, odata + n); + cudaEvent_t start, end; cudaEventCreate(&start); cudaEventCreate(&end); cudaEventRecord(start); - thrust::device_vector thrust_idata(idata, idata + n); - thrust::device_vector thrust_odata(odata, odata + n); thrust::exclusive_scan(thrust_idata.begin(), thrust_idata.end(), thrust_odata.begin()); - thrust::copy(thrust_odata.begin(), thrust_odata.end(), odata); cudaEventRecord(end); cudaEventSynchronize(end); float milliseconds = 0; cudaEventElapsedTime(&milliseconds, start, end); printf("Thrust scan: %f ms\n", milliseconds); + thrust::copy(thrust_odata.begin(), thrust_odata.end(), odata); + } }