Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"files.associations": {
"algorithm": "cpp",
"cstdio": "cpp"
}
}
107 changes: 101 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,107 @@ 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)
* Wanru Zhao
* [LinkedIn](www.linkedin.com/in/wanru-zhao).
* Tested on: Windows 10, Intel(R) Xeon(R) CPU E5-1630 v4 @ 3.70GHz, GTX 1070 (SIG Lab)

### (TODO: Your README)
### Features

Include analysis, etc. (Remember, this is public, so don't put
anything here that you don't want to share with the world.)
- CPU Scan & Stream Compaction
- Naive GPU Scan Algorithm
- Work-Efficient GPU Scan
- Work-Efficient GPU Stream Compaction
- Thrust Implementation

### Extra Credits

- Work-Efficient GPU Scan Optimization: by reducing empty or rest threads, dynamically lauch kernal
- Radix Sort

### Performance Analysis
#### Block size over GPU scan methods (Array size 256)

![](img/scan_bs.JPG)

Performed analysis of blocksize impact over Naive Scan(power of two/non-power of two) and work-efficient scan(power of two/non-power of two). It shows that given the same block size, work-efficient GPU scan will be always better than Naive scan. And the size of array, whether is power of two or not, has little impact on one implementation of same block size. And from the graph, the performance has little change over the block size. The rough optimal blocksize is 512 for each implementation.

#### Array size over scan methods (Block size 1024)

![](img/scan_as.JPG)

From this graph, we can find, thrust scan has the best performance over changes of array size, and it keeps the same for 2^6 to 2^20 numbers. And as array size raises, CPU scan's performance decreases dramatically. And Naive is better than CPU, but worse than work-efficient scan. When array size is between 2^6 to 2^14, CPU method is better than two GPU scan methods, and when array size is between 2^14 to 2^16, work-efficient is better than CPU, and when array size is larger than 2^16, Naive becomes better than CPU.
Thrust methods may optimized memory allocation and accessing methods and how it devides the block to make the performance stable and best.

#### Array size over compaction methods (Block size 1024)

![](img/compact_as.JPG)
We can see from the graph that, CPU method with scan is the worst, since for compaction, it actually does O(n) for finding booleans, O(n) for doing the scan and O(n) for scattering, since simple compaction just needs O(n) time. And when array size is large enough, work-efficient compaction benefits from its parallism and performs the best.

#### Radix Sort (Block size 1024, Array size 256)

Time: 1.52576ms

### Result
```
****************
** SCAN TESTS **
****************
[ 18 22 20 42 10 18 23 8 38 0 30 27 2 ... 27 0 ]
==== cpu scan, power-of-two ====
elapsed time: 0.0011ms (std::chrono Measured)
[ 0 18 40 60 102 112 130 153 161 199 199 229 256 ... 6238 6265 ]
==== cpu scan, non-power-of-two ====
elapsed time: 0.0008ms (std::chrono Measured)
[ 0 18 40 60 102 112 130 153 161 199 199 229 256 ... 6157 6161 ]
passed
==== naive scan, power-of-two ====
elapsed time: 0.036864ms (CUDA Measured)
passed
==== naive scan, non-power-of-two ====
elapsed time: 0.036864ms (CUDA Measured)
passed
==== work-efficient scan, power-of-two ====
elapsed time: 0.070656ms (CUDA Measured)
passed
==== work-efficient scan, non-power-of-two ====
elapsed time: 0.068608ms (CUDA Measured)
passed
==== thrust scan, power-of-two ====
elapsed time: 0.001024ms (CUDA Measured)
passed
==== thrust scan, non-power-of-two ====
elapsed time: 0.001024ms (CUDA Measured)
passed
[ 18 22 20 42 10 18 23 8 38 0 30 27 2 ... 27 35 ]
==== radix sort, power-of-two ====
elapsed time: 1.52883ms (CUDA Measured)
[ 0 0 0 1 1 1 1 2 2 2 2 2 2 ... 49 49 ]
passed
==== radix sort, non-power-of-two ====
elapsed time: 1.52576ms (CUDA Measured)
[ 0 0 0 1 1 1 1 2 2 2 2 2 2 ... 49 49 ]
passed

*****************************
** STREAM COMPACTION TESTS **
*****************************
[ 0 2 2 0 0 0 3 0 2 2 2 1 0 ... 1 0 ]
==== cpu compact without scan, power-of-two ====
elapsed time: 0.0015ms (std::chrono Measured)
[ 2 2 3 2 2 2 1 1 3 1 3 1 1 ... 1 1 ]
passed
==== cpu compact without scan, non-power-of-two ====
elapsed time: 0.0015ms (std::chrono Measured)
[ 2 2 3 2 2 2 1 1 3 1 3 1 1 ... 3 1 ]
passed
==== cpu compact with scan ====
elapsed time: 0.0062ms (std::chrono Measured)
[ 2 2 3 2 2 2 1 1 3 1 3 1 1 ... 1 1 ]
passed
==== work-efficient compact, power-of-two ====
elapsed time: 0.09216ms (CUDA Measured)
passed
==== work-efficient compact, non-power-of-two ====
elapsed time: 0.090112ms (CUDA Measured)
passed
```
Binary file added img/compact_as.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/scan_as.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/scan_bs.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions result.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

****************
** SCAN TESTS **
****************
[ 18 22 20 42 10 18 23 8 38 0 30 27 2 ... 27 0 ]
==== cpu scan, power-of-two ====
elapsed time: 0.0011ms (std::chrono Measured)
[ 0 18 40 60 102 112 130 153 161 199 199 229 256 ... 6238 6265 ]
==== cpu scan, non-power-of-two ====
elapsed time: 0.0008ms (std::chrono Measured)
[ 0 18 40 60 102 112 130 153 161 199 199 229 256 ... 6157 6161 ]
passed
==== naive scan, power-of-two ====
elapsed time: 0.036864ms (CUDA Measured)
passed
==== naive scan, non-power-of-two ====
elapsed time: 0.036864ms (CUDA Measured)
passed
==== work-efficient scan, power-of-two ====
elapsed time: 0.070656ms (CUDA Measured)
passed
==== work-efficient scan, non-power-of-two ====
elapsed time: 0.068608ms (CUDA Measured)
passed
==== thrust scan, power-of-two ====
elapsed time: 0.001024ms (CUDA Measured)
passed
==== thrust scan, non-power-of-two ====
elapsed time: 0.001024ms (CUDA Measured)
passed
[ 18 22 20 42 10 18 23 8 38 0 30 27 2 ... 27 35 ]
==== radix sort, power-of-two ====
elapsed time: 1.52883ms (CUDA Measured)
[ 0 0 0 1 1 1 1 2 2 2 2 2 2 ... 49 49 ]
passed
==== radix sort, non-power-of-two ====
elapsed time: 1.52576ms (CUDA Measured)
[ 0 0 0 1 1 1 1 2 2 2 2 2 2 ... 49 49 ]
passed

*****************************
** STREAM COMPACTION TESTS **
*****************************
[ 0 2 2 0 0 0 3 0 2 2 2 1 0 ... 1 0 ]
==== cpu compact without scan, power-of-two ====
elapsed time: 0.0015ms (std::chrono Measured)
[ 2 2 3 2 2 2 1 1 3 1 3 1 1 ... 1 1 ]
passed
==== cpu compact without scan, non-power-of-two ====
elapsed time: 0.0015ms (std::chrono Measured)
[ 2 2 3 2 2 2 1 1 3 1 3 1 1 ... 3 1 ]
passed
==== cpu compact with scan ====
elapsed time: 0.0062ms (std::chrono Measured)
[ 2 2 3 2 2 2 1 1 3 1 3 1 1 ... 1 1 ]
passed
==== work-efficient compact, power-of-two ====
elapsed time: 0.09216ms (CUDA Measured)
passed
==== work-efficient compact, non-power-of-two ====
elapsed time: 0.090112ms (CUDA Measured)
passed
67 changes: 67 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,26 @@
*/

#include <cstdio>
#include <vector>
#include <stream_compaction/cpu.h>
#include <stream_compaction/naive.h>
#include <stream_compaction/efficient.h>
#include <stream_compaction/thrust.h>
#include <stream_compaction/radix.h>
#include "testing_helpers.hpp"

#define RADIX

const int SIZE = 1 << 8; // 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];
int *c = new int[SIZE];

int *d = new int[SIZE];
int *e = new int[NPOT];
int *f = new int[SIZE];

int main(int argc, char* argv[]) {
// Scan tests

Expand Down Expand Up @@ -67,6 +75,20 @@ int main(int argc, char* argv[]) {
//printArray(SIZE, c, true);
printCmpResult(NPOT, b, c);

//zeroArray(SIZE, c);
//printDesc("naive scan, power-of-two, shared memory");
//StreamCompaction::Naive::shared_scan(SIZE, c, a);
//printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)");
//printArray(SIZE, c, true);
//printCmpResult(SIZE, b, c);

//zeroArray(SIZE, c);
//printDesc("naive scan, non-power-of-two, shared memory");
//StreamCompaction::Naive::shared_scan(NPOT, c, a);
//printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)");
//printArray(SIZE, c, true);
//printCmpResult(NPOT, b, c);

zeroArray(SIZE, c);
printDesc("work-efficient scan, power-of-two");
StreamCompaction::Efficient::scan(SIZE, c, a);
Expand Down Expand Up @@ -95,6 +117,48 @@ int main(int argc, char* argv[]) {
//printArray(NPOT, c, true);
printCmpResult(NPOT, b, c);





#ifdef RADIX
zeroArray(SIZE, d);
genArray(SIZE, d, 50);
printArray(SIZE, d, true);
std::vector<int> dVec(SIZE);
for(int i = 0; i < SIZE; i++) {
dVec[i] = d[i];
}
std::sort(dVec.begin(), dVec.end());
for(int i = 0; i < SIZE; i++) {
f[i] = dVec[i];
}

zeroArray(SIZE, e);
printDesc("radix sort, power-of-two");
StreamCompaction::Radix::sort(SIZE, e, d);
printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)");
printArray(SIZE, e, true);
printCmpResult(SIZE, f, e);

std::vector<int> dVecN(NPOT);
for (int i = 0; i < NPOT; i++) {
dVecN[i] = d[i];
}
std::sort(dVecN.begin(), dVecN.end());
for (int i = 0; i < NPOT; i++) {
f[i] = dVecN[i];
}

zeroArray(NPOT, e);
printDesc("radix sort, non-power-of-two");
StreamCompaction::Radix::sort(NPOT, e, d);
printElapsedTime(StreamCompaction::Radix::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)");
printArray(NPOT, e, true);
printCmpResult(NPOT, f, e);

#endif

printf("\n");
printf("*****************************\n");
printf("** STREAM COMPACTION TESTS **\n");
Expand Down Expand Up @@ -151,4 +215,7 @@ int main(int argc, char* argv[]) {
delete[] a;
delete[] b;
delete[] c;
delete[] d;
delete[] e;
delete[] f;
}
4 changes: 3 additions & 1 deletion stream_compaction/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ set(SOURCE_FILES
"efficient.cu"
"thrust.h"
"thrust.cu"
"radix.h"
"radix.cu"
)

cuda_add_library(stream_compaction
${SOURCE_FILES}
OPTIONS -arch=sm_20
OPTIONS -arch=sm_61
)
12 changes: 12 additions & 0 deletions stream_compaction/common.cu
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ namespace StreamCompaction {
*/
__global__ void kernMapToBoolean(int n, int *bools, const int *idata) {
// TODO

int index = blockDim.x * blockIdx.x + threadIdx.x;
if (index >= n) return;

bools[index] = idata[index] ? 1 : 0;

}

/**
Expand All @@ -33,6 +39,12 @@ namespace StreamCompaction {
__global__ void kernScatter(int n, int *odata,
const int *idata, const int *bools, const int *indices) {
// TODO
int index = blockDim.x * blockIdx.x + threadIdx.x;
if (index >= n) return;

if (bools[index]) {
odata[indices[index]] = idata[index];
}
}

}
Expand Down
2 changes: 2 additions & 0 deletions stream_compaction/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__)

#define blockSize 1024

/**
* Check for CUDA errors; print and exit if there was a problem.
*/
Expand Down
Loading