From 787d7d104bfc2fe5f4d8afff3dbc5e58a3b41b37 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Tue, 16 Sep 2025 20:12:45 -0400 Subject: [PATCH 01/11] feat: cpu --- stream_compaction/common.h | 4 +++ stream_compaction/cpu.cu | 52 +++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/stream_compaction/common.h b/stream_compaction/common.h index d2c1fed9..1080750b 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -114,6 +114,10 @@ namespace StreamCompaction { PerformanceTimer& operator=(const PerformanceTimer&) = delete; PerformanceTimer& operator=(PerformanceTimer&&) = delete; + bool cpuTimerStarted() { + return cpu_timer_started; + } + private: cudaEvent_t event_start = nullptr; cudaEvent_t event_end = nullptr; diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 719fa115..0d055c10 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -18,9 +18,22 @@ namespace StreamCompaction { * (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(); + bool timerStarted = false; + if (!timer().cpuTimerStarted()) { + timer().startCpuTimer(); + timerStarted = true; + } + + if (n > 0) { + odata[0] = 0; + } + for (int i = 1; i < n; i++) { + odata[i] = odata[i - 1] + idata[i - 1]; + } + + if (timerStarted) { + timer().endCpuTimer(); + } } /** @@ -30,9 +43,17 @@ namespace StreamCompaction { */ int compactWithoutScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + int offset = 0; + for (int i = 0; i < n; i++) { + if (idata[i] == 0) { + offset++; + } + else { + odata[i - offset] = idata[i]; + } + } timer().endCpuTimer(); - return -1; + return n - offset; } /** @@ -42,9 +63,26 @@ namespace StreamCompaction { */ int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + + + odata[0] = 0; + for (int i = 1; i < n; i++) { + odata[i] = idata[i] == 0 ? 1 : 0; + } + + int* offsets = new int[n]; + + scan(n, offsets, odata); + for (int i = 0; i < n; i++) { + odata[i - offsets[i]] = idata[i]; + } + + int out = n - offsets[n - 1] - (idata[n - 1] == 0 ? 1 : 0); + + delete[] offsets; + timer().endCpuTimer(); - return -1; + return out; } } } From 3157993926bcee5b66c1ed8efb6a39bdb9e8d2a8 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:34:27 -0400 Subject: [PATCH 02/11] feat: naive scan --- stream_compaction/common.cu | 12 +++++++-- stream_compaction/naive.cu | 53 ++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 2ed6d630..2bbc0933 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -23,7 +23,10 @@ namespace StreamCompaction { * which map to 0 will be removed, and elements which map to 1 will be kept. */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { - // TODO + int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index >= n) return; + + bools[index] = idata[index] == 0 ? 0 : 1; } /** @@ -32,7 +35,12 @@ 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] == 0) return; + + odata[indices[index]] = idata[index]; } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 43088769..ed380fb2 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -12,13 +12,64 @@ namespace StreamCompaction { return timer; } // TODO: __global__ + int *dev_arr1; + int *dev_arr2; + + __global__ void add(int n, int skip, int* out, int* in) { + int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index >= n) return; + + out[index] = in[index]; + + if (index >= skip) { + out[index] += in[index - skip]; + } + } + + __global__ void insert0(int n, int* out, int* in) { + int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index >= n) return; + + out[index] = index == 0 ? 0 : in[index - 1]; + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { timer().startGpuTimer(); - // TODO + + cudaMalloc((void **)&dev_arr1, n * sizeof(int)); + cudaMalloc((void **)&dev_arr2, n * sizeof(int)); + + + cudaMemcpy(dev_arr1, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + + int blockSize = 256; + int nBlocks = (n + blockSize - 1) / blockSize; + + + int *curIn = dev_arr1; + int *curOut = dev_arr2; + + insert0<<>>(n, curOut, curIn); + + for (int skip = 1; skip < n; skip <<= 1) { + int* tmp = curIn; + curIn = curOut; + curOut = tmp; + + add<<>>(n, skip, curOut, curIn); + } + + + cudaMemcpy(odata, curOut, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_arr1); + cudaFree(dev_arr2); + + timer().endGpuTimer(); } } From d8d6bd2e5107829b050d1bae6345f84e87ade4ab Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Tue, 16 Sep 2025 23:20:11 -0400 Subject: [PATCH 03/11] feat: work efficient scan --- stream_compaction/common.cu | 2 +- stream_compaction/cpu.cu | 10 +-- stream_compaction/efficient.cu | 111 ++++++++++++++++++++++++++++++++- 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 2bbc0933..9bb4cdb5 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -38,7 +38,7 @@ namespace StreamCompaction { int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n) return; - if (bools[index] == 0) return; + if (idata[index] == 0) return; odata[indices[index]] = idata[index]; } diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 0d055c10..f996c788 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -65,19 +65,19 @@ namespace StreamCompaction { timer().startCpuTimer(); - odata[0] = 0; - for (int i = 1; i < n; i++) { - odata[i] = idata[i] == 0 ? 1 : 0; + for (int i = 0; i < n; i++) { + odata[i] = idata[i] == 0 ? 0 : 1; } int* offsets = new int[n]; scan(n, offsets, odata); for (int i = 0; i < n; i++) { - odata[i - offsets[i]] = idata[i]; + if (idata[i] == 0) continue; + odata[offsets[i]] = idata[i]; } - int out = n - offsets[n - 1] - (idata[n - 1] == 0 ? 1 : 0); + int out = offsets[n - 1]; delete[] offsets; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 2db346ee..a5bcaa34 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -12,12 +12,75 @@ namespace StreamCompaction { return timer; } + + int* dev_arr; + int *dev_arr2; + int* dev_bools; + + __global__ void upsweepStep(int nc, int step, int* arr) { + int index = blockIdx.x * blockDim.x + threadIdx.x; + + int indexRight = (index + 1) * step - 1; + if (indexRight >= nc) return; + + if (indexRight == nc - 1 && step == nc) { + arr[indexRight] = 0; + return; + } + + int indexLeft = indexRight - step / 2; + + arr[indexRight] = arr[indexLeft] + arr[indexRight]; + } + + __global__ void downsweepStep(int nc, int step, int* arr) { + int index = blockIdx.x * blockDim.x + threadIdx.x; + + int indexRight = (index + 1) * step - 1; + if (indexRight >= nc) return; + + int indexLeft = indexRight - step / 2; + + int right = arr[indexRight]; + arr[indexRight] += arr[indexLeft]; + arr[indexLeft] = right; + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { timer().startGpuTimer(); // TODO + + + int nc = 1 << ilog2ceil(n); + + cudaMalloc((void **)&dev_arr, nc * sizeof(int)); + + + cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); + + + int blockSize = 256; + int nBlocks = (nc + blockSize - 1) / blockSize; + + + for (int step = 2; step <= nc; step <<= 1) { + upsweepStep<<>>(nc, step, dev_arr); + } + + for (int step = nc; step >= 2; step >>= 1) { + downsweepStep<<>>(nc, step, dev_arr); + } + + + cudaMemcpy(odata, dev_arr, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_arr); + + timer().endGpuTimer(); } @@ -32,9 +95,53 @@ namespace StreamCompaction { */ int compact(int n, int *odata, const int *idata) { timer().startGpuTimer(); - // TODO + + if (n == 0) { + timer().endGpuTimer(); + return 0; + } + + + int blockSize = 256; + int nBlocks = (n + blockSize - 1) / blockSize; + + + int nc = 1 << ilog2ceil(n); + + cudaMalloc((void**)&dev_arr, nc * sizeof(int)); + cudaMalloc((void**)&dev_arr2, n * sizeof(int)); + cudaMalloc((void**)&dev_bools, nc * sizeof(int)); + + + cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); + + Common::kernMapToBoolean<<>>(nc, dev_bools, dev_arr); + + for (int step = 2; step <= nc; step <<= 1) { + upsweepStep << > > (nc, step, dev_bools); + } + + for (int step = nc; step >= 2; step >>= 1) { + downsweepStep << > > (nc, step, dev_bools); + } + + + + Common::kernScatter<<>>(n, dev_arr2, dev_arr, NULL, dev_bools); + + int nFinal; + cudaMemcpy(&nFinal, dev_bools + nc - 1, sizeof(int), cudaMemcpyDeviceToHost); + + cudaMemcpy(odata, dev_arr2, nFinal * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_arr); + cudaFree(dev_arr2); + cudaFree(dev_bools); + + timer().endGpuTimer(); - return -1; + return nFinal; } } } From 86e14ea072d76aec433d9a9f9702787e532195d3 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Tue, 16 Sep 2025 23:48:03 -0400 Subject: [PATCH 04/11] feat: thrust scan --- stream_compaction/thrust.cu | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 1def45e7..7dff5e8d 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -1,11 +1,12 @@ #include #include -#include -#include -#include #include "common.h" #include "thrust.h" +#include +#include +#include + namespace StreamCompaction { namespace Thrust { using StreamCompaction::Common::PerformanceTimer; @@ -22,6 +23,14 @@ namespace StreamCompaction { // TODO use `thrust::exclusive_scan` // example: for device_vectors dv_in and dv_out: // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + + thrust::device_vector dv_in = (thrust::device_vector)thrust::host_vector(idata, idata + n); + thrust::device_vector dv_out = (thrust::device_vector)thrust::host_vector(odata, odata + n); + + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + + thrust::copy(dv_out.begin(), dv_out.end(), odata); + timer().endGpuTimer(); } } From 3652c594e0ae93621186485fc0396b4a7daee4c1 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Tue, 16 Sep 2025 23:48:49 -0400 Subject: [PATCH 05/11] docs: readme --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0e38ddb1..c6490080 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,6 @@ 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) - -### (TODO: Your README) - -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) - +* Daniel Chen + * https://www.linkedin.com/in/daniel-c-a02ba2229/ +* Tested on: Windows 11, AMD Ryzen 7 8845HS w/ Radeon 780M Graphics (3.80 GHz), RTX 4070 notebook From 110220f266bb5297c2afd6b3d61c70f8f8256f47 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Wed, 17 Sep 2025 01:55:53 -0400 Subject: [PATCH 06/11] fix: adjust times --- README.md | 61 ++++++++++++++++++++++++++++++++++ stream_compaction/efficient.cu | 41 ++++++++++++----------- stream_compaction/naive.cu | 13 ++++---- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index c6490080..284f5933 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,64 @@ CUDA Stream Compaction * Daniel Chen * https://www.linkedin.com/in/daniel-c-a02ba2229/ * Tested on: Windows 11, AMD Ryzen 7 8845HS w/ Radeon 780M Graphics (3.80 GHz), RTX 4070 notebook + + +# Writeup + +## Test output +``` + +**************** +** SCAN TESTS ** +**************** + [ 29 22 44 14 19 35 38 23 43 43 35 30 6 ... 20 0 ] +==== cpu scan, power-of-two ==== + elapsed time: 0.0012ms (std::chrono Measured) + [ 0 29 51 95 109 128 163 201 224 267 310 345 375 ... 6278 6298 ] +==== cpu scan, non-power-of-two ==== + elapsed time: 0.0006ms (std::chrono Measured) + [ 0 29 51 95 109 128 163 201 224 267 310 345 375 ... 6216 6219 ] + passed +==== naive scan, power-of-two ==== + elapsed time: 1.81264ms (CUDA Measured) + passed +==== naive scan, non-power-of-two ==== + elapsed time: 0.605056ms (CUDA Measured) + passed +==== work-efficient scan, power-of-two ==== + elapsed time: 0.677728ms (CUDA Measured) + passed +==== work-efficient scan, non-power-of-two ==== + elapsed time: 0.334272ms (CUDA Measured) + passed +==== thrust scan, power-of-two ==== + elapsed time: 2.58243ms (CUDA Measured) + passed +==== thrust scan, non-power-of-two ==== + elapsed time: 0.525216ms (CUDA Measured) + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 2 2 1 2 0 2 3 0 3 1 1 2 1 ... 2 0 ] +==== cpu compact without scan, power-of-two ==== + elapsed time: 0.0014ms (std::chrono Measured) + [ 2 2 1 2 2 3 3 1 1 2 1 1 3 ... 2 2 ] + passed +==== cpu compact without scan, non-power-of-two ==== + elapsed time: 0.0011ms (std::chrono Measured) + [ 2 2 1 2 2 3 3 1 1 2 1 1 3 ... 2 3 ] + passed +==== cpu compact with scan ==== + elapsed time: 0.0057ms (std::chrono Measured) + [ 2 2 1 2 2 3 3 1 1 2 1 1 3 ... 2 2 ] + passed +==== work-efficient compact, power-of-two ==== + elapsed time: 0.727648ms (CUDA Measured) + passed +==== work-efficient compact, non-power-of-two ==== + elapsed time: 1.61846ms (CUDA Measured) + passed +Press any key to continue . . . +``` \ No newline at end of file diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index a5bcaa34..c52a9487 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -50,20 +50,20 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); - // TODO - - int nc = 1 << ilog2ceil(n); - cudaMalloc((void **)&dev_arr, nc * sizeof(int)); + cudaMalloc((void**)&dev_arr, nc * sizeof(int)); + + + timer().startGpuTimer(); + // TODO cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); - int blockSize = 256; + int blockSize = 128; int nBlocks = (nc + blockSize - 1) / blockSize; @@ -78,10 +78,11 @@ namespace StreamCompaction { cudaMemcpy(odata, dev_arr, n * sizeof(int), cudaMemcpyDeviceToHost); - cudaFree(dev_arr); - timer().endGpuTimer(); + + + cudaFree(dev_arr); } /** @@ -94,6 +95,13 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { + int nc = 1 << ilog2ceil(n); + + cudaMalloc((void**)&dev_arr, nc * sizeof(int)); + cudaMalloc((void**)&dev_arr2, n * sizeof(int)); + cudaMalloc((void**)&dev_bools, nc * sizeof(int)); + + timer().startGpuTimer(); if (n == 0) { @@ -102,15 +110,8 @@ namespace StreamCompaction { } - int blockSize = 256; - int nBlocks = (n + blockSize - 1) / blockSize; - - - int nc = 1 << ilog2ceil(n); - - cudaMalloc((void**)&dev_arr, nc * sizeof(int)); - cudaMalloc((void**)&dev_arr2, n * sizeof(int)); - cudaMalloc((void**)&dev_bools, nc * sizeof(int)); + int blockSize = 128; + int nBlocks = (nc + blockSize - 1) / blockSize; cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); @@ -135,12 +136,14 @@ namespace StreamCompaction { cudaMemcpy(odata, dev_arr2, nFinal * sizeof(int), cudaMemcpyDeviceToHost); + + timer().endGpuTimer(); + + cudaFree(dev_arr); cudaFree(dev_arr2); cudaFree(dev_bools); - - timer().endGpuTimer(); return nFinal; } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index ed380fb2..fe794316 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -37,10 +37,11 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - timer().startGpuTimer(); + cudaMalloc((void**)&dev_arr1, n * sizeof(int)); + cudaMalloc((void**)&dev_arr2, n * sizeof(int)); + - cudaMalloc((void **)&dev_arr1, n * sizeof(int)); - cudaMalloc((void **)&dev_arr2, n * sizeof(int)); + timer().startGpuTimer(); cudaMemcpy(dev_arr1, idata, n * sizeof(int), cudaMemcpyHostToDevice); @@ -66,11 +67,11 @@ namespace StreamCompaction { cudaMemcpy(odata, curOut, n * sizeof(int), cudaMemcpyDeviceToHost); - cudaFree(dev_arr1); - cudaFree(dev_arr2); - timer().endGpuTimer(); + + cudaFree(dev_arr1); + cudaFree(dev_arr2); } } } From 6f981f5da543cc35977f0d37cff0002109b1d504 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Wed, 17 Sep 2025 02:02:00 -0400 Subject: [PATCH 07/11] fix: kernel launch params --- stream_compaction/efficient.cu | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index c52a9487..28ea11dd 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -63,7 +63,7 @@ namespace StreamCompaction { cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); - int blockSize = 128; + int blockSize = 256; int nBlocks = (nc + blockSize - 1) / blockSize; @@ -98,7 +98,7 @@ namespace StreamCompaction { int nc = 1 << ilog2ceil(n); cudaMalloc((void**)&dev_arr, nc * sizeof(int)); - cudaMalloc((void**)&dev_arr2, n * sizeof(int)); + cudaMalloc((void**)&dev_arr2, nc * sizeof(int)); cudaMalloc((void**)&dev_bools, nc * sizeof(int)); @@ -110,26 +110,24 @@ namespace StreamCompaction { } - int blockSize = 128; + int blockSize = 256; int nBlocks = (nc + blockSize - 1) / blockSize; cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); - Common::kernMapToBoolean<<>>(nc, dev_bools, dev_arr); + Common::kernMapToBoolean<<>>(nc, dev_bools, dev_arr); for (int step = 2; step <= nc; step <<= 1) { - upsweepStep << > > (nc, step, dev_bools); + upsweepStep<<>>(nc, step, dev_bools); } for (int step = nc; step >= 2; step >>= 1) { - downsweepStep << > > (nc, step, dev_bools); + downsweepStep<<>>(nc, step, dev_bools); } - - - Common::kernScatter<<>>(n, dev_arr2, dev_arr, NULL, dev_bools); + Common::kernScatter<<>>(n, dev_arr2, dev_arr, NULL, dev_bools); int nFinal; cudaMemcpy(&nFinal, dev_bools + nc - 1, sizeof(int), cudaMemcpyDeviceToHost); From f98c15582af649f8a81a79e0ede00a97e6b6e185 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Wed, 17 Sep 2025 02:13:07 -0400 Subject: [PATCH 08/11] fix: fix block sizes --- src/main.cpp | 2 +- stream_compaction/efficient.cu | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3d5c8820..19655349 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 << 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/efficient.cu b/stream_compaction/efficient.cu index 28ea11dd..32e9104b 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -64,14 +64,15 @@ namespace StreamCompaction { int blockSize = 256; - int nBlocks = (nc + blockSize - 1) / blockSize; for (int step = 2; step <= nc; step <<= 1) { + int nBlocks = (nc / step + blockSize - 1) / blockSize; upsweepStep<<>>(nc, step, dev_arr); } for (int step = nc; step >= 2; step >>= 1) { + int nBlocks = (nc / step + blockSize - 1) / blockSize; downsweepStep<<>>(nc, step, dev_arr); } @@ -111,23 +112,25 @@ namespace StreamCompaction { int blockSize = 256; - int nBlocks = (nc + blockSize - 1) / blockSize; + int nBlocksOuter = (nc + blockSize - 1) / blockSize; cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); - Common::kernMapToBoolean<<>>(nc, dev_bools, dev_arr); + Common::kernMapToBoolean<<>>(nc, dev_bools, dev_arr); for (int step = 2; step <= nc; step <<= 1) { + int nBlocks = (nc / step + blockSize - 1) / blockSize; upsweepStep<<>>(nc, step, dev_bools); } for (int step = nc; step >= 2; step >>= 1) { + int nBlocks = (nc / step + blockSize - 1) / blockSize; downsweepStep<<>>(nc, step, dev_bools); } - Common::kernScatter<<>>(n, dev_arr2, dev_arr, NULL, dev_bools); + Common::kernScatter<<>>(n, dev_arr2, dev_arr, NULL, dev_bools); int nFinal; cudaMemcpy(&nFinal, dev_bools + nc - 1, sizeof(int), cudaMemcpyDeviceToHost); From 9272d8e3aa4f1d509376842eac2fe4faa9a70c37 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Wed, 17 Sep 2025 03:11:07 -0400 Subject: [PATCH 09/11] fix: use longs --- README.md | 2 ++ src/main.cpp | 2 +- stream_compaction/efficient.cu | 15 ++++++++------- stream_compaction/naive.cu | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 284f5933..bdb0755d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ CUDA Stream Compaction # Writeup +## Algorithm comparison + ## Test output ``` diff --git a/src/main.cpp b/src/main.cpp index 19655349..a2d3f8fa 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 << 24; // 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 32e9104b..eea31cc2 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -2,6 +2,7 @@ #include #include "common.h" #include "efficient.h" +#include namespace StreamCompaction { namespace Efficient { @@ -18,9 +19,9 @@ namespace StreamCompaction { int* dev_bools; __global__ void upsweepStep(int nc, int step, int* arr) { - int index = blockIdx.x * blockDim.x + threadIdx.x; + unsigned long long int index = blockIdx.x * blockDim.x + threadIdx.x; - int indexRight = (index + 1) * step - 1; + unsigned long long int indexRight = (index + 1) * step - 1; if (indexRight >= nc) return; if (indexRight == nc - 1 && step == nc) { @@ -28,18 +29,18 @@ namespace StreamCompaction { return; } - int indexLeft = indexRight - step / 2; + unsigned long long int indexLeft = indexRight - step / 2; arr[indexRight] = arr[indexLeft] + arr[indexRight]; } __global__ void downsweepStep(int nc, int step, int* arr) { - int index = blockIdx.x * blockDim.x + threadIdx.x; + unsigned long long int index = blockIdx.x * blockDim.x + threadIdx.x; - int indexRight = (index + 1) * step - 1; + unsigned long long int indexRight = (index + 1) * step - 1; if (indexRight >= nc) return; - int indexLeft = indexRight - step / 2; + unsigned long long int indexLeft = indexRight - step / 2; int right = arr[indexRight]; arr[indexRight] += arr[indexLeft]; @@ -75,7 +76,7 @@ namespace StreamCompaction { int nBlocks = (nc / step + blockSize - 1) / blockSize; downsweepStep<<>>(nc, step, dev_arr); } - + cudaMemcpy(odata, dev_arr, n * sizeof(int), cudaMemcpyDeviceToHost); diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index fe794316..4a615fa8 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -16,7 +16,7 @@ namespace StreamCompaction { int *dev_arr2; __global__ void add(int n, int skip, int* out, int* in) { - int index = blockIdx.x * blockDim.x + threadIdx.x; + unsigned long long int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n) return; out[index] = in[index]; @@ -27,7 +27,7 @@ namespace StreamCompaction { } __global__ void insert0(int n, int* out, int* in) { - int index = blockIdx.x * blockDim.x + threadIdx.x; + unsigned long long int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n) return; out[index] = index == 0 ? 0 : in[index - 1]; From 5a2f1037e0989664d387d48a70f24f25195c6982 Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Wed, 17 Sep 2025 04:18:31 -0400 Subject: [PATCH 10/11] docs,fix: complete writeup, move memory ops out of timings --- README.md | 8 ++++++ img/graphs.png | Bin 0 -> 81540 bytes src/main.cpp | 2 +- stream_compaction/efficient.cu | 43 ++++++++++++++++----------------- stream_compaction/naive.cu | 10 ++++---- stream_compaction/thrust.cu | 12 +++++---- 6 files changed, 42 insertions(+), 33 deletions(-) create mode 100644 img/graphs.png diff --git a/README.md b/README.md index bdb0755d..185237c1 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,14 @@ CUDA Stream Compaction # Writeup ## Algorithm comparison +![algorithm comparison graphs](/img/graphs.png) + +The above log-log plots show the running times of each of the implemented scan algorithms, at power-of-2 array sizes (left) and 3 less than each of those power-of-2 array sizes (right). Initial memory setup using `cudaMalloc`, `cudaMemset`, and `thrust::{host_vector, copy}` is excluded from the timings as per Part 7 of [INSTRUCTION.md](INSTRUCTION.md). + +For small arrays, CPU scan was the fastest, followed by naive on average, then work-efficient, then thrust. However, throughout array sizes in the low millions, the order reverses: thrust is the fastest, followed by work-efficient, then naive, and then CPU. As the array sizes grow large, they all seem to increase by the same power (about linear). The reversal of the order would likely be due to overhead of the kernel launches at the smaller array sizes and/or possible block size misalignment with the number of threads. As the arrays grow large, however, the benefits of parallelization and GPU memory access optimization become more pronounced the more times the kernels are launched and the more blocks that are instantiated, even if the long-term complexity of all the algorithms remains $O(n)$ (not $O(\log(n))$ for the GPU implementations as the number of concurrent threads will be capped to be some fixed number based on the GPU hardware). + +## Extra credit +As requested in Part 5 of [INSTRUCTION.md](INSTRUCTION.md), the work-efficient GPU implementation is over 4 times faster than the CPU implementation for array sizes greater than ~1 million even without further optimization. ## Test output ``` diff --git a/img/graphs.png b/img/graphs.png new file mode 100644 index 0000000000000000000000000000000000000000..87de47e4a8c59b6dfa0b6dcfac829e487fe2a7a9 GIT binary patch literal 81540 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 Wc9q&|WDNAteu8SjHA~e0c<>*Y1ki8* literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index a2d3f8fa..3f8e321b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 24; // feel free to change the size of array +const int SIZE = 1 << 29; // 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 eea31cc2..b6d61cbf 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -24,11 +24,6 @@ namespace StreamCompaction { unsigned long long int indexRight = (index + 1) * step - 1; if (indexRight >= nc) return; - if (indexRight == nc - 1 && step == nc) { - arr[indexRight] = 0; - return; - } - unsigned long long int indexLeft = indexRight - step / 2; arr[indexRight] = arr[indexLeft] + arr[indexRight]; @@ -55,13 +50,13 @@ namespace StreamCompaction { cudaMalloc((void**)&dev_arr, nc * sizeof(int)); + cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); + timer().startGpuTimer(); // TODO - - cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); - cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); int blockSize = 256; @@ -72,17 +67,19 @@ namespace StreamCompaction { upsweepStep<<>>(nc, step, dev_arr); } + cudaMemset(dev_arr + nc - 1, 0, sizeof(int)); + for (int step = nc; step >= 2; step >>= 1) { int nBlocks = (nc / step + blockSize - 1) / blockSize; downsweepStep<<>>(nc, step, dev_arr); } - cudaMemcpy(odata, dev_arr, n * sizeof(int), cudaMemcpyDeviceToHost); + timer().endGpuTimer(); - timer().endGpuTimer(); + cudaMemcpy(odata, dev_arr, n * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_arr); } @@ -97,21 +94,16 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { + if (n == 0) return 0; + + int nc = 1 << ilog2ceil(n); cudaMalloc((void**)&dev_arr, nc * sizeof(int)); - cudaMalloc((void**)&dev_arr2, nc * sizeof(int)); + cudaMalloc((void**)&dev_arr2, n * sizeof(int)); cudaMalloc((void**)&dev_bools, nc * sizeof(int)); - timer().startGpuTimer(); - - if (n == 0) { - timer().endGpuTimer(); - return 0; - } - - int blockSize = 256; int nBlocksOuter = (nc + blockSize - 1) / blockSize; @@ -119,6 +111,9 @@ namespace StreamCompaction { cudaMemcpy(dev_arr, idata, n * sizeof(int), cudaMemcpyHostToDevice); cudaMemset(dev_arr + n, 0, (nc - n) * sizeof(int)); + + timer().startGpuTimer(); + Common::kernMapToBoolean<<>>(nc, dev_bools, dev_arr); for (int step = 2; step <= nc; step <<= 1) { @@ -126,6 +121,8 @@ namespace StreamCompaction { upsweepStep<<>>(nc, step, dev_bools); } + cudaMemset(dev_bools + nc - 1, 0, sizeof(int)); + for (int step = nc; step >= 2; step >>= 1) { int nBlocks = (nc / step + blockSize - 1) / blockSize; downsweepStep<<>>(nc, step, dev_bools); @@ -133,15 +130,17 @@ namespace StreamCompaction { Common::kernScatter<<>>(n, dev_arr2, dev_arr, NULL, dev_bools); + + timer().endGpuTimer(); + + + int nFinal; cudaMemcpy(&nFinal, dev_bools + nc - 1, sizeof(int), cudaMemcpyDeviceToHost); cudaMemcpy(odata, dev_arr2, nFinal * sizeof(int), cudaMemcpyDeviceToHost); - timer().endGpuTimer(); - - cudaFree(dev_arr); cudaFree(dev_arr2); cudaFree(dev_bools); diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 4a615fa8..77c47588 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -40,11 +40,10 @@ namespace StreamCompaction { cudaMalloc((void**)&dev_arr1, n * sizeof(int)); cudaMalloc((void**)&dev_arr2, n * sizeof(int)); + cudaMemcpy(dev_arr1, idata, n * sizeof(int), cudaMemcpyHostToDevice); - timer().startGpuTimer(); - - cudaMemcpy(dev_arr1, idata, n * sizeof(int), cudaMemcpyHostToDevice); + timer().startGpuTimer(); int blockSize = 256; @@ -65,10 +64,11 @@ namespace StreamCompaction { } - cudaMemcpy(odata, curOut, n * sizeof(int), cudaMemcpyDeviceToHost); - timer().endGpuTimer(); + + + cudaMemcpy(odata, curOut, n * sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_arr1); cudaFree(dev_arr2); diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 7dff5e8d..72e8a8e7 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -19,19 +19,21 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + thrust::device_vector dv_in = (thrust::device_vector)thrust::host_vector(idata, idata + n); + thrust::device_vector dv_out = (thrust::device_vector)thrust::host_vector(odata, odata + n); + + timer().startGpuTimer(); // TODO use `thrust::exclusive_scan` // example: for device_vectors dv_in and dv_out: // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); - thrust::device_vector dv_in = (thrust::device_vector)thrust::host_vector(idata, idata + n); - thrust::device_vector dv_out = (thrust::device_vector)thrust::host_vector(odata, odata + n); - thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); - thrust::copy(dv_out.begin(), dv_out.end(), odata); - timer().endGpuTimer(); + + + thrust::copy(dv_out.begin(), dv_out.end(), odata); } } } From dd021237aa79fd20912d88e9efba6294c28bb4be Mon Sep 17 00:00:00 2001 From: enamchae <56808335+enamchae@users.noreply.github.com> Date: Wed, 17 Sep 2025 04:25:37 -0400 Subject: [PATCH 11/11] docs: add project desc --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 185237c1..9c407f28 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ CUDA Stream Compaction # Writeup +This project contains several CPU/GPU prefix sum and stream compaction algorithms to compare their performance. ## Algorithm comparison ![algorithm comparison graphs](/img/graphs.png)