From fa821d7a62220d4da0095d2fa3921703c7fe8842 Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Wed, 17 Sep 2025 10:54:14 -0400 Subject: [PATCH 01/10] finish naive scan --- src/main.cpp | 2 +- stream_compaction/common.h | 1 + stream_compaction/cpu.cu | 81 ++++++++++++++++++++++++++-- stream_compaction/naive.cu | 107 +++++++++++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3d5c8820..3227aaa5 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 << 11; // 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 d2c1fed9..b3564f91 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -9,6 +9,7 @@ #include #include #include +#include #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 719fa115..1a1bcc4c 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -1,5 +1,6 @@ #include #include "cpu.h" +#include #include "common.h" @@ -17,9 +18,45 @@ namespace StreamCompaction { * 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) { + void scan(int n, int* odata, const int* idata) { timer().startCpuTimer(); // TODO + + // -------------- Option 1: Single for loop ---------------- + odata[0] = 0; + for (int i = 1; i < n; ++i) { + odata[i] = odata[i - 1] + idata[i - 1]; + } + + // --------------- Option 2: Simulate GPU ------------------- + //int layer = ilog2ceil(n); + + //// Copy the input to the ping pong buffer + //int* ppdata = new int[n]; + //for (int i = 0; i < n; ++i) { + // ppdata[i] = idata[i]; + //} + + //// Scan + //for (int d = 1; d <= layer; ++d) { + // int offset = 1 << (d - 1); + // for (int i = 0; i < n; ++i) { + // if (i >= offset) { + // odata[i] = ppdata[i - offset] + ppdata[i]; + // } else { + // odata[i] = ppdata[i]; + // } + // } + // std::swap(odata, ppdata); + //} + //// Shift right + //odata[0] = 0; + //for (int i = 1; i < n; ++i) { + // odata[i] = ppdata[i - 1]; + //} + //delete[] ppdata; + + // ----------------------------------------------------------- timer().endCpuTimer(); } @@ -31,8 +68,16 @@ namespace StreamCompaction { int compactWithoutScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); // TODO + int idx = 0; + for (int i = 0; i < n; ++i) { + if (idata[i] != 0) { + odata[idx] = idata[i]; + idx++; + } + } + timer().endCpuTimer(); - return -1; + return idx; } /** @@ -43,8 +88,38 @@ namespace StreamCompaction { int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); // TODO + // Create a boolean array + int* boolVal = new int[n]; + int cnt = 0; + for (int i = 0; i < n; ++i) { + if (idata[i] == 0) { + boolVal[i] = 0; + } else { + boolVal[i] = 1; + cnt++; + } + } + + // Scan + int* scanVal = new int[n]; + scanVal[0] = 0; + for (int i = 1; i < n; ++i) { + scanVal[i] = scanVal[i - 1] + boolVal[i - 1]; + } + + // Scatter + for (int i = 0; i < n; ++i) { + if (boolVal[i]) { + int idx = scanVal[i]; + odata[idx] = idata[i]; + } + } + + delete[] boolVal; + delete[] scanVal; + timer().endCpuTimer(); - return -1; + return cnt; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 43088769..f3ceaaa7 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -13,12 +13,119 @@ namespace StreamCompaction { } // TODO: __global__ + + __global__ void kernNaiveScan(int n, int* odata, const int* idata, int* blockSum) { + int id = blockIdx.x * blockDim.x + threadIdx.x; + int thid = threadIdx.x; + int size = blockDim.x; + + if (id >= n) return; + + // Shared memory array + extern __shared__ int temp[]; + + // Set ping pong buffer flags + int pout = 0, pin = 1; + + // Load array + temp[pout * size + thid] = (thid > 0) ? idata[id - 1] : 0; // thread 0 -> 0, others -> idata[id - 1] + __syncthreads(); + + // Scan within the block + for (int offset = 1; offset < n; offset *= 2) { + pout = 1 - pout; + pin = 1 - pout; + if (thid >= offset) { + temp[pout * size + thid] = temp[pin * size + thid] + temp[pin * size + thid - offset]; + } + else { + temp[pout * size + thid] = temp[pin * size + thid]; + } + __syncthreads(); + } + + odata[id] = temp[pout * size + thid]; + + // Write block sum + if (threadIdx.x == blockDim.x - 1 || id == n - 1) { + int blockEnd = (n < (blockIdx.x + 1) * blockDim.x) ? n : ((blockIdx.x + 1) * blockDim.x); // last global index in this block + blockSum[blockIdx.x] = odata[blockEnd - 1] + idata[blockEnd - 1]; + } + } + + __global__ void kernAddBlockOffset(int n, int* odata, const int* blockOffset) { + int id = blockIdx.x * blockDim.x + threadIdx.x; + if (id >= n) return; + odata[id] += blockOffset[blockIdx.x]; + } + /** * 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 threads = 1024; + int blocks = (n + threads - 1) / threads; // ceil + + // Allocate & copy memory + int* d_odata, *d_idata; + + cudaMalloc(&d_odata, n * sizeof(int)); + checkCUDAError("Naive::cudaMalloc d_odata fails!"); + + cudaMalloc(&d_idata, n * sizeof(int)); + checkCUDAError("Naive::cudaMalloc d_idata fails!"); + + cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Naive::cudaMemcpyHostToDevice fails!"); + + // Handle array of arbitrary length - create and set blockSum + int* blockSum = new int[blocks]; + + int* d_blockSum; + cudaMalloc(&d_blockSum, blocks * sizeof(int)); + checkCUDAError("Naive::cudaMalloc d_blockSum fails!"); + + cudaMemset(d_blockSum, 0, blocks * sizeof(int)); // Initialize the sum to 0 + checkCUDAError("Naive::cudaMemset d_blockSum fails!"); + + // Call kernNaiveScan + kernNaiveScan<<>>(n, d_odata, d_idata, d_blockSum); + checkCUDAError("Naive::kernNaiveScan fails!"); + + // Handle array of arbitrary length - scan the blockSum (on CPU) + cudaMemcpy(blockSum, d_blockSum, blocks * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Naive::cudaMemcpyDeviceToHost fails!"); + + int* blockOffset = new int[blocks]; + blockOffset[0] = 0; + for (int i = 1; i < blocks; ++i) { + blockOffset[i] = blockOffset[i - 1] + blockSum[i - 1]; + } + + // Handle array of arbitrary length - add the offset back to array + int* d_blockOffset; + cudaMalloc(&d_blockOffset, blocks * sizeof(int)); + checkCUDAError("Naive::cudaMalloc d_blockOffset fails!"); + + cudaMemcpy(d_blockOffset, blockOffset, blocks * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Naive::cudaMemcpyHostToDevice fails!"); + + // Call kernAddBlockOffset + kernAddBlockOffset<<>>(n, d_odata, d_blockOffset); + + // Copy the value back + cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Naive::cudaMemcpyDeviceToHost fails!"); + + delete[] blockSum; + delete[] blockOffset; + cudaFree(d_idata); + cudaFree(d_odata); + cudaFree(d_blockSum); + cudaFree(d_blockOffset); + timer().endGpuTimer(); } } From c00342adf514015378a8bf772b1f2e9d8a7d97cd Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Wed, 17 Sep 2025 22:06:19 -0400 Subject: [PATCH 02/10] implement work efficient power of 2 --- src/main.cpp | 2 +- stream_compaction/efficient.cu | 124 +++++++++++++++++++++++++++++++++ stream_compaction/naive.cu | 7 +- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3227aaa5..274b4c4c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 11; // feel free to change the size of array +const int SIZE = 1 << 12; // 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 2db346ee..7becec3c 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -12,12 +12,136 @@ namespace StreamCompaction { return timer; } + __global__ void kernEfficientScan(int n, int* odata, const int* idata, int* blockSum) { + int id = blockIdx.x * blockDim.x + threadIdx.x; + int thid = threadIdx.x; + int size = 2 * blockDim.x; + int offset = 1; + + // Shared memory array + extern __shared__ int temp[]; + + // Load input into shared memory + temp[2 * thid] = idata[2 * id]; + temp[2 * thid + 1] = idata[2 * id + 1]; + __syncthreads(); + + // Up sweep + for (int d = size >> 1; d > 0; d >>= 1) { + __syncthreads(); + if (thid < d) { + int ai = offset * (2 * thid + 1) - 1; + int bi = offset * (2 * thid + 2) - 1; + temp[bi] += temp[ai]; + } + offset *= 2; + } + + // Set the root for down sweep tree to 0 + if (thid == 0) { + blockSum[blockIdx.x] = temp[size - 1]; + temp[size - 1] = 0; + } + + // Down sweep + for (int d = 1; d < size; d *= 2) { + offset >>= 1; + __syncthreads(); + if (thid < d) { + int ai = offset * (2 * thid + 1) - 1; + int bi = offset * (2 * thid + 2) - 1; + int t = temp[ai]; + temp[ai] = temp[bi]; + temp[bi] += t; + } + } + __syncthreads(); + + odata[2 * id] = temp[2 * thid]; // write results to device memory + odata[2 * id + 1] = temp[2 * thid + 1]; + } + + + __global__ void kernAddBlockOffset(int n, int* odata, const int* blockOffset) { + int id = blockIdx.x * blockDim.x + threadIdx.x; + if (id >= n / 2) return; + odata[2 * id] += blockOffset[blockIdx.x]; + odata[2 * id + 1] += blockOffset[blockIdx.x]; + } + + /** * 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 threads = 1024; + int blocks = (n / 2 + threads - 1) / threads; // ceil + + // Allocate & copy memory + int* d_odata, *d_idata; + + cudaMalloc(&d_odata, n * sizeof(int)); + checkCUDAError("Efficient::cudaMalloc d_odata fails!"); + + cudaMalloc(&d_idata, n * sizeof(int)); + checkCUDAError("Efficient::cudaMalloc d_idata fails!"); + + cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Efficient::cudaMemcpyHostToDevice fails!"); + + // Handle array of arbitrary length - create and set blockSum + int* blockSum = new int[blocks]; + + int* d_blockSum; + cudaMalloc(&d_blockSum, blocks * sizeof(int)); + checkCUDAError("Efficient::cudaMalloc d_blockSum fails!"); + + cudaMemset(d_blockSum, 0, blocks * sizeof(int)); // Initialize the sum to 0 + checkCUDAError("Efficient::cudaMemset d_blockSum fails!"); + + // Call kernEfficientScan + kernEfficientScan<<>>(n, d_odata, d_idata, d_blockSum); + checkCUDAError("Efficient::kernEfficientScan fails!"); + + // Handle array of arbitrary length - scan the blockSum (on CPU) + cudaMemcpy(blockSum, d_blockSum, blocks * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Efficient::cudaMemcpyDeviceToHost fails!"); + + int* blockOffset = new int[blocks]; + blockOffset[0] = 0; + for (int i = 1; i < blocks; ++i) { + blockOffset[i] = blockOffset[i - 1] + blockSum[i - 1]; + } + + // Handle array of arbitrary length - add the offset back to array + int* d_blockOffset; + cudaMalloc(&d_blockOffset, blocks * sizeof(int)); + checkCUDAError("Efficient::cudaMalloc d_blockOffset fails!"); + + cudaMemcpy(d_blockOffset, blockOffset, blocks * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Efficient::cudaMemcpyHostToDevice fails!"); + + // Call kernAddBlockOffset + kernAddBlockOffset<<>>(n, d_odata, d_blockOffset); + checkCUDAError("Efficient::kernAddBlockOffset fails!"); + + // Copy the value back + cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Efficient::cudaMemcpyDeviceToHost fails!"); + + //for (int i = 1020; i < 1030; ++i) { + // std::cout << "index " << i << ": " << odata[i] << std::endl; + //} + + delete[] blockSum; + delete[] blockOffset; + cudaFree(d_idata); + cudaFree(d_odata); + cudaFree(d_blockSum); + cudaFree(d_blockOffset); + timer().endGpuTimer(); } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index f3ceaaa7..c1e746f2 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -48,7 +48,7 @@ namespace StreamCompaction { // Write block sum if (threadIdx.x == blockDim.x - 1 || id == n - 1) { - int blockEnd = (n < (blockIdx.x + 1) * blockDim.x) ? n : ((blockIdx.x + 1) * blockDim.x); // last global index in this block + int blockEnd = (n < (blockIdx.x + 1) * size) ? n : ((blockIdx.x + 1) * size); // last global index in this block blockSum[blockIdx.x] = odata[blockEnd - 1] + idata[blockEnd - 1]; } } @@ -114,11 +114,16 @@ namespace StreamCompaction { // Call kernAddBlockOffset kernAddBlockOffset<<>>(n, d_odata, d_blockOffset); + checkCUDAError("Naive::kernAddBlockOffset fails!"); // Copy the value back cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Naive::cudaMemcpyDeviceToHost fails!"); + //for (int i = 1020; i < 1030; ++i) { + // std::cout << "index " << i << ": " << odata[i] << std::endl; + //} + delete[] blockSum; delete[] blockOffset; cudaFree(d_idata); From a6f7d82537cd9a1446d99364f2836ac248025f47 Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Wed, 17 Sep 2025 22:39:10 -0400 Subject: [PATCH 03/10] finish work efficient non power of 2 --- stream_compaction/efficient.cu | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 7becec3c..517a9e36 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -76,17 +76,24 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata) { timer().startGpuTimer(); // TODO + // Pad + int pad_n = 1 << ilog2ceil(n); + + // Assign hreads and blocks int threads = 1024; - int blocks = (n / 2 + threads - 1) / threads; // ceil - + int blocks = (pad_n / 2 + threads - 1) / threads; // ceil + // Allocate & copy memory int* d_odata, *d_idata; - cudaMalloc(&d_odata, n * sizeof(int)); + cudaMalloc(&d_odata, pad_n * sizeof(int)); checkCUDAError("Efficient::cudaMalloc d_odata fails!"); - cudaMalloc(&d_idata, n * sizeof(int)); + cudaMalloc(&d_idata, pad_n * sizeof(int)); checkCUDAError("Efficient::cudaMalloc d_idata fails!"); + + cudaMemset(d_idata, 0, pad_n * sizeof(int)); + checkCUDAError("Efficient::cudaMemset d_idata fails!"); cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); checkCUDAError("Efficient::cudaMemcpyHostToDevice fails!"); @@ -102,7 +109,7 @@ namespace StreamCompaction { checkCUDAError("Efficient::cudaMemset d_blockSum fails!"); // Call kernEfficientScan - kernEfficientScan<<>>(n, d_odata, d_idata, d_blockSum); + kernEfficientScan<<>>(pad_n, d_odata, d_idata, d_blockSum); checkCUDAError("Efficient::kernEfficientScan fails!"); // Handle array of arbitrary length - scan the blockSum (on CPU) @@ -124,7 +131,7 @@ namespace StreamCompaction { checkCUDAError("Efficient::cudaMemcpyHostToDevice fails!"); // Call kernAddBlockOffset - kernAddBlockOffset<<>>(n, d_odata, d_blockOffset); + kernAddBlockOffset<<>>(pad_n, d_odata, d_blockOffset); checkCUDAError("Efficient::kernAddBlockOffset fails!"); // Copy the value back From d8a6ade38064f49f1954bb4b8122c724479ca2ea Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Thu, 18 Sep 2025 00:50:25 -0400 Subject: [PATCH 04/10] implement stream compaction --- src/main.cpp | 2 +- stream_compaction/common.cu | 14 ++++++ stream_compaction/efficient.cu | 91 +++++++++++++++++++++++++++------- stream_compaction/naive.cu | 6 +-- stream_compaction/thrust.cu | 9 ++++ 5 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 274b4c4c..83be3174 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 12; // feel free to change the size of array +const int SIZE = 1 << 15; // 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.cu b/stream_compaction/common.cu index 2ed6d630..30df97e1 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -24,6 +24,14 @@ namespace StreamCompaction { */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { // TODO + int id = threadIdx.x + blockIdx.x * blockDim.x; + if (id >= n) return; + + if (idata[id] == 0) { + bools[id] = 0; + } else { + bools[id] = 1; + } } /** @@ -33,6 +41,12 @@ namespace StreamCompaction { __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { // TODO + int id = threadIdx.x + blockIdx.x * blockDim.x; + if (id >= n) return; + + if (bools[id] == 1) { + odata[indices[id]] = idata[id]; + } } } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 517a9e36..acbcc487 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -74,7 +74,7 @@ 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(); + //timer().startGpuTimer(); // TODO // Pad int pad_n = 1 << ilog2ceil(n); @@ -87,34 +87,34 @@ namespace StreamCompaction { int* d_odata, *d_idata; cudaMalloc(&d_odata, pad_n * sizeof(int)); - checkCUDAError("Efficient::cudaMalloc d_odata fails!"); + checkCUDAError("Efficient::scan::cudaMalloc d_odata fails!"); cudaMalloc(&d_idata, pad_n * sizeof(int)); - checkCUDAError("Efficient::cudaMalloc d_idata fails!"); + checkCUDAError("Efficient::scan::cudaMalloc d_idata fails!"); cudaMemset(d_idata, 0, pad_n * sizeof(int)); - checkCUDAError("Efficient::cudaMemset d_idata fails!"); + checkCUDAError("Efficient::scan::cudaMemset d_idata fails!"); cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); - checkCUDAError("Efficient::cudaMemcpyHostToDevice fails!"); + checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); // Handle array of arbitrary length - create and set blockSum int* blockSum = new int[blocks]; int* d_blockSum; cudaMalloc(&d_blockSum, blocks * sizeof(int)); - checkCUDAError("Efficient::cudaMalloc d_blockSum fails!"); + checkCUDAError("Efficient::scan::cudaMalloc d_blockSum fails!"); cudaMemset(d_blockSum, 0, blocks * sizeof(int)); // Initialize the sum to 0 - checkCUDAError("Efficient::cudaMemset d_blockSum fails!"); + checkCUDAError("Efficient::scan::cudaMemset d_blockSum fails!"); // Call kernEfficientScan kernEfficientScan<<>>(pad_n, d_odata, d_idata, d_blockSum); - checkCUDAError("Efficient::kernEfficientScan fails!"); + checkCUDAError("Efficient::scan::kernEfficientScan fails!"); // Handle array of arbitrary length - scan the blockSum (on CPU) cudaMemcpy(blockSum, d_blockSum, blocks * sizeof(int), cudaMemcpyDeviceToHost); - checkCUDAError("Efficient::cudaMemcpyDeviceToHost fails!"); + checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); int* blockOffset = new int[blocks]; blockOffset[0] = 0; @@ -125,22 +125,20 @@ namespace StreamCompaction { // Handle array of arbitrary length - add the offset back to array int* d_blockOffset; cudaMalloc(&d_blockOffset, blocks * sizeof(int)); - checkCUDAError("Efficient::cudaMalloc d_blockOffset fails!"); + checkCUDAError("Efficient::scan::cudaMalloc d_blockOffset fails!"); cudaMemcpy(d_blockOffset, blockOffset, blocks * sizeof(int), cudaMemcpyHostToDevice); - checkCUDAError("Efficient::cudaMemcpyHostToDevice fails!"); + checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); // Call kernAddBlockOffset kernAddBlockOffset<<>>(pad_n, d_odata, d_blockOffset); - checkCUDAError("Efficient::kernAddBlockOffset fails!"); + checkCUDAError("Efficient::scan::kernAddBlockOffset fails!"); // Copy the value back cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); - checkCUDAError("Efficient::cudaMemcpyDeviceToHost fails!"); + checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); - //for (int i = 1020; i < 1030; ++i) { - // std::cout << "index " << i << ": " << odata[i] << std::endl; - //} + //timer().endGpuTimer(); delete[] blockSum; delete[] blockOffset; @@ -148,8 +146,6 @@ namespace StreamCompaction { cudaFree(d_odata); cudaFree(d_blockSum); cudaFree(d_blockOffset); - - timer().endGpuTimer(); } /** @@ -164,8 +160,65 @@ namespace StreamCompaction { int compact(int n, int *odata, const int *idata) { timer().startGpuTimer(); // TODO + int threads = 1024; + int blocks = (n + threads - 1) / threads; // ceil + + // Allocate memory on host + int* bools = new int[n]; + int* indices = new int[n]; + + // Allocate memory on device + int* d_odata, *d_idata, *d_bools, *d_indices; + + cudaMalloc(&d_odata, n * sizeof(int)); + checkCUDAError("Efficient::compact::cudaMalloc d_odata fails!"); + + cudaMalloc(&d_idata, n * sizeof(int)); + checkCUDAError("Efficient::compact::cudaMalloc d_idata fails!"); + + cudaMalloc(&d_bools, n * sizeof(int)); + checkCUDAError("Efficient::compact::cudaMalloc d_bools fails!"); + + cudaMalloc(&d_indices, n * sizeof(int)); + checkCUDAError("Efficient::compact::cudaMalloc d_indices fails!"); + + cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Efficient::compact::cudaMemcpyHostToDevice fails!"); + + // Create boolean array + Common::kernMapToBoolean<<>>(n, d_bools, d_idata); + checkCUDAError("Efficient::compact::kernMapToBoolean fails!"); + + // Copy boolean array to host + cudaMemcpy(bools, d_bools, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Efficient::compact::cudaMemcpyDeviceToHost fails!"); + + // Create indices array through exclusive scan + scan(n, indices, bools); + + // Copy indices array to device + cudaMemcpy(d_indices, indices, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Efficient::compact::cudaMemcpyHostToDevice fails!"); + + // Scatter + Common:: kernScatter<<>>(n, d_odata, d_idata, d_bools, d_indices); + checkCUDAError("Efficient::compact::kernScatter fails!"); + + cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Efficient::compact::cudaMemcpyDeviceToHost fails!"); + + int count = indices[n - 1] + bools[n - 1]; + timer().endGpuTimer(); - return -1; + + delete[] bools; + delete[] indices; + cudaFree(d_odata); + cudaFree(d_idata); + cudaFree(d_bools); + cudaFree(d_indices); + + return count; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index c1e746f2..a97d77ac 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -120,9 +120,7 @@ namespace StreamCompaction { cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Naive::cudaMemcpyDeviceToHost fails!"); - //for (int i = 1020; i < 1030; ++i) { - // std::cout << "index " << i << ": " << odata[i] << std::endl; - //} + timer().endGpuTimer(); delete[] blockSum; delete[] blockOffset; @@ -130,8 +128,6 @@ namespace StreamCompaction { cudaFree(d_odata); cudaFree(d_blockSum); cudaFree(d_blockOffset); - - timer().endGpuTimer(); } } } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 1def45e7..f7407400 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -18,10 +18,19 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + thrust::host_vector in(idata, idata + n); + thrust::device_vector d_in = in; + thrust::device_vector d_out(n); + timer().startGpuTimer(); + // TODO use `thrust::exclusive_scan` // example: for device_vectors dv_in and dv_out: // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + + thrust::exclusive_scan(d_in.begin(), d_in.end(), d_out.begin()); + thrust::copy(d_out.begin(), d_out.end(), odata); + timer().endGpuTimer(); } } From 4279cce6ba14d5b6403d1ffaaa723731444bfa83 Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Thu, 18 Sep 2025 13:09:38 -0400 Subject: [PATCH 05/10] debug compact --- src/main.cpp | 2 +- stream_compaction/efficient.cu | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 83be3174..9cf5b7c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 15; // feel free to change the size of array +const int SIZE = 1 << 18; // 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 acbcc487..cd39aac8 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -163,10 +163,6 @@ namespace StreamCompaction { int threads = 1024; int blocks = (n + threads - 1) / threads; // ceil - // Allocate memory on host - int* bools = new int[n]; - int* indices = new int[n]; - // Allocate memory on device int* d_odata, *d_idata, *d_bools, *d_indices; @@ -189,16 +185,8 @@ namespace StreamCompaction { Common::kernMapToBoolean<<>>(n, d_bools, d_idata); checkCUDAError("Efficient::compact::kernMapToBoolean fails!"); - // Copy boolean array to host - cudaMemcpy(bools, d_bools, n * sizeof(int), cudaMemcpyDeviceToHost); - checkCUDAError("Efficient::compact::cudaMemcpyDeviceToHost fails!"); - // Create indices array through exclusive scan - scan(n, indices, bools); - - // Copy indices array to device - cudaMemcpy(d_indices, indices, n * sizeof(int), cudaMemcpyHostToDevice); - checkCUDAError("Efficient::compact::cudaMemcpyHostToDevice fails!"); + scan(n, d_indices, d_bools); // Scatter Common:: kernScatter<<>>(n, d_odata, d_idata, d_bools, d_indices); @@ -207,12 +195,14 @@ namespace StreamCompaction { cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Efficient::compact::cudaMemcpyDeviceToHost fails!"); - int count = indices[n - 1] + bools[n - 1]; + // Get the count + int lastIndex, lastBool; + cudaMemcpy(&lastIndex, d_indices + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&lastBool, d_bools + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); + int count = lastIndex + lastBool; timer().endGpuTimer(); - delete[] bools; - delete[] indices; cudaFree(d_odata); cudaFree(d_idata); cudaFree(d_bools); From 6422620af6fb37f8b3d35821dd37a4ad039563a4 Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Thu, 18 Sep 2025 16:13:21 -0400 Subject: [PATCH 06/10] update readme & adjust timer --- README.md | 71 ++++++++++++++-- stream_compaction/efficient.cu | 144 ++++++++++++++++++++++++++++----- stream_compaction/efficient.h | 2 + stream_compaction/naive.cu | 32 ++++---- 4 files changed, 205 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 0e38ddb1..9daba2cc 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,71 @@ 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) +* Zhuoran Li + * [LinkedIn](https://www.linkedin.com/in/zhuoran-li-856658244/) +* Tested on: Windows 11, AMD Ryzen 5 5600H @ 3.30 GHz 16.0GB, NVIDIA GeForce RTX 3050 Laptop GPU 4GB -### (TODO: Your README) +## 1. Overview +This project implements the parallel **prefix sum (scan)** algorithm (naive and work-efficient), as well as two of its applications: **stream compaction** and **radix sort**. -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +### cpu.cu +- `CPU::scan`, `CPU::compactWithoutScan` and `CPU::compactWithScan` provide serial implementations used as references for testing. +### naive.cu +- `Naive::scan`: Performs an exclusive scan within each block by iteratively doubling the offset and using shared memory as a ping-pong buffer. Each thread contributes by summing its own element with a neighbor at the current offset. + + **For arrays larger than a block**, I compute a block sum (the last element of a block's scan + the last element of the input array within the block) for each block and then scan the block sum array on the host. The resulting offsets are written back to the device and added to each block's output. + + +### efficient.cu +- `Efficient::scan`: Contains a binary tree-based algorithm, including an up-sweep and a down-sweep phase. Like the naive scan, it relies on shared memory for cross-block communication. + + Since the algorithm assumes a binary tree structure, we need to pad the array to the next power of 2. To do that, I allocated another array on device with padded size, initialize all elements with 0, and copy the initial input array into the padded array. +- `Efficient::compact`: The stream compaction algorithm first maps the input array into a boolean array, marking elements with 1 and 0. An exclusive scan is then performed on this boolean array to produce target indices for the valid elements. Finally, a scatter kernel places the valid elements into their new compacted positions, and the total count is obtained from the last scanned index plus the last boolean value. + + +## 2. Extra Features +### 2.1. Shared Memory Allocation +Both `Naive::scan` and `Efficient::scan` load the input array into shared memory to improve performance. Shared memory is allocated dynamically at kernel launch, with the required size specified as part of the kernel configuration. + +Using `Efficient::scan()` as an example: + +- **Single-block case:** If only *one* block is used, we allocate `2 * n * sizeof(int)` bytes of shared memory, where `n` is the array length. + +- **General case:** For arrays of arbitrary length processed across multiple blocks, we instead allocate `2 * blockDim.x * sizeof(int)` bytes per block, which is how I implemented here. + +``` +kernEfficientScan<<>>(pad_n, d_odata, d_idata, d_blockSum); +``` +Inside the kernel, the input array is first copied into a shared memory buffer: +``` +extern __shared__ int temp[]; +temp[2 * thid] = idata[2 * id]; +temp[2 * thid + 1] = idata[2 * id + 1]; +``` + +Notice here `thid = threadIdx.x`, `id = threadIdx.x + blockIdx.x * blockDim.x`. + +In the initial version, each thread simply loaded two consecutive elements into `temp`. In the optimized version, additional steps are taken to avoid bank conflicts (see [2.2. Avoid Bank Conflicts](#2.2.-avoid-bank-conflicts)). + + +### 2.2. Avoid Bank Conflicts +To avoid bank conflicts in `Efficient::scan`, we add a small offset to each index so that consecutive threads are distributed across different banks. Compared to the implementation in [GPU Gem 3 Ch 39-3](https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-39-parallel-prefix-sum-scan-cuda), the key difference lies in how the input array is loaded **across blocks**. + +The reference code uses an offset of `n / 2` when assigning indices. In contrast, my implementation uses `blockDim.x` as the offset. This adjustment is necessary because each thread must access elements based on its **global index** rather than just its local position within a block. + +Accordingly, calculate indices relative to the start of each block: +``` +int ai = thid; +int bi = thid + blockDim.x; +// ... +int blockStart = blockIdx.x * size; +temp[ai + bankOffsetA] = idata[blockStart + ai]; +temp[bi + bankOffsetB] = idata[blockStart + bi]; +``` + +### 2.3. Radix Sort +### 2.4. Thrust::remove_if + + +## 3. Performance Analysis \ No newline at end of file diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index cd39aac8..6169d5ac 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,6 +3,10 @@ #include "common.h" #include "efficient.h" +#define NUM_BANKS 16 +#define LOG_NUM_BANKS 4 +#define CONFLICT_FREE_OFFSET(n)((n) >> NUM_BANKS + (n) >> (2 * LOG_NUM_BANKS)) + namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; @@ -22,8 +26,20 @@ namespace StreamCompaction { extern __shared__ int temp[]; // Load input into shared memory - temp[2 * thid] = idata[2 * id]; - temp[2 * thid + 1] = idata[2 * id + 1]; + // Avoid bandk conflict + int ai = thid; + int bi = thid + blockDim.x; + + int bankOffsetA = CONFLICT_FREE_OFFSET(ai); + int bankOffsetB = CONFLICT_FREE_OFFSET(bi); + + int blockStart = blockIdx.x * size; + + temp[ai + bankOffsetA] = idata[blockStart + ai]; + temp[bi + bankOffsetB] = idata[blockStart + bi]; + + //temp[2 * thid] = idata[2 * id]; + //temp[2 * thid + 1] = idata[2 * id + 1]; __syncthreads(); // Up sweep @@ -32,15 +48,19 @@ namespace StreamCompaction { if (thid < d) { int ai = offset * (2 * thid + 1) - 1; int bi = offset * (2 * thid + 2) - 1; + ai += CONFLICT_FREE_OFFSET(ai); + bi += CONFLICT_FREE_OFFSET(bi); temp[bi] += temp[ai]; } offset *= 2; } + // Set the root for down sweep tree to 0 if (thid == 0) { blockSum[blockIdx.x] = temp[size - 1]; - temp[size - 1] = 0; + //temp[size - 1] = 0; + temp[size - 1 + CONFLICT_FREE_OFFSET(size - 1)] = 0; } // Down sweep @@ -50,6 +70,8 @@ namespace StreamCompaction { if (thid < d) { int ai = offset * (2 * thid + 1) - 1; int bi = offset * (2 * thid + 2) - 1; + ai += CONFLICT_FREE_OFFSET(ai); + bi += CONFLICT_FREE_OFFSET(bi); int t = temp[ai]; temp[ai] = temp[bi]; temp[bi] += t; @@ -57,8 +79,12 @@ namespace StreamCompaction { } __syncthreads(); - odata[2 * id] = temp[2 * thid]; // write results to device memory - odata[2 * id + 1] = temp[2 * thid + 1]; + // Write results + odata[blockStart + ai] = temp[ai + bankOffsetA]; + odata[blockStart + bi] = temp[bi + bankOffsetB]; + + //odata[2 * id] = temp[2 * thid]; + //odata[2 * id + 1] = temp[2 * thid + 1]; } @@ -74,7 +100,6 @@ 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 // Pad int pad_n = 1 << ilog2ceil(n); @@ -83,8 +108,12 @@ namespace StreamCompaction { int threads = 1024; int blocks = (pad_n / 2 + threads - 1) / threads; // ceil - // Allocate & copy memory - int* d_odata, *d_idata; + // Allocate memory on host + int* blockSum = new int[blocks]; + int* blockOffset = new int[blocks]; + + // Allocate & copy memory on device + int* d_odata, *d_idata, *d_blockSum, *d_blockOffset; cudaMalloc(&d_odata, pad_n * sizeof(int)); checkCUDAError("Efficient::scan::cudaMalloc d_odata fails!"); @@ -92,6 +121,12 @@ namespace StreamCompaction { cudaMalloc(&d_idata, pad_n * sizeof(int)); checkCUDAError("Efficient::scan::cudaMalloc d_idata fails!"); + cudaMalloc(&d_blockSum, blocks * sizeof(int)); + checkCUDAError("Efficient::scan::cudaMalloc d_blockSum fails!"); + + cudaMalloc(&d_blockOffset, blocks * sizeof(int)); + checkCUDAError("Efficient::scan::cudaMalloc d_blockOffset fails!"); + cudaMemset(d_idata, 0, pad_n * sizeof(int)); checkCUDAError("Efficient::scan::cudaMemset d_idata fails!"); @@ -99,47 +134,110 @@ namespace StreamCompaction { checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); // Handle array of arbitrary length - create and set blockSum + cudaMemset(d_blockSum, 0, blocks * sizeof(int)); // Initialize the sum to 0 + checkCUDAError("Efficient::scan::cudaMemset d_blockSum fails!"); + + timer().startGpuTimer(); + + // Call kernEfficientScan + kernEfficientScan<<>>(pad_n, d_odata, d_idata, d_blockSum); + checkCUDAError("Efficient::scan::kernEfficientScan fails!"); + + // Handle array of arbitrary length - scan the blockSum (on CPU) + cudaMemcpy(blockSum, d_blockSum, blocks * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); + + blockOffset[0] = 0; + for (int i = 1; i < blocks; ++i) { + blockOffset[i] = blockOffset[i - 1] + blockSum[i - 1]; + } + + // Handle array of arbitrary length - add the offset back to array + cudaMemcpy(d_blockOffset, blockOffset, blocks * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); + + // Call kernAddBlockOffset + kernAddBlockOffset<<>>(pad_n, d_odata, d_blockOffset); + checkCUDAError("Efficient::scan::kernAddBlockOffset fails!"); + + timer().endGpuTimer(); + + // Copy the value back + cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); + + delete[] blockSum; + delete[] blockOffset; + cudaFree(d_idata); + cudaFree(d_odata); + cudaFree(d_blockSum); + cudaFree(d_blockOffset); + } + + + void scan_temp(int n, int* odata, const int* idata) { + // TODO + // Pad + int pad_n = 1 << ilog2ceil(n); + + // Assign hreads and blocks + int threads = 1024; + int blocks = (pad_n / 2 + threads - 1) / threads; // ceil + + // Allocate memory on host int* blockSum = new int[blocks]; + int* blockOffset = new int[blocks]; + + // Allocate & copy memory on device + int* d_odata, * d_idata, * d_blockSum, * d_blockOffset; + + cudaMalloc(&d_odata, pad_n * sizeof(int)); + checkCUDAError("Efficient::scan::cudaMalloc d_odata fails!"); + + cudaMalloc(&d_idata, pad_n * sizeof(int)); + checkCUDAError("Efficient::scan::cudaMalloc d_idata fails!"); - int* d_blockSum; cudaMalloc(&d_blockSum, blocks * sizeof(int)); checkCUDAError("Efficient::scan::cudaMalloc d_blockSum fails!"); + cudaMalloc(&d_blockOffset, blocks * sizeof(int)); + checkCUDAError("Efficient::scan::cudaMalloc d_blockOffset fails!"); + + cudaMemset(d_idata, 0, pad_n * sizeof(int)); + checkCUDAError("Efficient::scan::cudaMemset d_idata fails!"); + + cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); + + // Handle array of arbitrary length - create and set blockSum cudaMemset(d_blockSum, 0, blocks * sizeof(int)); // Initialize the sum to 0 checkCUDAError("Efficient::scan::cudaMemset d_blockSum fails!"); // Call kernEfficientScan - kernEfficientScan<<>>(pad_n, d_odata, d_idata, d_blockSum); + kernEfficientScan << > > (pad_n, d_odata, d_idata, d_blockSum); checkCUDAError("Efficient::scan::kernEfficientScan fails!"); // Handle array of arbitrary length - scan the blockSum (on CPU) cudaMemcpy(blockSum, d_blockSum, blocks * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); - int* blockOffset = new int[blocks]; blockOffset[0] = 0; for (int i = 1; i < blocks; ++i) { blockOffset[i] = blockOffset[i - 1] + blockSum[i - 1]; } // Handle array of arbitrary length - add the offset back to array - int* d_blockOffset; - cudaMalloc(&d_blockOffset, blocks * sizeof(int)); - checkCUDAError("Efficient::scan::cudaMalloc d_blockOffset fails!"); - cudaMemcpy(d_blockOffset, blockOffset, blocks * sizeof(int), cudaMemcpyHostToDevice); checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); // Call kernAddBlockOffset - kernAddBlockOffset<<>>(pad_n, d_odata, d_blockOffset); + kernAddBlockOffset << > > (pad_n, d_odata, d_blockOffset); checkCUDAError("Efficient::scan::kernAddBlockOffset fails!"); // Copy the value back cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); - //timer().endGpuTimer(); - delete[] blockSum; delete[] blockOffset; cudaFree(d_idata); @@ -148,6 +246,7 @@ namespace StreamCompaction { cudaFree(d_blockOffset); } + /** * Performs stream compaction on idata, storing the result into odata. * All zeroes are discarded. @@ -158,7 +257,6 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { - timer().startGpuTimer(); // TODO int threads = 1024; int blocks = (n + threads - 1) / threads; // ceil @@ -181,17 +279,21 @@ namespace StreamCompaction { cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); checkCUDAError("Efficient::compact::cudaMemcpyHostToDevice fails!"); + timer().startGpuTimer(); + // Create boolean array Common::kernMapToBoolean<<>>(n, d_bools, d_idata); checkCUDAError("Efficient::compact::kernMapToBoolean fails!"); // Create indices array through exclusive scan - scan(n, d_indices, d_bools); + scan_temp(n, d_indices, d_bools); // Scatter Common:: kernScatter<<>>(n, d_odata, d_idata, d_bools, d_indices); checkCUDAError("Efficient::compact::kernScatter fails!"); + timer().endGpuTimer(); + cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Efficient::compact::cudaMemcpyDeviceToHost fails!"); @@ -201,8 +303,6 @@ namespace StreamCompaction { cudaMemcpy(&lastBool, d_bools + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); int count = lastIndex + lastBool; - timer().endGpuTimer(); - cudaFree(d_odata); cudaFree(d_idata); cudaFree(d_bools); diff --git a/stream_compaction/efficient.h b/stream_compaction/efficient.h index 803cb4fe..0976a350 100644 --- a/stream_compaction/efficient.h +++ b/stream_compaction/efficient.h @@ -8,6 +8,8 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata); + void scan_temp(int n, int* odata, const int* idata); + int compact(int n, int *odata, const int *idata); } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index a97d77ac..dc28a244 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -63,33 +63,38 @@ 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 threads = 1024; int blocks = (n + threads - 1) / threads; // ceil - // Allocate & copy memory - int* d_odata, *d_idata; + // Allocate memory on host + int* blockSum = new int[blocks]; + int* blockOffset = new int[blocks]; + + // Allocate & copy memory on device + int* d_odata, *d_idata, *d_blockSum, *d_blockOffset; cudaMalloc(&d_odata, n * sizeof(int)); checkCUDAError("Naive::cudaMalloc d_odata fails!"); cudaMalloc(&d_idata, n * sizeof(int)); checkCUDAError("Naive::cudaMalloc d_idata fails!"); + + cudaMalloc(&d_blockSum, blocks * sizeof(int)); + checkCUDAError("Naive::cudaMalloc d_blockSum fails!"); + + cudaMalloc(&d_blockOffset, blocks * sizeof(int)); + checkCUDAError("Naive::cudaMalloc d_blockOffset fails!"); cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); checkCUDAError("Naive::cudaMemcpyHostToDevice fails!"); // Handle array of arbitrary length - create and set blockSum - int* blockSum = new int[blocks]; - - int* d_blockSum; - cudaMalloc(&d_blockSum, blocks * sizeof(int)); - checkCUDAError("Naive::cudaMalloc d_blockSum fails!"); - cudaMemset(d_blockSum, 0, blocks * sizeof(int)); // Initialize the sum to 0 checkCUDAError("Naive::cudaMemset d_blockSum fails!"); + timer().startGpuTimer(); + // Call kernNaiveScan kernNaiveScan<<>>(n, d_odata, d_idata, d_blockSum); checkCUDAError("Naive::kernNaiveScan fails!"); @@ -98,17 +103,12 @@ namespace StreamCompaction { cudaMemcpy(blockSum, d_blockSum, blocks * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Naive::cudaMemcpyDeviceToHost fails!"); - int* blockOffset = new int[blocks]; blockOffset[0] = 0; for (int i = 1; i < blocks; ++i) { blockOffset[i] = blockOffset[i - 1] + blockSum[i - 1]; } // Handle array of arbitrary length - add the offset back to array - int* d_blockOffset; - cudaMalloc(&d_blockOffset, blocks * sizeof(int)); - checkCUDAError("Naive::cudaMalloc d_blockOffset fails!"); - cudaMemcpy(d_blockOffset, blockOffset, blocks * sizeof(int), cudaMemcpyHostToDevice); checkCUDAError("Naive::cudaMemcpyHostToDevice fails!"); @@ -116,12 +116,12 @@ namespace StreamCompaction { kernAddBlockOffset<<>>(n, d_odata, d_blockOffset); checkCUDAError("Naive::kernAddBlockOffset fails!"); + timer().endGpuTimer(); + // Copy the value back cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Naive::cudaMemcpyDeviceToHost fails!"); - timer().endGpuTimer(); - delete[] blockSum; delete[] blockOffset; cudaFree(d_idata); From cb88417832ed976e75391ad6a9d12430159dd9ab Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Thu, 18 Sep 2025 19:26:34 -0400 Subject: [PATCH 07/10] add thrust compact, update readme --- README.md | 76 ++++++++++++++++++++++++++++++- images/graph1.png | Bin 0 -> 61596 bytes images/graph2.png | Bin 0 -> 46027 bytes src/main.cpp | 17 ++++++- stream_compaction/efficient.cu | 79 ++------------------------------- stream_compaction/efficient.h | 2 - stream_compaction/thrust.cu | 27 +++++++++++ stream_compaction/thrust.h | 2 + 8 files changed, 123 insertions(+), 80 deletions(-) create mode 100644 images/graph1.png create mode 100644 images/graph2.png diff --git a/README.md b/README.md index 9daba2cc..17be91e0 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,80 @@ temp[bi + bankOffsetB] = idata[blockStart + bi]; ### 2.3. Radix Sort ### 2.4. Thrust::remove_if +I also implemented stream compaction using Thrust in `Thrust::compact` with the `thrust::remove_if` function. The code is placed in `thrust.cu`, and I added tests at the end of `main.cpp` to compare it with my own implementations. -## 3. Performance Analysis \ No newline at end of file +## 3. Performance Analysis +### Scan +![](images/graph1.png) +For smaller array sizes, CPU performs better than GPU due to lower overhead in launching kernels and managing memory transfers. However, as the array size grows, the parallelism of the GPU becomes more effective, and all GPU algorithms begin to perform better than CPU. Among the GPU implementations, the work-efficient scan consistently works faster than the naive version, while Thrust achieves the best performance overall. + +I think my algorithm involves a lot of memory copying between the CPU and GPU, which is slow. In addition, threads are not fully utilized during the tree traversals. + +### Stream Compaction +![](images/graph2.png) +Stream compaction shows similar pattern - for smaller array size, CPU performs better. When the array size become larger, the cost of CPU grows much more faster, and GPU algorithms beats the CPU's. Thrust still performs better. + +I think except the slower performance in `Efficient::scan`, repeated memory allocation also hurts performance. Since `scan` is called within `compact`, and both functions allocate device memory for `idata` and `odata`, this results in redundant allocations. + +### Test output (array size: 2^24) +``` +**************** +** SCAN TESTS ** +**************** + [ 42 9 15 33 8 18 37 14 42 25 30 39 39 ... 35 0 ] +==== cpu scan, power-of-two ==== + elapsed time: 9.4693ms (std::chrono Measured) + [ 0 42 51 66 99 107 125 162 176 218 243 273 312 ... 410808967 410809002 ] +==== cpu scan, non-power-of-two ==== + elapsed time: 10.0322ms (std::chrono Measured) + [ 0 42 51 66 99 107 125 162 176 218 243 273 312 ... 410808882 410808928 ] + passed +==== naive scan, power-of-two ==== + elapsed time: 7.18131ms (CUDA Measured) + passed +==== naive scan, non-power-of-two ==== + elapsed time: 7.04205ms (CUDA Measured) + passed +==== work-efficient scan, power-of-two ==== + elapsed time: 3.99462ms (CUDA Measured) + passed +==== work-efficient scan, non-power-of-two ==== + elapsed time: 4.29261ms (CUDA Measured) + passed +==== thrust scan, power-of-two ==== + elapsed time: 1.66093ms (CUDA Measured) + passed +==== thrust scan, non-power-of-two ==== + elapsed time: 1.4336ms (CUDA Measured) + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 0 1 3 3 0 0 1 2 2 3 2 1 3 ... 1 0 ] +==== cpu compact without scan, power-of-two ==== + elapsed time: 34.5797ms (std::chrono Measured) + [ 1 3 3 1 2 2 3 2 1 3 1 3 1 ... 2 1 ] + passed +==== cpu compact without scan, non-power-of-two ==== + elapsed time: 33.8107ms (std::chrono Measured) + [ 1 3 3 1 2 2 3 2 1 3 1 3 1 ... 3 1 ] + passed +==== cpu compact with scan ==== + elapsed time: 98.8601ms (std::chrono Measured) + [ 1 3 3 1 2 2 3 2 1 3 1 3 1 ... 2 1 ] + passed +==== work-efficient compact, power-of-two ==== + elapsed time: 13.392ms (CUDA Measured) + passed +==== work-efficient compact, non-power-of-two ==== + elapsed time: 12.5718ms (CUDA Measured) + passed +==== thrust compact, power-of-two ==== + elapsed time: 1.41328ms (CUDA Measured) + passed +==== thrust compact, non-power-of-two ==== + elapsed time: 1.44829ms (CUDA Measured) + passed +``` \ No newline at end of file diff --git a/images/graph1.png b/images/graph1.png new file mode 100644 index 0000000000000000000000000000000000000000..5c057a82895ead88ff47c664e0337643fafa52b4 GIT binary patch literal 61596 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 Zs;riMCjzOC-Y1=+t(E<$(v#jd{|P=6-XQ=0 literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index 9cf5b7c0..b14eeefe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 18; // feel free to change the size of array +const int SIZE = 1 << 12; // 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]; @@ -147,6 +147,21 @@ int main(int argc, char* argv[]) { //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); + // Thrust + zeroArray(SIZE, c); + printDesc("thrust compact, power-of-two"); + count = StreamCompaction::Thrust::compact(SIZE, c, a); + printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + //printArray(SIZE, c, true); + printCmpLenResult(count, expectedCount, b, c); + + zeroArray(SIZE, c); + printDesc("thrust compact, non-power-of-two"); + count = StreamCompaction::Thrust::compact(NPOT, c, a); + printElapsedTime(StreamCompaction::Thrust::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + //printArray(NPOT, c, true); + printCmpLenResult(count, expectedNPOT, b, c); + system("pause"); // stop Win32 console from closing on exit delete[] a; delete[] b; diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 6169d5ac..f284dbc2 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -174,79 +174,6 @@ namespace StreamCompaction { cudaFree(d_blockOffset); } - - void scan_temp(int n, int* odata, const int* idata) { - // TODO - // Pad - int pad_n = 1 << ilog2ceil(n); - - // Assign hreads and blocks - int threads = 1024; - int blocks = (pad_n / 2 + threads - 1) / threads; // ceil - - // Allocate memory on host - int* blockSum = new int[blocks]; - int* blockOffset = new int[blocks]; - - // Allocate & copy memory on device - int* d_odata, * d_idata, * d_blockSum, * d_blockOffset; - - cudaMalloc(&d_odata, pad_n * sizeof(int)); - checkCUDAError("Efficient::scan::cudaMalloc d_odata fails!"); - - cudaMalloc(&d_idata, pad_n * sizeof(int)); - checkCUDAError("Efficient::scan::cudaMalloc d_idata fails!"); - - cudaMalloc(&d_blockSum, blocks * sizeof(int)); - checkCUDAError("Efficient::scan::cudaMalloc d_blockSum fails!"); - - cudaMalloc(&d_blockOffset, blocks * sizeof(int)); - checkCUDAError("Efficient::scan::cudaMalloc d_blockOffset fails!"); - - cudaMemset(d_idata, 0, pad_n * sizeof(int)); - checkCUDAError("Efficient::scan::cudaMemset d_idata fails!"); - - cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); - checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); - - // Handle array of arbitrary length - create and set blockSum - cudaMemset(d_blockSum, 0, blocks * sizeof(int)); // Initialize the sum to 0 - checkCUDAError("Efficient::scan::cudaMemset d_blockSum fails!"); - - // Call kernEfficientScan - kernEfficientScan << > > (pad_n, d_odata, d_idata, d_blockSum); - checkCUDAError("Efficient::scan::kernEfficientScan fails!"); - - // Handle array of arbitrary length - scan the blockSum (on CPU) - cudaMemcpy(blockSum, d_blockSum, blocks * sizeof(int), cudaMemcpyDeviceToHost); - checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); - - blockOffset[0] = 0; - for (int i = 1; i < blocks; ++i) { - blockOffset[i] = blockOffset[i - 1] + blockSum[i - 1]; - } - - // Handle array of arbitrary length - add the offset back to array - cudaMemcpy(d_blockOffset, blockOffset, blocks * sizeof(int), cudaMemcpyHostToDevice); - checkCUDAError("Efficient::scan::cudaMemcpyHostToDevice fails!"); - - // Call kernAddBlockOffset - kernAddBlockOffset << > > (pad_n, d_odata, d_blockOffset); - checkCUDAError("Efficient::scan::kernAddBlockOffset fails!"); - - // Copy the value back - cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); - checkCUDAError("Efficient::scan::cudaMemcpyDeviceToHost fails!"); - - delete[] blockSum; - delete[] blockOffset; - cudaFree(d_idata); - cudaFree(d_odata); - cudaFree(d_blockSum); - cudaFree(d_blockOffset); - } - - /** * Performs stream compaction on idata, storing the result into odata. * All zeroes are discarded. @@ -279,20 +206,20 @@ namespace StreamCompaction { cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); checkCUDAError("Efficient::compact::cudaMemcpyHostToDevice fails!"); - timer().startGpuTimer(); + //timer().startGpuTimer(); // Create boolean array Common::kernMapToBoolean<<>>(n, d_bools, d_idata); checkCUDAError("Efficient::compact::kernMapToBoolean fails!"); // Create indices array through exclusive scan - scan_temp(n, d_indices, d_bools); + scan(n, d_indices, d_bools); // Scatter Common:: kernScatter<<>>(n, d_odata, d_idata, d_bools, d_indices); checkCUDAError("Efficient::compact::kernScatter fails!"); - timer().endGpuTimer(); + //timer().endGpuTimer(); cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); checkCUDAError("Efficient::compact::cudaMemcpyDeviceToHost fails!"); diff --git a/stream_compaction/efficient.h b/stream_compaction/efficient.h index 0976a350..803cb4fe 100644 --- a/stream_compaction/efficient.h +++ b/stream_compaction/efficient.h @@ -8,8 +8,6 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata); - void scan_temp(int n, int* odata, const int* idata); - int compact(int n, int *odata, const int *idata); } } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index f7407400..cfb0179f 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -3,6 +3,7 @@ #include #include #include +#include #include "common.h" #include "thrust.h" @@ -29,9 +30,35 @@ namespace StreamCompaction { // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); thrust::exclusive_scan(d_in.begin(), d_in.end(), d_out.begin()); + + timer().endGpuTimer(); + thrust::copy(d_out.begin(), d_out.end(), odata); + } + + struct is_zero + { + __host__ __device__ + bool operator()(const int x) const { + return x == 0; + } + }; + + int compact(int n, int* odata, const int* idata) { + thrust::device_vector d_in(idata, idata + n); + + timer().startGpuTimer(); + + auto new_end = thrust::remove_if(d_in.begin(), d_in.end(), is_zero()); + + // Get the new array size + int new_size = new_end - d_in.begin(); timer().endGpuTimer(); + + thrust::copy(d_in.begin(), new_end, odata); + + return new_size; } } } diff --git a/stream_compaction/thrust.h b/stream_compaction/thrust.h index fe98206b..d5c82ec5 100644 --- a/stream_compaction/thrust.h +++ b/stream_compaction/thrust.h @@ -7,5 +7,7 @@ namespace StreamCompaction { StreamCompaction::Common::PerformanceTimer& timer(); void scan(int n, int *odata, const int *idata); + + int compact(int n, int* odata, const int* idata); } } From 964f023dcb5066c94d4d939821f024cab576c946 Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Thu, 18 Sep 2025 20:21:45 -0400 Subject: [PATCH 08/10] add perform analysis --- README.md | 16 +++++++++++++--- images/graph3.png | Bin 0 -> 19455 bytes images/report1.png | Bin 0 -> 51712 bytes images/report2.png | Bin 0 -> 11568 bytes images/report3.png | Bin 0 -> 15418 bytes src/main.cpp | 2 +- stream_compaction/efficient.cu | 2 +- stream_compaction/naive.cu | 2 +- 8 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 images/graph3.png create mode 100644 images/report1.png create mode 100644 images/report2.png create mode 100644 images/report3.png diff --git a/README.md b/README.md index 17be91e0..db5402cd 100644 --- a/README.md +++ b/README.md @@ -72,17 +72,27 @@ I also implemented stream compaction using Thrust in `Thrust::compact` with the ## 3. Performance Analysis +### Block size optimization +![](images/graph3.png) +Based on the test, performance is best with the block size of 512. + ### Scan ![](images/graph1.png) For smaller array sizes, CPU performs better than GPU due to lower overhead in launching kernels and managing memory transfers. However, as the array size grows, the parallelism of the GPU becomes more effective, and all GPU algorithms begin to perform better than CPU. Among the GPU implementations, the work-efficient scan consistently works faster than the naive version, while Thrust achieves the best performance overall. -I think my algorithm involves a lot of memory copying between the CPU and GPU, which is slow. In addition, threads are not fully utilized during the tree traversals. - ### Stream Compaction ![](images/graph2.png) Stream compaction shows similar pattern - for smaller array size, CPU performs better. When the array size become larger, the cost of CPU grows much more faster, and GPU algorithms beats the CPU's. Thrust still performs better. -I think except the slower performance in `Efficient::scan`, repeated memory allocation also hurts performance. Since `scan` is called within `compact`, and both functions allocate device memory for `idata` and `odata`, this results in redundant allocations. +### Nsight Analysis +Thrust: `thrust::exclusive_scan` and `thrust::remove_if` +![](images/report1.png) +Work-efficient scan: `kernEfficientScan` +![](images/report2.png) +Stream compaction: `kernMapToBoolean` and `kernScatter` +![](images/report3.png) + +The memory throughput in thrust kernels is very high, meaning that data is loaded efficiently and bandwidth is well utilized. My work-efficient scan shows much lower memory throughput, meaning it does not achieve ideal bandwidth. Furthermore, the compute throughput of my kernels is also quite low, meaning that threads are not being kept fully busy. Thrust's scan and compaction kernels, instead, achieve relatively high compute throughput. ### Test output (array size: 2^24) ``` diff --git a/images/graph3.png b/images/graph3.png new file mode 100644 index 0000000000000000000000000000000000000000..d73f6a02d2e44c89b689d40e6258cf2351773c22 GIT binary patch literal 19455 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 literal 0 HcmV?d00001 diff --git a/images/report2.png b/images/report2.png new file mode 100644 index 0000000000000000000000000000000000000000..285f306d283797d05b8e535fbb405df18f6a61c4 GIT binary patch literal 11568 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�A=Zdv6?h=lFj05Jf^S8Lp1KygsK0IDG z4*9(a92M6>N^GkM4~)*|;N?HL4Y* z9nPnfnVq0v1B$ghIxlUcw0yH4NsR4X@6ffCuz7-}!%xKSQFz#BDF^Xi!A8Nrit7zK zyO&BirYqh?`aU?OJ>Te3KURn2%x*JA+^kLqgAVYEd|Nc~=SE2*`Ja=?tZF$C#eQ3h zb$nOS8GBO@vFVr(?r_9Q9?JG~Qii494H*707`#DI+cH5j2hPFn#FA(!oOqgvl6aqxKIR?%BmKI_ zQQoHd2Sn>++%)2vf9gY+nwFN6x@)yB4j%@ceug#W&U^3i&%z;t7nv_vMD%!<^BU1Y2X#Arskz zt>)k*wk2Kc_N%m7NZgpNXx=~nl#F-$>ZO;(NNo}gy?3xIvm%u5_Bo@DEV8qbq<3&q zSvHvyOGTB?Zg^Azghc#tFTyI$pV4QRP|50UlPUJZ$01aDP?$7dq2r z4VQ}LHZ>Djj;MG@7M%2CAtbCWNou_2nX29;zzvHxFz!W3{rXg>y~Ek>>|g;L{sp^@ z^P&Anq?dDi^MAE-X6#&4pB$TlGR@m5VNcPmg+GEy&bqUD0s^8F_*^X;{B5M%vw;uI z-JM-VgNWW%S|3Gto3+aM*!)jmX6zM$k=t`6Y&dEY?hovDA#9{g%>NT)2`aR%JlW~s zth|^xfZOhJFxkA#wA_?A2f>FcE;jhcoS}<_Bn;`Ua}FBOc-@!l^-t%EUk_8|Y(+43 zF}VSE=+su>QKI7S;}TdbQO`;-==mZJfp+JH95?% zYPsje9G}%@{GIk({KsRnfG3+xXLhk}xRsJ%8$6K0+?c5^Xk5SLUw#S5A_6;cJsjKz z`k?A*G;d%zhKh7p!}L##$pQuJRJIw(GE}fIjD_#UKRQc*1R*|nFJ^i@#W&fYv0RT7 z!`_V_uiCKh;XpEYe0=J~=jqCz6TWDn`<11hf7!-5r4YbU|88RH*r~P5p8DeWHahp` zsT<(PcWYwm?Pw3W@28yV`8*dMK0_%lbTKZ18aDE~KV7|S_XeEO05Ygc15djFbmP74dPdrc{y0myu$01S%^zanrg15>B1$+(JHb)98FKn6re)R*-TfY_2*26{ zVA<6+VxoAzSU49gcE7WcqW7t@U74)1Dq}gj*WvBXzdoOTEEtYh&1Tx?$l*7k+{D`a zCyX@)n2*qksWco;f3O~T{{Ebq2s{UlE`Hp;WD~L!@wLxCDwt44c6oFNq!5-~_8bwV zdX^GiW*XV^nFt7fPR}b}>Wxoxmy6`raGFaDXX~SXrtE}8w}00zyMjsRZ*V8z?q6UA zphtn)BbPdd?D%+9(FKn_Dkawa%*`~D=I?f036?Y)7?xd+)!hP%PFCIZxKd8ti|qNi zG%ejVysZxzEH*@T?;EdOL6gN;`|i&jMGQ+H8JEkTN{xI?U)>2GJ3X=HOAV5kCJ^*m z4Gl_b4cX`D`!Rjg>rL zIYph7o#M|G9@d)8S(o0ri@^FxgEu=LtV7>x{_(e>PTQrz0l-z8d}S(o%FTJdN8bm7 z_`mJmjm_%59jeo^Gk3m(`-^e!qUfW|>9_N9xpu3JYdOBIh;IJY$?fNJ?fyBeV)Zu` zW%-Ja%EPOf?=0Sik`!^A#cmKbPrmNi z`r$ZkXZdV(TrPzzbKtb+FO%K)3J#@%=2A+puaIG9jF?k29=32d41UNj3$Tkg6XYAY zm9q?B??{R~+O)X{Nu3b2XS(o;T)RU`ddnd_Z*LajNd!?1)_Y; zLDAi%&CT;IpFDEc%Y60DILBt6G-GFhjqm&Op~J}>4e!V6?TRZXT|s=?)B|Qb%4`uqpR-(!Han5vIr&?=Ivq?Y0l3MG-e?!&u@1UWR(k=GlZ_oo zno9TJ0qU|c;{kO11NEOf!7<5khzHp@JG)7~y%kE}Z_(of7sSK1+9yE~N2D4Bsr#y9 zNhqO;BrUZV<4C+o<&%Q=VPFr!t*t32PKeBF@7>d6DKKfP*B$zqnEofVy@*&INj zngk0&%Qx6;QddL=eQ3QqgzAJXyMivupAg=Bc+(LJq(0M9TAQ7YKCJ>KlHqT zl-Io1aAW*|y z3_wQ6*#uexZuVd`fwvu6DII*h@^xGQt@Uh}*wGy-k8JUz9IVqkLsHFoX3oq&cX*F3 z=WnbY6UA6>9;Tkv#Gp9j$j;^bQ-JOn?!C8f_Tu%r>XXOXV_8bI*FK&5ZWM>M974ol zjxV8(j?UA*-n+@7-8^Umq^D<7tuPrKJEy0o?+4kg(yGno4XATN+wYxJD-TrV8pL(b zE|WKnxw@-2OW~6~{?>@03!R~6{Et*}25;5PAIaYIl#cU`z4xvplh`MwTODj2WDj+kWj^$41V>dPkSva|hvLDaNgPWkcSF5W2DvYdXTp zrscU?ce_$VPKAK-=3oM#B`1RTS(OengTYw7r^KWSk4ibs`kQ^H?{CeO0(62o*&Q)q zKc-%#KC7T~yD;rsKzpJ$M^dle4&?ybv6dg_j4Kd@Dtwi;+MbPPPI238pZPOx@NRwU z-LraW8C^Rgj9tH63Y?;rAB{~Ix;DTuY7V;d2?v23s|P826VE6%PO1=tBRmz_Kvf^u z0V6w^cfZ4nwu8rNI7t*;8JT4AhQ@6Ru5gw6ET}Wr$UsL1v;&sx&?0$E>55>?&N1Ug7480He=x{7Tvkkk8?*6Y z&3diB{#((}itPI8Uex|;Z}MIn z_Y21vV?}#>gNVBdFF@mL!(buO9If%g8w?y|>_eLL*HK@px z>P#0!95;u(duJVEA)h$xM@oIi`}_oo1~|N~`?0=`cE6u?rwdU0r=AV@v3$P|!qE}Xj48?6sc zNB&TB)q52nCXYQeMjrF*QbL6>%n@MbtOI|$Qr%+h%D^JpPNI_`%5mxfgFlS zKp^-cl~->M+cO}0GGw`yCbb8f!HC!ab&&KY`AM-X_|Pq{R#@L}_wLmMNT`FPmw-9; zTHDBZ>s@5w$0gSVlR5+UWnIUD&Sk>Cb!YJj>D~(fdjC6-QblAGeC1#+Gzcs@sc9;Q$vBBFn1m6W|C1C{2z|x|zENwe(dDnuNr~LhVPzyrX2C9-iN(BW?%}>~_>2 zzG;pU;!y~{H_CQ$@_jvg2Ka^1blVSXjXgB&NNMucyH}I;6`#d4lu|ny`MUA(8F}m# zy!#w7l5)GW75qwE-k44GC$sBJ{rzMt*u6yW*)O|%gE^kgz?ftbnK@U@<;xsiI=fJx zet7zoa!oGWf2f4idsb2>T2qNsr3`&gEBkPnmK^H90p;;JthJZrq(FICFc4R(?r7su z(C{Z0e|P#%X#_W2Fd{L~r-^^~pl^QS_Km9BuM7z(Q z(~**55(&RHn48g)9T2OOdT!HyTv{?~Ix)FRT4i2}_m4p_nkK_Lx%XF7f2*w%qH?i{ zmOGd-S0SH3c7LL&r+QXjM)a=G%3!oNGvf>~`wtA!75M3h2fkESpY5(d3o4Adb}xxd zXwR%mOi4Y(Gf9J8@j0utMVngxHx&6~W=EyCm}TAKNvBzRcWcsxEp<&im4(v(j4E9= ztRI`50-;cUQ(4Yv>TKxk`Ye(Er|koYotR>Wx>g&8;7j;y3az%CI%0B!-}%>{YwgO6 z;bx5+K^Ip`yxG8o=B~z1r0B#$B3ci4w5_PB$~)MnW&ZsfF@146i4DD{0P+&wJSapD- zuKTFo(YhrfLEDoblzAMYR1<0vXxOP($EmHT1Y-taE-~At&ZLu_D5#OI+>Mgzh*Ir3 zq+f>@7XZXiyf^c9CdAD8@Y+FwbJf)OSv3?UosoTQgZ8&mZwRh)D#`Yanm%a`yG?XL zC`$6uJ&BJyr0g)uwxFpjiaMfVvS*fmB3%9{a#Zq&PrJiYI2+8PEyv4N*pgDIh|@hc z*eA(6v&(s45?XW$P5J}n*!c-2lZA3nJ{E_3+3^xFIn1ZgyznE^95as}lk%k%bnK-OXgQBm{oydVc!q?? zRV}&BP93&FT~c>rj5hcNVWeSF4xK!`P+8OrLpC9YlA7~q{;MyO^}&br-uM}9ZStAN zT*4*6oEaXtkCJ(pXhQYaXpRbD$VSur1!riuyIu66ks<>Emdnv-s987{Jzb+<=0frS zhGg}(Wo^4D;5?0D5#kyhAc;&qeX_C~C zS!zjyp|2W0=uDNI8Z?el2DipW%iMEmVRRJZ?ezMgi$o8v;(e3mW`XIQ?7y7!pl#4H zVH=aw-)K7VyRvH6_4rX>U8P1E2U+k1v&C9tQ+T1513X7#pB}SD6;FKNFM$n2_JqD+ zqX{WxQL_B3v*k&+SuYsuSVw^8=cY*7yr@a3xrf0s5z+To7C)dqvVq~$f8Kh64~~zd z@prXy{8FO1+xSE92f}jtQuH6(r|dH2a<&E|E6mPZ-+J%=NP~HIMik6CWz5roCDCr@ zM=r9{iHdPsB&MC40ko#MV+8|9TMYLDXDAOSKS_#n;m;G^1DHh`Y5l)! z%AW3L=!vKkirCB$KJ}SFD~NNcS>Jm|ge||A7~eWSw1{hp!`z7SBq1=u@ODRNZWDHoSbZMdW8!JUz3ZRCol>_Gx6J6@EnD()7Iu)S|{FY(E3|)Qf zxCss!g!13ojmjiVC0oPwBfjLys}Xw83WF)d=(;%t1%#U)&p96+|7{FThjOpC0w1iO zokEWP{;&w3WG zbGjcduQ_}l)SjrEeCN@GAAJFE%O5w;PehH6_Qq(`C7>R|{7;YHTHk<=tQbLwNgXs0 zf#|UN;nrAVQ&K__77E2fu6}iLO=v|OzWa+l=Zo02f+JG7aV|pPevYC|a0f7uBn3Z8 zl#SbeYEZ4hDsSWf2ROQFyQ~%&c)efdc#~t}|7gca{cI&&g&pA`35+$=2MRXfO?JNC zj5=G#hJ*e{`u!;gbRzl%3{=C!{~UUmq2`Tv6}H^=u#EasH~&L3Yi36b?w%fq{hu;O zmgQEfozHE?>7>91Z`acfoD&fVg&;`a-}v&jHf|>@61JX|6{4MYzsRmNNQv3AL0JnNghctbf&#r>P;LVXTxk_DNY zex{?mfdk@-|LZ@fzh%K7s{4OSzrMdmM?IXn-yLzUjGr4TTXes}#8fR_s9Bt}Mu|5A zri?F$Go&g<3I3;bf#(d4H+`|VlUQcJdu8_cjw-?Z3Vlm{& zSmSQnbGvxVl3+C|yY5BB?K9Q~K+*i9$0lYIDnUaCM%(6R9QTIL`c&_ifGYK^--^>F4I)S!Cqsw(T1fBSjT}5et&F#o=O(4O+A$$Of^ zmCcY}(!Nh4!Lc?HzltQktE7iM-z7csLgUs1Bg59Rb=oXO3O0qxh zw;uXnMU=gxWmQA~ztkBsrkns@sPa_Xj|-gw(Iu6UYz0olBPa%ekGoIqnBU)|s4CvL z31u_E(Azh^7xzp9kqEt!j(Kx9<7#b0Z;^$<4~W#Jp98q?UwiO<4&8jjA42@*DoH{& zLY>J|W{MFCSkGH@B_6-k+~ead)7O~`mJhaD`2rzFR=O*wrLo(RzG!MjlEu1v3dF&^CX+ZXQuzH@>as!d^{%N`wDg4gZ3Z?dzPtP@4;hPtE| ziOoR_RY1C_qbk|_AP6H1M}MgxT%19+Exdupbw~ZlxUVlx=^`_hOJl)g2y<3!9fIf|?z){`hx#PQaR7uR2 z<*{%dee5qsGRpE;nz8*YMhXTNN0>@w7Z}7XM!nyN6nS?nB@zkcNX3e)9!t7WHnLaw z&NZL@tR{5-4nm*WDZ>#lO2Ng_CHGDtgsR9=p1_#NU$=6PDSNNz`Dd7pdpa%)K4BEN zg^usX^p9C*@Q^3xRqp(S`2w&aQejuVmPnXq>G>kl3ilGZz0a`O+;H&iSn+cH-sW4v zC9cnph4yu(uf3k*S2g}vw8UGets8Q>Q^f27+)6A9kUNatKkkbV8be-#7+D{pYxlEFDe@>FtV^R>MGBXXDo) z2K|BjTFL@;bAP^MBd(@%b`ggg4AS4a+yP?A0qeEk{ zkeY{&`Hb`aKC*7Y9Q3T~yfuh(pF(=AA! zt+FHL4XCF!LBk7dThzUGW-vI)axk7}+U^K7o4*9WTt=Er&lK|D=TT86N{Mq*pD8r` zJ47Mjl)|R^-F_$%vc%d^_hu7kR7UvGbz?0{=dK3_)c6yphTTz z1H4Z6oSKjCf8vSO4nwBUf$i9<+F}h(@th1TX&zC^3?;B9eUy zYZETV-VbJ`b5BpW;ua}`%HGb=LQMy+qf5z(5(e{P*9@R8&7X7Jm~--aB%5eeLn>N4 zCqdSr20x)%kVv-o%$Ls%Qz}S*D54LYizRrc6eu1N4nsmtf4(@q`Nd94VP@_&`v~51 zDd)L0!pZUX(V7nP<>vJ@z5a^cw5M@};PxAE8jHI>B38cv{|V{$cHdR_03C*WRx6Ny zpkA!I2qRroCDcDUYT@*)YN81vK2PUq8d9!>YzwZ&KN4pXX^FsI%OWG1m~h;%rx&5E zp}{!t6uKp*5X1O}J^7n#R7nVMMVbz08b`G$E#c!XcN0ev#y)$%lk5(rqu>+p+jHltqv_B+w6{D`eZcZhLTUlp%U*Nj%h1gS* zB4rqzC~q4xTAAcv=!mpvZWY8yMq)FhhLXj_7<+~bUGpf%6?SsSI`G3Sawt>yiF7yHpU*_wAj zBHL;u*fHK^h9#2}&qFQT?+((=CGUQygKWlh!zl>vDS zDz&8sbuWQukru_!*)R5ckXoNUS0=J4cPc*26zNL=SxupWB@T&7pA_Y(V+Wjgi`zs4 z703b7oz&%VZ5abe4ovE~^|HP3xa`diMvjUr=iGf))Av_JC)&juCe%y`{RBMQx1;JWtJ?jS66DBO;b?fo>VM-eqP%1 zY9p+~Ov^%arHudpey3D~&ts7$dlaXbZS5v;{3G|U>8Ry-aoX^rhq?}1psJu3u(Rp2 zhVieeAY(K-lfZ&bd1#(Ll`lkZs0)eYtNZ!B&fm?qG{qS1Vyk)U6tu6JWEI4Q{{+*1;?2&KQzq1W*?u$p2XUgB* z=9M!K26g|gK3TR`H$1S?K70B{p8eMouWG~ZwmE&^y-&jv+k&HR0@@`_k_photnRy7 zv8rnDKv%UhU{rp>8)df9@d5u%`ZbA_WnE& zcWs&QZ_{LH{`eD56yz|TBb_Nu|72niEqd4Zs6eSdK8z+BX}vZUM=p1Ta{%VUE3yLo zvw>~Ot;_etw{MI2&n^RUw>hCk+fVOP()iHM)-e00(qS2`8w}{O4SLae^8j~SDIq!1 z_-a;ablu@cyDTh&%asBZ8u~51&Ka0#uyC_Vw<;1fFQ!v;?;8a=6~XGvo18<2iStWg zF)n%EH_m9_M)`7!Ks;Tag#6MBvhE`7nCe(WO1+1NhiPcmb9c|o6ptYW-m(Qd0^uVM zJp}mP#Rb<}XLpDeHb+X>STxz}$&t9UEB>JHnz?}oO3Oi;9|Nn~g(p^$XcH)?(d~!4Go{)?C~%W)>Nw+9YnOhJJ69^q zLS-1NLc`)iDZ#pf@4Va!>Wt&sXZ8b(B=4zph3M_kkGTC03C~{VAbLIhH;vV#=FhZ@v!HoI++idds70Q0~gkBf}W*MpbaNr4!~uBU%%YIqHL zgZVz@h){LRa}C-Ue@YLVusiRqh9sud9aYrSLK+*{UWEoRZe#0i%YFGGK}9E45|hDr zD+v%KS?zspl_($--ZxhoWh>CcJ&onMWA^wS>LP=nKf;~(5Kt%QuORzSMRTd=buAhi z`u~ET*zZuFtla+>+F1%<1X<%I(ka2tDHwu1b$?t$dPpok&vz5DbD$_OcL;^SlJpw| z)7cpV6%lHp$_ZXqkVd#+g&OC-ny$+YLX~2Fww38kjMeP@AwzQ+h#r&9IPnlL#|?sf zu07?p#YO?Y;72e@ArtB9bQBPjL7BLp81rirkui_K{COrQpY?x{naZfswY%5)n#BJi zHDo0O-p89!#_CH@F0_0OKieaU66)vs=v&LGt2x|Il+cV>aDI+(f8tjP{bRHxpI95P zRirwdtFqO!@K3qc2Ih&VO1yJKby>Nu37Y-VO(0xI3$-8q>x)a@_k`iC6raa7kD+9; zmw$4vcZ}}}w+v`IK@^J$Vr#BvOM=TJ1rjn@WlazH$e%j4v&&}ZNU#Z|IaOGR(TsIb z(&@M|FUf0)pt%I0*Z)*hM6s6bmMe^PR{viGZN7AA~i%`&|>xBd^IGW zgD@imS*i1kFCwOhN3q#na>(5g-RgQYm%yDsIHCn#ueYQDh2i^Lu9IdX68*YPtc^Nq zzLkTvDL{B6Fjmm1Jd7;AiJ%cm-vH9D{NWEb839e%`oVM`GHUjER4VuSZ$4??fB=Ei z-X5Y?3d#=$EKlEVl+7BX7tz=E%jWH#%ku>vO?=f(o22~UE_J!nQS+L!vV0EYUxiJH zM)~>Q5$UyO67R30Zn1UBg%5FDJ*)x{;dP>oZ083IK|{$&v^vy+S0nqJ)X#*emc5r+Kau79NGZ^KWS zaGJLzD}a9;kvr>=XMPs=(6xQSdUZIVxw_XeuYbelQG9gSy7hf3y>0(C&Ai818G-W( zd$Cj_REQqaLa3A{CK!4Zms@{g7YKcjUrzu6l%sUZQ|9h#h1aE?JdwyutoWmhfwe~F zD4?}aB22(tj8)5d*VK|S>Hy}T&3+*+5W}sxb?-%vOkfjggz(7E9(0d*fcd*EI3-io zqxUw_?$3xnB~h8SCX2f&9KpxYneJkvOd5-cT!I$ew-RB#5JHTC@%(KJwgUa*Bqs#2 zJ=G)*92;lVH`(7n@ZZ5=M!mauoR*Pao=(Be?6$fXYrWg*5lc?{8Ww@%1yCq2h9_a+ zb0$qV1yCNPxrpjo{bnOgJWg<6xcclIU1v5T5WnX!T=sU2*0hw*0#a}lLf9%!u>Se0 zwTnqmw9M|}S~O*%lf2^kIS+7ON}y!%3i;QfxpeQmK1S&+!)_Hnh3%g%y+xEj)I1=4 z?BM_URyVnS1HKe}zMV(l4o-kafSrB;DVE^MIkZtvbewyZqB_l0>6X1y{pBzUIlgVe zcB^9-R`jr8{kWBtUM#Zs_THa6xW>J;2lhiMI&Zsf*5L$Ovu{6Gk$m4fOd3Y`RxW1W zJ89d9?-H(fxbQ2_kAWM+*2!JDq$^MU_?*rt1TfXy6?#lJPju*H_{ji*jql17@XIyb zJzd@s(N!cq9w-s(<=LB?c~$k`PEBhJf2y2tBW4k>6`T4Q>FU;)1N%oEC?a1`Re6dL zf+_-#ZK_W%M2!M=8UUr>E+qjcy|u^K{_fV&m?lH#W$?;t%}6P#Jj|6Ykvr}fhqdyz zwg>GwJlKRf!dZC$-MvLWMEFtzLts31P+_~<6j?X)Lf<70i=<^%+k{2K_<3*P*KKl+ zQ(CZ4y)LNCl(wDa5;UdCAy>rc>64@1@_@sBX@r`(-oShXiwF}u+A|nxIp5-_MJREY zWhQR>qIyL2RJdspxI9+C^-8(RU)-spahl$TfW$B&MnipA&bv0&!GR+>p6lmgfu6ye zSi6o6IKUNp&g;^Xjba~(49Pac)X7scdmnfOd*-w;-^cGbIk3UtqrZJv3^ZAkxqmgO zfmsHTRrA~Pi}<2i*bZ(_jp%`cbm}U6XEAnLCG&nsNcpS50Eebk38Pp8t?MR4Fu?V4 z&4s!9>os3R5YG4CCHifnqd~JVcrWQhn`ghiNYe zea#OmMZUh{Xw>2!Ypa=J`_|2Aq%P2WDVUL0n!>uOsu(ftk=%8AH*!c19xe|y(BUlK zrX0iqjcK6apts8He~phOE28(Bl=WBO9bG`tR}C`8 z4j+TX>kf)gpvx~G$Y20|aZI_BT5qBQNe-D)_UDgLzReNKj^#U+e#p0hbGd0!Ost5s zz;o7$w)n-wjzjO1-=mhr(mxC}L-W{YKCokm@C<;nSxt`uOpYR*PMY3bqtu)+$TVpG z{%Ev6)?RRWcKFYCh{;hPvm9Dh@N2nn$^78gLdZ^m1vBrRGMV)=jFK5f*-|oIIrTZ4 zSm1|IQ5J*KwfvbyS10DY6r-Upie%bRWE$dop?8yPMs_K5ujK?KB z3yuZZbImI4AZEjA-#i9y6T!VgX845uU(GEz6eWJ{m7SkMKN1zVjbtQ7K+7*;(We|7 zw9p^~)X`CJ1$6s*GRmLu+wGq?z_zficNW24ce}U3o_9gXY{*B5ootbabm7KDq?mtx z8<8QZ=QdW=Xgaa$D~6{X_2%##)ta`u>TJT{G$B!W@z3=M;2u;|ia|(+|BJ*|m_Eo| zvhDM0^-ifl?mm-4iaW+{qff7gNg2u_1hCsK-gnv;ej8S_6l%gD5GHgorQW`CZRyBW zm*WJ{f<0#CRzGzhe%-R_y_8a!k~8WfF)hhOOscL&3s<*t!7L;9b%1TpD1g3QfnP;Z zQoRu~3JnvUzRMVKtmzID27BnZ$=Z_c8tGABMa!?D){67!0YPfyjX@qhu@LF#%yJ8= zLNZEx=fg-ELRp+`13AQf=g|El&r%xFoJzxS2@sIoFq>mhUV36j(=6*y z#D0N%*;c$QQ$icI{xUYM*A+k}ezmin%zx3pY(?s=LU2T8EF7D#W0gOK`+hENkX5^a zd{M|0oKp6Ksk&O1f4 zcb+`;OH?oP)fT{}r7RQas;s%v89*VV_khAvT?&`vj5WoW^(7hsuDsX8CAeZ3y1G?W z$bpB_f5$~`8nzw3SqZ!^UU!8VFqyZyad{Ewnu${W=Lm)V;A1Kurow|K5wWPbI4p1w z$aBdDK*(~5-ZqpaFL%MLa>=vprE_eJ%$%k{kzQzvViWjM9qw2xE+V0U@F%lfOvO=e zP(-0)q7*wuXx>)exToAP4yZFBusnrJhV=4bNst?D2#7I9Q_@IO@n#t84{T?7WBSG8 zS}ZQdSy5!duoYiNzqyMiz7N?YWx2aT9iQo*K8O1082LytWisXt%-{yH2u)Bo`bD*) zs||iw(98SGr3Ef7aGjvhz)4B9%e*z7x*_ot=6~1iNZ|fG!Q174o`N}d=gWMV+oVZO z(toon3i2^?sl4c^9nZ+V)|KH(m}8z=OS)G1lA;@zf2e4%P-|z`{HBessP7MTeV+I4 zb=qCGkCmAssSE`I2e{%ca}8PK#NiT_)Nc?dn3`yMB8_smIS@!FH$aEC?>bATf- zCC+ZdUo{Tk1>x~pX0=-0lENE~7sIzFtFhj@R+P2cq9=>eXf*`ETeS-N963Tu$LCg% z+4E-Ypq@o0&c8oTK=C*=`|r`nX69b4K=0V9;FMecY;j}wOJErrHAZZH#EwQ*Sm@)u zS5Zeo%ys&UPsJ@ze^|xan~x<8-sjk@IwQoI>yz7(|JXr_8#W%(2{c!CM4&kZs{v)N zj3APT8lL=WY$OT_?W#fZ1>82YrdbQiXoSr8%=uR&oa_XN3u83u$awg^+*uHUzzBs9 zh@Ja`i=fQSE}<|MjO%QTtb|%5r=kT^gyCin4Wc;(rQ^|H&%;2}XtZ`?0!dL|8}Ru{|Bz8ip+Yf1#qQYEDC|Nj7$G*`I* literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index b14eeefe..5856dfbd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 12; // 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 f284dbc2..0da9b82e 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -105,7 +105,7 @@ namespace StreamCompaction { int pad_n = 1 << ilog2ceil(n); // Assign hreads and blocks - int threads = 1024; + int threads = 512; int blocks = (pad_n / 2 + threads - 1) / threads; // ceil // Allocate memory on host diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index dc28a244..e973e301 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -64,7 +64,7 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { // TODO - int threads = 1024; + int threads = 512; int blocks = (n + threads - 1) / threads; // ceil // Allocate memory on host From 25f360d1236dbc42a6ce96a85b05284fe3620a35 Mon Sep 17 00:00:00 2001 From: luoluobuli Date: Thu, 18 Sep 2025 23:53:07 -0400 Subject: [PATCH 09/10] implement radix sort --- README.md | 88 ++++++++++++++++++++++++---------- src/main.cpp | 27 ++++++++++- stream_compaction/common.cu | 23 +++++++++ stream_compaction/common.h | 5 ++ stream_compaction/efficient.cu | 65 ++++++++++++++++++++++++- stream_compaction/efficient.h | 2 + stream_compaction/thrust.cu | 9 ++++ stream_compaction/thrust.h | 2 + 8 files changed, 194 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index db5402cd..2bafa231 100644 --- a/README.md +++ b/README.md @@ -67,28 +67,59 @@ temp[bi + bankOffsetB] = idata[blockStart + bi]; ``` ### 2.3. Radix Sort -### 2.4. Thrust::remove_if -I also implemented stream compaction using Thrust in `Thrust::compact` with the `thrust::remove_if` function. The code is placed in `thrust.cu`, and I added tests at the end of `main.cpp` to compare it with my own implementations. +I implemented radix sort in `Efficient::sort` inside efficient.cu. To do this, I wrote two kernels in `common.cu`: `Common::kernMapToBit` and `Common::kernRadixScatter`. + +The process works like this: +- Since an integer has 32 bits, I run 32 passes. +- In each pass, I call `Common::kernMapToBit` to extract the current bit from each integer. +- Then, I call `Efficient::sort` to compute the array of indices for elements where the bit is 0. +- Next, I call `Common::kernRadixScatter` to scatter the elements into their correct positions using those indices. + +For radix sort, I just use global memory because I'm lazy :). + +I implement a thrust call using `thrust::sort` in `thrust.cu` as the reference. Finally, I added two tests in `main.cpp` to test the correctness of my radix sort. + +The usage of radix sort: +``` +StreamCompaction::Efficient::sort(SIZE, c, a); +``` +The given array and the output: +``` + [ 2 10 6 37 7 38 48 27 19 31 40 30 31 ... 48 0 ] +==== thrust sort, power-of-two ==== + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 49 49 ] +==== work-efficient sort, power-of-two ==== + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 49 49 ] + passed +``` + +### 2.4. Thrust::remove_if +I also implemented stream compaction using Thrust in `Thrust::compact` with the `thrust::remove_if` function. The code is placed in `thrust.cu`, and I added tests after the compaction tests in `main.cpp` to compare it with my own implementations. ## 3. Performance Analysis ### Block size optimization ![](images/graph3.png) + Based on the test, performance is best with the block size of 512. ### Scan ![](images/graph1.png) + For smaller array sizes, CPU performs better than GPU due to lower overhead in launching kernels and managing memory transfers. However, as the array size grows, the parallelism of the GPU becomes more effective, and all GPU algorithms begin to perform better than CPU. Among the GPU implementations, the work-efficient scan consistently works faster than the naive version, while Thrust achieves the best performance overall. ### Stream Compaction ![](images/graph2.png) + Stream compaction shows similar pattern - for smaller array size, CPU performs better. When the array size become larger, the cost of CPU grows much more faster, and GPU algorithms beats the CPU's. Thrust still performs better. ### Nsight Analysis Thrust: `thrust::exclusive_scan` and `thrust::remove_if` ![](images/report1.png) + Work-efficient scan: `kernEfficientScan` ![](images/report2.png) + Stream compaction: `kernMapToBoolean` and `kernScatter` ![](images/report3.png) @@ -99,59 +130,68 @@ The memory throughput in thrust kernels is very high, meaning that data is loade **************** ** SCAN TESTS ** **************** - [ 42 9 15 33 8 18 37 14 42 25 30 39 39 ... 35 0 ] + [ 10 38 12 1 38 44 32 22 13 18 29 18 27 ... 5 0 ] ==== cpu scan, power-of-two ==== - elapsed time: 9.4693ms (std::chrono Measured) - [ 0 42 51 66 99 107 125 162 176 218 243 273 312 ... 410808967 410809002 ] + elapsed time: 10.3416ms (std::chrono Measured) + [ 0 10 48 60 61 99 143 175 197 210 228 257 275 ... 410980021 410980026 ] ==== cpu scan, non-power-of-two ==== - elapsed time: 10.0322ms (std::chrono Measured) - [ 0 42 51 66 99 107 125 162 176 218 243 273 312 ... 410808882 410808928 ] + elapsed time: 10.0785ms (std::chrono Measured) + [ 0 10 48 60 61 99 143 175 197 210 228 257 275 ... 410979953 410979995 ] passed ==== naive scan, power-of-two ==== - elapsed time: 7.18131ms (CUDA Measured) + elapsed time: 5.20397ms (CUDA Measured) passed ==== naive scan, non-power-of-two ==== - elapsed time: 7.04205ms (CUDA Measured) + elapsed time: 5.53779ms (CUDA Measured) passed ==== work-efficient scan, power-of-two ==== - elapsed time: 3.99462ms (CUDA Measured) + elapsed time: 2.68493ms (CUDA Measured) passed ==== work-efficient scan, non-power-of-two ==== - elapsed time: 4.29261ms (CUDA Measured) + elapsed time: 2.37363ms (CUDA Measured) passed ==== thrust scan, power-of-two ==== - elapsed time: 1.66093ms (CUDA Measured) + elapsed time: 1.52678ms (CUDA Measured) passed ==== thrust scan, non-power-of-two ==== - elapsed time: 1.4336ms (CUDA Measured) + elapsed time: 1.90054ms (CUDA Measured) passed ***************************** ** STREAM COMPACTION TESTS ** ***************************** - [ 0 1 3 3 0 0 1 2 2 3 2 1 3 ... 1 0 ] + [ 3 3 0 2 3 3 0 2 0 0 1 0 2 ... 2 0 ] ==== cpu compact without scan, power-of-two ==== - elapsed time: 34.5797ms (std::chrono Measured) - [ 1 3 3 1 2 2 3 2 1 3 1 3 1 ... 2 1 ] + elapsed time: 39.9029ms (std::chrono Measured) + [ 3 3 2 3 3 2 1 2 1 3 2 3 2 ... 2 2 ] passed ==== cpu compact without scan, non-power-of-two ==== - elapsed time: 33.8107ms (std::chrono Measured) - [ 1 3 3 1 2 2 3 2 1 3 1 3 1 ... 3 1 ] + elapsed time: 35.6129ms (std::chrono Measured) + [ 3 3 2 3 3 2 1 2 1 3 2 3 2 ... 2 2 ] passed ==== cpu compact with scan ==== - elapsed time: 98.8601ms (std::chrono Measured) - [ 1 3 3 1 2 2 3 2 1 3 1 3 1 ... 2 1 ] + elapsed time: 99.8712ms (std::chrono Measured) + [ 3 3 2 3 3 2 1 2 1 3 2 3 2 ... 2 2 ] passed ==== work-efficient compact, power-of-two ==== - elapsed time: 13.392ms (CUDA Measured) + elapsed time: 2.37261ms (CUDA Measured) passed ==== work-efficient compact, non-power-of-two ==== - elapsed time: 12.5718ms (CUDA Measured) + elapsed time: 2.30605ms (CUDA Measured) passed ==== thrust compact, power-of-two ==== - elapsed time: 1.41328ms (CUDA Measured) + elapsed time: 1.77309ms (CUDA Measured) passed ==== thrust compact, non-power-of-two ==== - elapsed time: 1.44829ms (CUDA Measured) + elapsed time: 1.75958ms (CUDA Measured) + passed + +********************** +** RADIX SORT TESTS ** +********************** + [ 16 17 22 41 0 47 7 17 18 22 33 6 37 ... 27 0 ] +==== thrust sort, power-of-two ==== + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 49 49 ] +==== work-efficient sort, power-of-two ==== passed ``` \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 5856dfbd..5a7d3e2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -147,7 +147,7 @@ int main(int argc, char* argv[]) { //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); - // Thrust + // Thrust compaction zeroArray(SIZE, c); printDesc("thrust compact, power-of-two"); count = StreamCompaction::Thrust::compact(SIZE, c, a); @@ -162,6 +162,31 @@ int main(int argc, char* argv[]) { //printArray(NPOT, c, true); printCmpLenResult(count, expectedNPOT, b, c); + + // Radix sort tests + printf("\n"); + printf("**********************\n"); + printf("** RADIX SORT 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); // Use as reference + printDesc("thrust sort, power-of-two"); + StreamCompaction::Thrust::sort(SIZE, b, a); + //printElapsedTime(StreamCompaction::Thrust::timer().getCpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + printArray(SIZE, b, true); + + zeroArray(SIZE, c); + printDesc("work-efficient sort, power-of-two"); + StreamCompaction::Efficient::sort(SIZE, c, a); + //printElapsedTime(StreamCompaction::Efficient::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); + + system("pause"); // stop Win32 console from closing on exit delete[] a; delete[] b; diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 30df97e1..175a1eaa 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -49,5 +49,28 @@ namespace StreamCompaction { } } + __global__ void kernMapToBit(int n, int* revBits, const int* idata, int pass) { + int id = threadIdx.x + blockIdx.x * blockDim.x; + if (id >= n) return; + + int bit = (idata[id] >> pass) & 1; + revBits[id] = 1 - bit; + } + + __global__ void kernRadixScatter(int n, int totalFalses, int* odata, const int* revBits, const int* falses, const int* idata) { + int id = threadIdx.x + blockIdx.x * blockDim.x; + if (id >= n) return; + + int t = id - falses[id] + totalFalses; + + __syncthreads(); + + int index = revBits[id] ? falses[id] : t; + + __syncthreads(); + + odata[index] = idata[id]; + } + } } diff --git a/stream_compaction/common.h b/stream_compaction/common.h index b3564f91..e8e87c3b 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -38,6 +38,11 @@ namespace StreamCompaction { __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices); + __global__ void kernMapToBit(int n, int* bits, const int* idata, int pass); + + __global__ void kernRadixScatter(int n, int totalFalses, int* odata, + const int* revBits, const int* falses, const int* idata); + /** * This class is used for timing the performance * Uncopyable and unmovable diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 0da9b82e..cc7d0d8a 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -17,7 +17,7 @@ namespace StreamCompaction { } __global__ void kernEfficientScan(int n, int* odata, const int* idata, int* blockSum) { - int id = blockIdx.x * blockDim.x + threadIdx.x; + //int id = blockIdx.x * blockDim.x + threadIdx.x; int thid = threadIdx.x; int size = 2 * blockDim.x; int offset = 1; @@ -185,7 +185,7 @@ namespace StreamCompaction { */ int compact(int n, int *odata, const int *idata) { // TODO - int threads = 1024; + int threads = 512; int blocks = (n + threads - 1) / threads; // ceil // Allocate memory on device @@ -237,5 +237,66 @@ namespace StreamCompaction { return count; } + + void sort(int n, int* odata, const int* idata) { + int threads = 512; // temp + int blocks = (n + threads - 1) / threads; // temp + + // Allocate memory on device + int* d_odata, *d_idata, *d_revBits, *d_falses, *d_trues, *d_indices; + + cudaMalloc(&d_odata, n * sizeof(int)); + checkCUDAError("Efficient::sort::cudaMalloc d_odata fails!"); + + cudaMalloc(&d_idata, n * sizeof(int)); + checkCUDAError("Efficient::sort::cudaMalloc d_idata fails!"); + + cudaMalloc(&d_revBits, n * sizeof(int)); + checkCUDAError("Efficient::sort::cudaMalloc d_bits fails!"); + + cudaMalloc(&d_falses, n * sizeof(int)); + checkCUDAError("Efficient::sort::cudaMalloc d_falses fails!"); + + cudaMalloc(&d_trues, n * sizeof(int)); + checkCUDAError("Efficient::sort::cudaMalloc d_trues fails!"); + + cudaMalloc(&d_indices, n * sizeof(int)); + checkCUDAError("Efficient::sort::cudaMalloc d_indices fails!"); + + // Copy memory to device + cudaMemcpy(d_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("Efficient::sort::cudaMemcpyHostToDevice fails!"); + + for (int i = 0; i < 32; ++i) { + // Map to bit + Common::kernMapToBit<<>>(n, d_revBits, d_idata, i); + checkCUDAError("Common::kernMapToBit fails!"); + + // Scan the reversed bits + scan(n, d_falses, d_revBits); + + // Get total falses + int lastRevBit, lastFalse; + cudaMemcpy(&lastRevBit, d_revBits + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&lastFalse, d_falses + (n - 1), sizeof(int), cudaMemcpyDeviceToHost); + int totalFalses = lastRevBit + lastFalse; + + // Get indices + Common::kernRadixScatter<<>>(n, totalFalses, d_odata, d_revBits, d_falses, d_idata); + + int* temp = d_idata; + d_idata = d_odata; + d_odata = temp; + } + + cudaMemcpy(odata, d_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(d_odata); + cudaFree(d_idata); + cudaFree(d_revBits); + cudaFree(d_falses); + cudaFree(d_trues); + cudaFree(d_indices); + } } } diff --git a/stream_compaction/efficient.h b/stream_compaction/efficient.h index 803cb4fe..ce56eadf 100644 --- a/stream_compaction/efficient.h +++ b/stream_compaction/efficient.h @@ -9,5 +9,7 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata); int compact(int n, int *odata, const int *idata); + + void sort(int n, int* odata, const int* idata); } } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index cfb0179f..91d81f41 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -4,6 +4,7 @@ #include #include #include +#include #include "common.h" #include "thrust.h" @@ -60,5 +61,13 @@ namespace StreamCompaction { return new_size; } + + void sort(int n, int* odata, const int* idata) { + thrust::device_vector d_in(idata, idata + n); + + thrust::sort(d_in.begin(), d_in.end()); + + thrust::copy(d_in.begin(), d_in.end(), odata); + } } } diff --git a/stream_compaction/thrust.h b/stream_compaction/thrust.h index d5c82ec5..74d6a15d 100644 --- a/stream_compaction/thrust.h +++ b/stream_compaction/thrust.h @@ -9,5 +9,7 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata); int compact(int n, int* odata, const int* idata); + + void sort(int n, int* odata, const int* idata); } } From 2796d1584c2c953dba10daabe984fde09a7a0e41 Mon Sep 17 00:00:00 2001 From: Zhuoran Li <121198882+luoluobuli@users.noreply.github.com> Date: Thu, 18 Sep 2025 23:57:12 -0400 Subject: [PATCH 10/10] Update README.md --- README.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 2bafa231..d38bc703 100644 --- a/README.md +++ b/README.md @@ -130,68 +130,69 @@ The memory throughput in thrust kernels is very high, meaning that data is loade **************** ** SCAN TESTS ** **************** - [ 10 38 12 1 38 44 32 22 13 18 29 18 27 ... 5 0 ] + [ 46 10 13 38 0 15 26 33 48 46 2 37 47 ... 37 0 ] ==== cpu scan, power-of-two ==== - elapsed time: 10.3416ms (std::chrono Measured) - [ 0 10 48 60 61 99 143 175 197 210 228 257 275 ... 410980021 410980026 ] + elapsed time: 11.078ms (std::chrono Measured) + [ 0 46 56 69 107 107 122 148 181 229 275 277 314 ... 410917379 410917416 ] ==== cpu scan, non-power-of-two ==== - elapsed time: 10.0785ms (std::chrono Measured) - [ 0 10 48 60 61 99 143 175 197 210 228 257 275 ... 410979953 410979995 ] + elapsed time: 11.6785ms (std::chrono Measured) + [ 0 46 56 69 107 107 122 148 181 229 275 277 314 ... 410917332 410917365 ] passed ==== naive scan, power-of-two ==== - elapsed time: 5.20397ms (CUDA Measured) + elapsed time: 5.64531ms (CUDA Measured) passed ==== naive scan, non-power-of-two ==== - elapsed time: 5.53779ms (CUDA Measured) + elapsed time: 5.0217ms (CUDA Measured) passed ==== work-efficient scan, power-of-two ==== - elapsed time: 2.68493ms (CUDA Measured) + elapsed time: 3.21946ms (CUDA Measured) passed ==== work-efficient scan, non-power-of-two ==== - elapsed time: 2.37363ms (CUDA Measured) + elapsed time: 2.62963ms (CUDA Measured) passed ==== thrust scan, power-of-two ==== - elapsed time: 1.52678ms (CUDA Measured) + elapsed time: 1.34349ms (CUDA Measured) passed ==== thrust scan, non-power-of-two ==== - elapsed time: 1.90054ms (CUDA Measured) + elapsed time: 1.5104ms (CUDA Measured) passed ***************************** ** STREAM COMPACTION TESTS ** ***************************** - [ 3 3 0 2 3 3 0 2 0 0 1 0 2 ... 2 0 ] + [ 2 2 0 3 3 0 3 3 2 0 0 2 0 ... 0 0 ] ==== cpu compact without scan, power-of-two ==== - elapsed time: 39.9029ms (std::chrono Measured) - [ 3 3 2 3 3 2 1 2 1 3 2 3 2 ... 2 2 ] + elapsed time: 33.1457ms (std::chrono Measured) + [ 2 2 3 3 3 3 2 2 1 3 1 1 2 ... 2 2 ] passed ==== cpu compact without scan, non-power-of-two ==== - elapsed time: 35.6129ms (std::chrono Measured) - [ 3 3 2 3 3 2 1 2 1 3 2 3 2 ... 2 2 ] + elapsed time: 34.8257ms (std::chrono Measured) + [ 2 2 3 3 3 3 2 2 1 3 1 1 2 ... 1 2 ] passed ==== cpu compact with scan ==== - elapsed time: 99.8712ms (std::chrono Measured) - [ 3 3 2 3 3 2 1 2 1 3 2 3 2 ... 2 2 ] + elapsed time: 83.9328ms (std::chrono Measured) + [ 2 2 3 3 3 3 2 2 1 3 1 1 2 ... 2 2 ] passed ==== work-efficient compact, power-of-two ==== - elapsed time: 2.37261ms (CUDA Measured) + elapsed time: 2.35418ms (CUDA Measured) passed ==== work-efficient compact, non-power-of-two ==== - elapsed time: 2.30605ms (CUDA Measured) + elapsed time: 5.15379ms (CUDA Measured) passed ==== thrust compact, power-of-two ==== - elapsed time: 1.77309ms (CUDA Measured) + elapsed time: 1.65043ms (CUDA Measured) passed ==== thrust compact, non-power-of-two ==== - elapsed time: 1.75958ms (CUDA Measured) + elapsed time: 1.6233ms (CUDA Measured) passed ********************** ** RADIX SORT TESTS ** ********************** - [ 16 17 22 41 0 47 7 17 18 22 33 6 37 ... 27 0 ] + [ 3 38 24 29 29 19 1 28 4 18 6 26 39 ... 10 0 ] ==== thrust sort, power-of-two ==== [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 49 49 ] ==== work-efficient sort, power-of-two ==== + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 49 49 ] passed -``` \ No newline at end of file +```