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
109 changes: 103 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,109 @@ 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)
* Yichao Wang
* [LinkedIn](https://www.linkedin.com/in/wangyic/)
* Tested on: Windows 10 Home 64-bit (10.0, Build 18363)
* Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 CPUs)
* GeForce GTX 1060 6.1

### (TODO: Your README)
## Description

Include analysis, etc. (Remember, this is public, so don't put
anything here that you don't want to share with the world.)
This project implements Scan and Stream Compaction in various ways:

* Scan (all implementations support Non-Power-Of-Two input)
* cpu
* naive (gpu)
* work-efficient (gpu, optimized indexing)
* thrust (gpu)
* Stream Compaction
* cpu without scan
* cpu with scan
* gpu with work-efficient scan

For more information, see [INSTRUCTION.md](INSTRUCTION.md).

## Performance Analysis

### Block Size Optimize

![](img/blockSize.png)

Note: ```NPOT stands for Non-Power-of-Two```

From above plot, we can see that there is no much difference between block sizes. So we choose block size of 512 for the following analysis.

### Implmentations Comparation

![](img/scan.png)

![](img/compact.png)

From above plots, we can see that the elapsed time increases as input size increases. However, there is no much difference for elapsed time when input size is non-power-of-two. Not surprising, thrust implmentation is the fastest one.

For stream compaction, we can see cpu without scan runs faster than cpu with scan. This is because cpu with scan requires three for loop while cpu without scan only requires one.

### Work Efficient Scan Optimization

Before the optimization, work efficient scan even runs slower than naive and cpu implementation. This is because not all threads are actually working. For example, if the input size is 1024, we only need 512 threads at most instead of 1024 for the first depth. This is because the additions is half of the size. Thus, to optimize the work efficient scan, I have adjusted the box amount according to the depth level and have changed index calculation in up-sweep and down-sweep method.


### Output for Scan and Stream Compaction Test (512 block size, 2^24 input size)

```

****************
** SCAN TESTS **
****************
[ 47 5 43 43 27 45 0 29 1 26 3 8 27 ... 2 0 ]
==== cpu scan, power-of-two ====
elapsed time: 29.0025ms (std::chrono Measured)
[ 0 47 52 95 138 165 210 210 239 240 266 269 277 ... 410953421 410953423 ]
==== cpu scan, non-power-of-two ====
elapsed time: 29.5704ms (std::chrono Measured)
[ 0 47 52 95 138 165 210 210 239 240 266 269 277 ... 410953326 410953375 ]
passed
==== naive scan, power-of-two ====
elapsed time: 24.2698ms (CUDA Measured)
[ 0 47 52 95 138 165 210 210 239 240 266 269 277 ... 410953421 410953423 ]
passed
==== naive scan, non-power-of-two ====
elapsed time: 23.7513ms (CUDA Measured)
passed
==== work-efficient scan, power-of-two ====
elapsed time: 9.78432ms (CUDA Measured)
passed
==== work-efficient scan, non-power-of-two ====
elapsed time: 9.76662ms (CUDA Measured)
passed
==== thrust scan, power-of-two ====
elapsed time: 1.13667ms (CUDA Measured)
passed
==== thrust scan, non-power-of-two ====
elapsed time: 1.34381ms (CUDA Measured)
passed

*****************************
** STREAM COMPACTION TESTS **
*****************************
[ 2 0 0 0 1 3 3 0 2 3 3 2 2 ... 3 0 ]
==== cpu compact without scan, power-of-two ====
elapsed time: 50.8381ms (std::chrono Measured)
[ 2 1 3 3 2 3 3 2 2 3 1 3 3 ... 3 3 ]
passed
==== cpu compact without scan, non-power-of-two ====
elapsed time: 44.1896ms (std::chrono Measured)
[ 2 1 3 3 2 3 3 2 2 3 1 3 3 ... 1 3 ]
passed
==== cpu compact with scan ====
elapsed time: 73.4177ms (std::chrono Measured)
[ 2 1 3 3 2 3 3 2 2 3 1 3 3 ... 3 3 ]
passed
==== work-efficient compact, power-of-two ====
elapsed time: 13.1512ms (CUDA Measured)
passed
==== work-efficient compact, non-power-of-two ====
elapsed time: 13.1215ms (CUDA Measured)
passed
Press any key to continue . . .
```
Binary file added img/blockSize.png
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/compact.png
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.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#include <stream_compaction/thrust.h>
#include "testing_helpers.hpp"

const int SIZE = 1 << 8; // 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];
Expand Down Expand Up @@ -51,7 +51,7 @@ int main(int argc, char* argv[]) {
printDesc("naive scan, power-of-two");
StreamCompaction::Naive::scan(SIZE, c, a);
printElapsedTime(StreamCompaction::Naive::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)");
//printArray(SIZE, c, true);
printArray(SIZE, c, true);
printCmpResult(SIZE, b, c);

/* For bug-finding only: Array of 1s to help find bugs in stream compaction or scan
Expand Down
21 changes: 19 additions & 2 deletions stream_compaction/common.cu
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ namespace StreamCompaction {
* which map to 0 will be removed, and elements which map to 1 will be kept.
*/
__global__ void kernMapToBoolean(int n, int *bools, const int *idata) {
// TODO
int idx = (blockIdx.x * blockDim.x) + threadIdx.x;
if (idx >= n) {
return;
}

if (idata[idx] != 0) {
bools[idx] = 1;
}
else {
bools[idx] = 0;
}
}

/**
Expand All @@ -32,7 +42,14 @@ namespace StreamCompaction {
*/
__global__ void kernScatter(int n, int *odata,
const int *idata, const int *bools, const int *indices) {
// TODO
int idx = (blockIdx.x * blockDim.x) + threadIdx.x;
if (idx >= n) {
return;
}

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

}
Expand Down
1 change: 1 addition & 0 deletions stream_compaction/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__)
#define blockSize 512

/**
* Check for CUDA errors; print and exit if there was a problem.
Expand Down
51 changes: 46 additions & 5 deletions stream_compaction/cpu.cu
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include "cpu.h"

#include "common.h"
#include <iostream>


namespace StreamCompaction {
namespace CPU {
Expand All @@ -12,27 +14,43 @@ namespace StreamCompaction {
return timer;
}

void scanNoTimer(int n, int* odata, const int* idata) {
// exclusive
odata[0] = 0;
for (int k = 1; k < n; k++) {
odata[k] = odata[k - 1] + idata[k - 1];
}
}

/**
* CPU scan (prefix sum).
* For performance analysis, this is supposed to be a simple for loop.
* (Optional) For better understanding before starting moving to GPU, you can simulate your GPU scan in this function first.
*/
void scan(int n, int *odata, const int *idata) {
timer().startCpuTimer();
// TODO
scanNoTimer(n, odata, idata);
timer().endCpuTimer();
}



/**
* CPU stream compaction without using the scan function.
*
* @returns the number of elements remaining after compaction.
*/
int compactWithoutScan(int n, int *odata, const int *idata) {
timer().startCpuTimer();
// TODO
int index = 0;
for (int i = 0; i < n; i++) {
if (idata[i] != 0) {
odata[index] = idata[i];
index++;
}
}
timer().endCpuTimer();
return -1;
return index;
}

/**
Expand All @@ -42,9 +60,32 @@ namespace StreamCompaction {
*/
int compactWithScan(int n, int *odata, const int *idata) {
timer().startCpuTimer();
// TODO

// compute temp array
int* tempArr = new int[n];
for (int i = 0; i < n; i++) {
if (idata[i] != 0) {
tempArr[i] = 1;
}
else {
tempArr[i] = 0;
}
}

// exclusive scan
scanNoTimer(n, odata, tempArr);

// scatter
int count = 0;
for (int i = 0; i < n; i++) {
if (tempArr[i] != 0) {
odata[odata[i]] = idata[i];
count++;
}
}

timer().endCpuTimer();
return -1;
return count;
}
}
}
Loading