From 2e01da8df92d120c8d8032ba90c5f28b9e50a017 Mon Sep 17 00:00:00 2001 From: Michael Willett Date: Tue, 20 Sep 2016 09:39:15 -0400 Subject: [PATCH 1/5] Working --- src/main.cpp | 25 ++++++- stream_compaction/CMakeLists.txt | 4 +- stream_compaction/common.cu | 18 ++++- stream_compaction/common.h | 2 + stream_compaction/cpu.cu | 62 +++++++++++++++-- stream_compaction/efficient.cu | 111 +++++++++++++++++++++++++++++-- stream_compaction/efficient.h | 3 +- stream_compaction/naive.cu | 37 ++++++++++- stream_compaction/sort.cu | 104 +++++++++++++++++++++++++++++ stream_compaction/sort.h | 7 ++ stream_compaction/thrust.cu | 8 ++- 11 files changed, 354 insertions(+), 27 deletions(-) create mode 100644 stream_compaction/sort.cu create mode 100644 stream_compaction/sort.h diff --git a/src/main.cpp b/src/main.cpp index 675da35..9194b1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "testing_helpers.hpp" int main(int argc, char* argv[]) { @@ -18,6 +19,11 @@ int main(int argc, char* argv[]) { const int NPOT = SIZE - 3; int a[SIZE], b[SIZE], c[SIZE]; + const int KEYSIZE = 10; + int a_sm[8] = { 0 }; + int c_sm[8] = { 0 }; + for (int i = 0; i < 8; i++) a_sm[i] = rand() % (1 << KEYSIZE); + // Scan tests printf("\n"); @@ -37,7 +43,7 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("cpu scan, non-power-of-two"); StreamCompaction::CPU::scan(NPOT, c, a); - printArray(NPOT, b, true); + //printArray(NPOT, b, true); printCmpResult(NPOT, b, c); zeroArray(SIZE, c); @@ -53,7 +59,7 @@ int main(int argc, char* argv[]) { printCmpResult(NPOT, b, c); zeroArray(SIZE, c); - printDesc("work-efficient scan, power-of-two"); + printDesc("work-efficient scan, power-of-two"); StreamCompaction::Efficient::scan(SIZE, c, a); //printArray(SIZE, c, true); printCmpResult(SIZE, b, c); @@ -67,7 +73,7 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("thrust scan, power-of-two"); StreamCompaction::Thrust::scan(SIZE, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); @@ -120,4 +126,17 @@ int main(int argc, char* argv[]) { count = StreamCompaction::Efficient::compact(NPOT, c, a); //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); + + printf("\n"); + printf("**********************\n"); + printf("** RADIX SORT TESTS **\n"); + printf("**********************\n"); + + + zeroArray(8, c_sm); + printArray(8, a_sm, true); + printDesc("radix sort, power-of-two"); + StreamCompaction::Sort::radix(8, KEYSIZE, c_sm, a_sm); + printArray(8, c_sm, true); + //printCmpLenResult(count, expectedNPOT, b, c); } diff --git a/stream_compaction/CMakeLists.txt b/stream_compaction/CMakeLists.txt index cdbef77..cb99a37 100644 --- a/stream_compaction/CMakeLists.txt +++ b/stream_compaction/CMakeLists.txt @@ -7,11 +7,13 @@ set(SOURCE_FILES "naive.cu" "efficient.h" "efficient.cu" + "sort.h" + "sort.cu" "thrust.h" "thrust.cu" ) cuda_add_library(stream_compaction ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_50 ) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index fe872d4..a3d2220 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -23,7 +23,12 @@ namespace Common { * which map to 0 will be removed, and elements which map to 1 will be kept. */ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { - // TODO + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + bools[index] = !(idata[index] == 0); } /** @@ -32,8 +37,17 @@ __global__ void kernMapToBoolean(int n, int *bools, const int *idata) { */ __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { - // TODO + + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + if (bools == NULL) odata[indices[index]] = idata[index]; + else if (bools[index] == 1) odata[indices[index]] = idata[index]; } + + } } diff --git a/stream_compaction/common.h b/stream_compaction/common.h index 4f52663..1c75973 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -7,6 +7,8 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) +#define blockSize 128 + /** * Check for CUDA errors; print and exit if there was a problem. */ diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index e600c29..f729c0f 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -8,8 +8,20 @@ namespace CPU { * CPU scan (prefix sum). */ void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); + for (int i = 0; i < n-1; i++) { + odata[i + 1] = odata[i] + idata[i]; + } +} + +/** +* CPU scatter. +*/ +void scatter(int n, int *odata, + const int *idata, const int *bools, const int *indices) { + + for (int i = 0; i < n - 1; i++) { + if (bools[i] == 1) odata[indices[i]] = idata[i]; + } } /** @@ -18,8 +30,27 @@ void scan(int n, int *odata, const int *idata) { * @returns the number of elements remaining after compaction. */ int compactWithoutScan(int n, int *odata, const int *idata) { - // TODO - return -1; + int r = 0; + + for (int i = 0; i < n-1; i++){ + if (idata[i] != 0) { + odata[r++] = idata[i]; + } + } + + return r; +} + +void printArray(int n, int *a, bool abridged = false) { + printf(" [ "); + for (int i = 0; i < n; i++) { + if (abridged && i + 2 == 15 && n > 16) { + i = n - 2; + printf("... "); + } + printf("%3d ", a[i]); + } + printf("]\n"); } /** @@ -27,9 +58,26 @@ int compactWithoutScan(int n, int *odata, const int *idata) { * * @returns the number of elements remaining after compaction. */ -int compactWithScan(int n, int *odata, const int *idata) { - // TODO - return -1; +int compactWithScan(const int n, int *odata, const int *idata) { + + // create arrays + int *indices = new int[n]; + int *bools = new int[n]; + int rtn = -1; + indices[0] = 0; + + for (int i = 0; i < n; i++) { + bools[i] = !(idata[i] == 0); + } + + scan(n, indices, bools); + scatter(n, odata, idata, bools, indices); + rtn = indices[n - 1]; + + delete indices; + delete bools; + + return rtn; } } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index b2f739b..618beb4 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -6,14 +6,78 @@ namespace StreamCompaction { namespace Efficient { -// TODO: __global__ +__global__ void kernUpStep(int n, int d, int *data) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + int s = pow((double)2, (double)(d + 1)); + + if (fmod((double) index, (double)s) == 0) { + data[index + s - 1] += data[index + s / 2 - 1]; + } +} + +__global__ void kernDownStep(int n, int d, int *data) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + int s = pow((double)2, (double)(d + 1)); + + if (fmod((double) index, (double)s) == 0) { + int t = data[index + s / 2 - 1]; + data[index + s / 2 - 1] = data[index + s - 1]; + data[index + s - 1] += t; + } +} /** - * Performs prefix-sum (aka scan) on idata, storing the result into odata. - */ +* Performs prefix-sum (aka scan) on idata, storing the result into odata. +*/ void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + // create device arrays + int *dev_out; + + cudaMalloc((void**)&dev_out, n*sizeof(int)); + cudaMemcpy(dev_out, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + int d = 0; + for (d; d < ilog2ceil(n); d++) { + kernUpStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_out); + } + + cudaMemset(&dev_out[n - 1], 0, sizeof(int)); + for (d; d >= 0; d--) { + kernDownStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_out); + } + + cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_out); +} + +/** +* Performs prefix-sum (aka scan) on idata, storing the result into odata. +* For use with arrays intiialized on GPU already. +*/ +void scan_dev(int n, int *dev_data) { + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + // create device arrays + int d = 0; + for (d; d < ilog2ceil(n); d++) { + kernUpStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_data); + } + + cudaMemset(&dev_data[n - 1], 0, sizeof(int)); + for (d; d >= 0; d--) { + kernDownStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_data); + } + } /** @@ -26,8 +90,41 @@ void scan(int n, int *odata, const int *idata) { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { - // TODO - return -1; + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + // create device arrays + int *dev_out; + int *dev_in; + int *dev_indices; + int *dev_bools; + int rtn = -1; + + cudaMalloc((void**)&dev_out, n*sizeof(int)); + cudaMalloc((void**)&dev_in, n*sizeof(int)); + cudaMalloc((void**)&dev_indices, n*sizeof(int)); + cudaMalloc((void**)&dev_bools, n*sizeof(int)); + + + cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); + StreamCompaction::Common::kernMapToBoolean << < fullBlocksPerGrid, blockSize >> >(n, dev_bools, dev_in); + + // scan without wasteful device-host-device write + cudaMemcpy(dev_indices, dev_bools, n*sizeof(int), cudaMemcpyDeviceToDevice); + scan_dev(n, dev_indices); + + // scatter + StreamCompaction::Common::kernScatter << < fullBlocksPerGrid, blockSize >> >(n, dev_out, dev_in, dev_bools, dev_indices); + + cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&rtn, &dev_indices[n-1], sizeof(int), cudaMemcpyDeviceToHost); + + + cudaFree(dev_out); + cudaFree(dev_in); + cudaFree(dev_bools); + cudaFree(dev_indices); + + return rtn; } } diff --git a/stream_compaction/efficient.h b/stream_compaction/efficient.h index 395ba10..6e5e6cc 100644 --- a/stream_compaction/efficient.h +++ b/stream_compaction/efficient.h @@ -2,7 +2,8 @@ namespace StreamCompaction { namespace Efficient { - void scan(int n, int *odata, const int *idata); + void scan(int n, int *odata, const int *idata); + void scan_dev(int n, int *dev_data); int compact(int n, int *odata, const int *idata); } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 3d86b60..5ce77f0 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -6,15 +6,46 @@ namespace StreamCompaction { namespace Naive { -// TODO: __global__ +__global__ void kernScanStep(int n, int d, int *odata, const int *idata) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + int s = pow((double)2, (double)(d - 1)); + + if (index >= s) { + odata[index] = idata[index] + idata[index - s]; + } +} /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - // TODO - printf("TODO\n"); + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + // create device arrays + int *dev_in; + int *dev_out; + + cudaMalloc((void**)&dev_in, n*sizeof(int)); + cudaMalloc((void**)&dev_out, n*sizeof(int)); + + cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy(dev_out, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + for (int d = 1; d <= ilog2ceil(n); d++) { + kernScanStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_out, dev_in); + cudaMemcpy(dev_in, dev_out, n*sizeof(int), cudaMemcpyDeviceToDevice); + } + + cudaMemcpy(&odata[1], dev_out, (n-1)*sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_in); + cudaFree(dev_out); } } } + diff --git a/stream_compaction/sort.cu b/stream_compaction/sort.cu new file mode 100644 index 0000000..aa2137a --- /dev/null +++ b/stream_compaction/sort.cu @@ -0,0 +1,104 @@ +#include +#include +#include "common.h" +#include "efficient.h" +#include "sort.h" + +namespace StreamCompaction { +namespace Sort { + +void printArray(int n, int *a, bool abridged = false) { + printf(" [ "); + for (int i = 0; i < n; i++) { + if (abridged && i + 2 == 15 && n > 16) { + i = n - 2; + printf("... "); + } + printf("%3d ", a[i]); + } + printf("]\n"); +} + +__global__ void kernGetBit(int n, int d, int *bits, int *nbits, const int *data) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + int s = (data[index] & (1 << d)) >> d; + + bits[index] = s; + nbits[index] = 1 - s; +} + + +__global__ void kernScanTrue(int n, int *trues, const int *falses, const int *lastBit) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + trues[index] = index - falses[index] + falses[n - 1] + lastBit[n - 1]; +} + +__global__ void kernDestination(int n, int *dest, int *trues, int *falses, int* bits) { + int index = threadIdx.x + (blockIdx.x * blockDim.x); + if (index >= n) { + return; + } + + dest[index] = bits[index] ? trues[index] : falses[index]; +} + + +void radix(int n, const int k, int *odata, const int *idata) { + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + // create device arrays + int *dev_out; + int *dev_in; + int *dev_bits; + int *dev_nbits; + int *dev_true; + int *dev_false; + int *dev_dest; + int *dev_all; + + cudaMalloc((void**)&dev_out, n*sizeof(int)); + cudaMalloc((void**)&dev_in, n*sizeof(int)); + cudaMalloc((void**)&dev_bits, n*sizeof(int)); + cudaMalloc((void**)&dev_nbits, n*sizeof(int)); + cudaMalloc((void**)&dev_true, n*sizeof(int)); + cudaMalloc((void**)&dev_false, n*sizeof(int)); + cudaMalloc((void**)&dev_dest, n*sizeof(int)); + cudaMalloc((void**)&dev_all, n*sizeof(int)); + + cudaMemset(dev_all, 0, n); + cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + for (int d = 0; d < k; d++) { + + kernGetBit << > >(n, d, dev_bits, dev_nbits, dev_in); + cudaMemcpy(dev_false, dev_nbits, n*sizeof(int), cudaMemcpyDeviceToDevice); + StreamCompaction::Efficient::scan_dev(n, dev_false); + kernScanTrue << > >(n, dev_true, dev_false, dev_nbits); + kernDestination << > >(n, dev_dest, dev_true, dev_false, dev_bits); + StreamCompaction::Common::kernScatter << > >(n, dev_out, dev_in, NULL, dev_dest); + cudaMemcpy(dev_in, dev_out, n*sizeof(int), cudaMemcpyDeviceToDevice); + } + + + cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_out); + cudaFree(dev_in); + cudaFree(dev_bits); + cudaFree(dev_nbits); + cudaFree(dev_true); + cudaFree(dev_false); + cudaFree(dev_dest); + cudaFree(dev_all); +} + +} +} \ No newline at end of file diff --git a/stream_compaction/sort.h b/stream_compaction/sort.h new file mode 100644 index 0000000..36c72f0 --- /dev/null +++ b/stream_compaction/sort.h @@ -0,0 +1,7 @@ +#pragma once + +namespace StreamCompaction { + namespace Sort { + void radix(int n, const int k, int *odata, const int *idata); + } +} diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index d8dbb32..d05c772 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -13,9 +13,11 @@ namespace Thrust { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - // TODO use `thrust::exclusive_scan` - // example: for device_vectors dv_in and dv_out: - // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + thrust::device_vector dv_in(idata, idata + n); + thrust::device_vector dv_out(odata, odata + n); + + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + thrust::copy(dv_out.begin(), dv_out.end(), odata); } } From 9013c99b0c0285cbba0830e602a1ac7e1cc5cb61 Mon Sep 17 00:00:00 2001 From: Michael Willett Date: Mon, 26 Sep 2016 13:43:42 -0400 Subject: [PATCH 2/5] Fix some std::pow bug refactor code to ignore memcpy speed during performance analysis --- README.md | 192 ++++++++++++++++++++++++++++++++- src/main.cpp | 124 ++++++++++++++++++--- stream_compaction/common.cu | 1 + stream_compaction/common.h | 4 +- stream_compaction/cpu.cu | 61 +++++++++++ stream_compaction/cpu.h | 5 + stream_compaction/efficient.cu | 170 ++++++++++++++++++++++++----- stream_compaction/efficient.h | 6 +- stream_compaction/naive.cu | 61 +++++++++-- stream_compaction/naive.h | 5 +- stream_compaction/sort.cu | 75 ++++++++++++- stream_compaction/sort.h | 3 + stream_compaction/thrust.cu | 76 +++++++++++++ stream_compaction/thrust.h | 7 +- 14 files changed, 731 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index b71c458..5f50304 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,193 @@ CUDA Stream Compaction **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 2** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Michael Willett +* Tested on: Windows 10, I5-4690k @ 3.50GHz 8.00GB, GTX 750-TI 2GB (Personal Computer) -### (TODO: Your README) +## Contents +1. [Introduction](#intro) +2. [Algorithms](#part1) +4. [Performance Analysis](#part3) +5. [Development Process](#part4) +6. [Build Instructions](#appendix) -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) + +## Introduction: Parallel Algorithms +This project explores introductory concepts of GPU paralization methods for simulating flocking behaviors +of simple particles known as boids. Boid motion is based off of three rules calculated from nearby particles: + +1. *Cohesion* - Boids will move towards the center of mass of nearby boids +2. *Separation* - Boids will try to maintain a minimum distance from one another to avoid collision +3. *Alignment* - Boids in a group will try to align vector headings with those in the group + +These simple rules with the appropriate tuning parameter set can lead to a surprisingly complex emergent +behavior very similar to how schools of fish or flocks of birds move in nature, as seen below. + + + +## Section 1: Scanning, Stream Compaction, and Sorting +The boids flocking simulation is naively calculated by comparing euclidean distance from the current +boid to every other boid in the simulation, and checking if the distance is within the desired range for +the rule being calculated (we use a smaller distance metric for calculating separation, otherwise the boids +never exhibit flocking behavior). + +While computationally this results in the correct behavior, it can be wasteful in the number of comparison operations +since we only apply position and velocity updates if a boid has at least one other particle close to it. For smaller +particle counts, this method can achieve 60 fps on a cheap modern process, but scales very poorly as the number of +comparisons increases. Detailed analysis is available in [Section 3: Performance Analysis](#part-3). + + +## Section 2: Performance Analysis + +Code Correctness for Scan, Compact, and Sort Implementations: +> **************** +> ** SCAN TESTS ** +> **************** +> [ 38 19 38 37 5 47 15 35 0 12 3 0 42 ... 35 0 ] + +> ==== cpu scan, power-of-two ==== + +> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] +> ==== cpu scan, non-power-of-two ==== + +> passed +> ==== naive scan, power-of-two ==== + +> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] +> passed +> ==== naive scan, non-power-of-two ==== + +> passed +> ==== work-efficient scan, power-of-two ==== + +> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] +> passed +> ==== work-efficient scan, non-power-of-two ==== + +> passed +> ==== thrust scan, power-of-two ==== + +> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] +> passed +> ==== thrust scan, non-power-of-two ==== + +> passed +> +> ***************************** +> ** STREAM COMPACTION TESTS ** +> ***************************** + +> [ 2 3 2 1 3 1 1 1 2 0 1 0 2 ... 1 0 ] +> ==== cpu compact without scan, power-of-two ==== + +> [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] +> passed +> ==== cpu compact without scan, non-power-of-two ==== + +> [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 3 3 ] +> passed +> ==== cpu compact with scan ==== + +> [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] +> passed +> ==== work-efficient compact, power-of-two ==== + +> passed +> ==== work-efficient compact, non-power-of-two ==== + +> passed +> +> ********************** +> ** RADIX SORT TESTS ** +> ********************** + +> ==== radix sort, power-of-two ==== + +> [ 38 7719 21238 2437 8855 11797 8365 32285 ] +> [ 38 2437 7719 8365 8855 11797 21238 32285 ] +> passed +> ==== radix sort, non-power-of-two ==== + +> [ 38 7719 21238 2437 8855 11797 8365 ] +> [ 38 2437 7719 8365 8855 11797 21238 ] +> passed + +> +> Sort passed 1000/1000 randomly generated verification tests. + + + + + +## Section 3: Development Process +Development was fairly straight forward algorithmic implementation. Future work could be done in the work effecient +implementations to better handle launching kernal functions at the high depth levels when only a couple of sums are +being calculated for the whole array. This should + +It was worth noting there was a bug in using std::pow for calculating the array index in each kernal invocation. For some +unknown reason, it consisantly produced erronius values at 2^11, or 2048. This is odd since variables were being cast to +double precision before the computation, but the reverse cast was incorrect. This bug may be compiler specific as running +the index calculation on a separate machine result in accurate indexing in this range. Simply changing the code to use +bitshift operations cleared the error entirely. + + + + +## Appendix: Build Instructions + +* `src/` contains the source code. + +**CMake note:** Do not change any build settings or add any files to your +project directly (in Visual Studio, Nsight, etc.) Instead, edit the +`src/CMakeLists.txt` file. Any files you add must be added here. If you edit it, +just rebuild your VS/Nsight project to make it update itself. + +#### Windows + +1. In Git Bash, navigate to your cloned project directory. +2. Create a `build` directory: `mkdir build` + * (This "out-of-source" build makes it easy to delete the `build` directory + and try again if something goes wrong with the configuration.) +3. Navigate into that directory: `cd build` +4. Open the CMake GUI to configure the project: + * `cmake-gui ..` or `"C:\Program Files (x86)\cmake\bin\cmake-gui.exe" ..` + * Don't forget the `..` part! + * Make sure that the "Source" directory is like + `.../Project2-Stream-Compaction`. + * Click *Configure*. Select your version of Visual Studio, Win64. + (**NOTE:** you must use Win64, as we don't provide libraries for Win32.) + * If you see an error like `CUDA_SDK_ROOT_DIR-NOTFOUND`, + set `CUDA_SDK_ROOT_DIR` to your CUDA install path. This will be something + like: `C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v7.5` + * Click *Generate*. +5. If generation was successful, there should now be a Visual Studio solution + (`.sln`) file in the `build` directory that you just created. Open this. + (from the command line: `explorer *.sln`) +6. Build. (Note that there are Debug and Release configuration options.) +7. Run. Make sure you run the `cis565_` target (not `ALL_BUILD`) by + right-clicking it and selecting "Set as StartUp Project". + * If you have switchable graphics (NVIDIA Optimus), you may need to force + your program to run with only the NVIDIA card. In NVIDIA Control Panel, + under "Manage 3D Settings," set "Multi-display/Mixed GPU acceleration" + to "Single display performance mode". + +#### OS X & Linux + +It is recommended that you use Nsight. + +1. Open Nsight. Set the workspace to the one *containing* your cloned repo. +2. *File->Import...->General->Existing Projects Into Workspace*. + * Select the Project 0 repository as the *root directory*. +3. Select the *cis565-* project in the Project Explorer. From the *Project* + menu, select *Build All*. + * For later use, note that you can select various Debug and Release build + configurations under *Project->Build Configurations->Set Active...*. +4. If you see an error like `CUDA_SDK_ROOT_DIR-NOTFOUND`: + * In a terminal, navigate to the build directory, then run: `cmake-gui ..` + * Set `CUDA_SDK_ROOT_DIR` to your CUDA install path. + This will be something like: `/usr/local/cuda` + * Click *Configure*, then *Generate*. +5. Right click and *Refresh* the project. +6. From the *Run* menu, *Run*. Select "Local C/C++ Application" and the + `cis565_` binary. diff --git a/src/main.cpp b/src/main.cpp index 9194b1c..e8cd770 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,20 +12,22 @@ #include #include #include +#include +#include #include "testing_helpers.hpp" int main(int argc, char* argv[]) { - const int SIZE = 1 << 8; + const int POW = 16; + const int SIZE = 1 << POW; const int NPOT = SIZE - 3; int a[SIZE], b[SIZE], c[SIZE]; - const int KEYSIZE = 10; - int a_sm[8] = { 0 }; - int c_sm[8] = { 0 }; - for (int i = 0; i < 8; i++) a_sm[i] = rand() % (1 << KEYSIZE); + const int KEYSIZE = 16; + int a_sm[8], b_sm[8], c_sm[8]; + genArray(8, a_sm, 1 << KEYSIZE); // Scan tests - +#if 1 printf("\n"); printf("****************\n"); printf("** SCAN TESTS **\n"); @@ -49,8 +51,11 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("naive scan, power-of-two"); StreamCompaction::Naive::scan(SIZE, c, a); - //printArray(SIZE, c, true); - printCmpResult(SIZE, b, c); + printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); + //printArray(8, &a[2046], true); + //printArray(8, &b[2046], true); + //printArray(8, &c[2046], true); zeroArray(SIZE, c); printDesc("naive scan, non-power-of-two"); @@ -61,7 +66,7 @@ int main(int argc, char* argv[]) { zeroArray(SIZE, c); printDesc("work-efficient scan, power-of-two"); StreamCompaction::Efficient::scan(SIZE, c, a); - //printArray(SIZE, c, true); + printArray(SIZE, c, true); printCmpResult(SIZE, b, c); zeroArray(SIZE, c); @@ -133,10 +138,105 @@ int main(int argc, char* argv[]) { printf("**********************\n"); + zeroArray(8, b_sm); zeroArray(8, c_sm); - printArray(8, a_sm, true); printDesc("radix sort, power-of-two"); + printArray(8, a_sm, true); + memcpy(b_sm, a_sm, 8 * sizeof(int)); StreamCompaction::Sort::radix(8, KEYSIZE, c_sm, a_sm); + thrust::sort(b_sm, b_sm + 8); printArray(8, c_sm, true); - //printCmpLenResult(count, expectedNPOT, b, c); -} + printCmpResult(8, b_sm, c_sm); + + int a_npot_sm[7]; + zeroArray(8, b_sm); + zeroArray(8, c_sm); + memcpy(a_npot_sm, a_sm, 7 * sizeof(int)); + printDesc("radix sort, non-power-of-two"); + printArray(7, a_npot_sm, true); + memcpy(b_sm, a_npot_sm, 7 * sizeof(int)); + StreamCompaction::Sort::radix(7, KEYSIZE, c_sm, a_npot_sm); + thrust::sort(b_sm, b_sm + 7); + printArray(7, c_sm, true); + printCmpResult(7, b_sm, c_sm); + +#endif +#if 1 + int successes = 0; + int tests = 1000; + for (int n = 0; n < tests; n++){ + zeroArray(SIZE, b); + zeroArray(SIZE, c); + genArray(SIZE, a, 1 << POW); // Leave a 0 at the end to test that edge case + memcpy(b, a, SIZE*sizeof(int)); + thrust::sort(b, b + SIZE); + StreamCompaction::Sort::radix(SIZE, KEYSIZE, c, a); + + if (!cmpArrays(SIZE, b, c)) successes++; + } + + printf("\nSort passed %i/%i randomly generated verification tests.\n", successes, tests); + +#endif + +#if 0 + printf("\n"); + printf("**********************\n"); + printf("** TIMING TESTS **\n"); + printf("**********************\n"); + +#define ITER 1 << i + + zeroArray(SIZE, c); + printDesc("naive scan, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::Naive::TestScan(ITER, c, a); + + zeroArray(SIZE, c); + printDesc("efficient scan, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::Efficient::TestScan(ITER, c, a); + + + zeroArray(SIZE, c); + printDesc("Thrust scan, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::Thrust::TestScan(ITER, c, a); + + + + + zeroArray(SIZE, c); + printDesc("CPU scan, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::CPU::TestScan(ITER, c, a); + + + zeroArray(SIZE, c); + printDesc("CPU compact, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::CPU::TestCompact(ITER, c, a); + + zeroArray(SIZE, c); + printDesc("CPU compact without scan, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::CPU::TestCompactWithoutScan(ITER, c, a); + + + zeroArray(SIZE, c); + printDesc("efficient compact, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::Efficient::TestCompact(ITER, c, a); + + + + zeroArray(SIZE, c); + printDesc("Thrust stable sort, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::Thrust::TestSortStable(ITER, c, a); + + + zeroArray(SIZE, c); + printDesc("Thrust unstable sort, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::Thrust::TestSortUnstable(ITER, c, a); + + + zeroArray(SIZE, c); + printDesc("Radix sort, power-of-two"); + for (int i = 1; i <= POW; i++) StreamCompaction::Sort::TestSort(ITER, KEYSIZE, c, a); + +#endif + +} \ No newline at end of file diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index a3d2220..1b5df61 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -1,5 +1,6 @@ #include "common.h" + void checkCUDAErrorFn(const char *msg, const char *file, int line) { cudaError_t err = cudaGetLastError(); if (cudaSuccess == err) { diff --git a/stream_compaction/common.h b/stream_compaction/common.h index 1c75973..e516c4a 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) @@ -34,4 +36,4 @@ namespace Common { __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices); } -} +} \ No newline at end of file diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index f729c0f..0c53b61 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -1,4 +1,5 @@ #include +#include "common.h" #include "cpu.h" namespace StreamCompaction { @@ -80,5 +81,65 @@ int compactWithScan(const int n, int *odata, const int *idata) { return rtn; } +void TestScan(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + for (int i = 0; i < samp; i++) { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + scan(n, odata, idata); + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + printf(" %f\n", time); + +} + +void TestCompact(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + for (int i = 0; i < samp; i++) { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + compactWithScan(n, odata, idata); + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + printf(" %f\n", time); + +} + +void TestCompactWithoutScan(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + for (int i = 0; i < samp; i++) { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + compactWithoutScan(n, odata, idata); + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + printf(" %f\n", time); + +} + } } diff --git a/stream_compaction/cpu.h b/stream_compaction/cpu.h index 6348bf3..f6d8cc3 100644 --- a/stream_compaction/cpu.h +++ b/stream_compaction/cpu.h @@ -7,5 +7,10 @@ namespace CPU { int compactWithoutScan(int n, int *odata, const int *idata); int compactWithScan(int n, int *odata, const int *idata); + + + void TestScan(int n, int *odata, const int *idata); + void TestCompact(int n, int *odata, const int *idata); + void TestCompactWithoutScan(int n, int *odata, const int *idata); } } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 618beb4..cff875b 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -6,55 +6,60 @@ namespace StreamCompaction { namespace Efficient { +void printArray(int n, int *a, bool abridged = false) { + printf(" [ "); + for (int i = 0; i < n; i++) { + if (abridged && i + 2 == 15 && n > 16) { + i = n - 2; + printf("... "); + } + printf("%3d ", a[i]); + } + printf("]\n"); +} + __global__ void kernUpStep(int n, int d, int *data) { - int index = threadIdx.x + (blockIdx.x * blockDim.x); + + int s = 1 << (d + 1); + int index = (threadIdx.x + (blockIdx.x * blockDim.x)) *s; + if (index >= n) { return; } - - int s = pow((double)2, (double)(d + 1)); - - if (fmod((double) index, (double)s) == 0) { + + //if (fmod((double) index+1, (double)s) == 0) { data[index + s - 1] += data[index + s / 2 - 1]; - } + //} } __global__ void kernDownStep(int n, int d, int *data) { - int index = threadIdx.x + (blockIdx.x * blockDim.x); + + int s = 1 << (d + 1); + int index = (threadIdx.x + (blockIdx.x * blockDim.x)) * s; + if (index >= n) { return; } - int s = pow((double)2, (double)(d + 1)); - if (fmod((double) index, (double)s) == 0) { + //if (fmod((double) index, (double)s) == 0) { int t = data[index + s / 2 - 1]; data[index + s / 2 - 1] = data[index + s - 1]; data[index + s - 1] += t; - } + //} } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); - // create device arrays int *dev_out; cudaMalloc((void**)&dev_out, n*sizeof(int)); cudaMemcpy(dev_out, idata, n*sizeof(int), cudaMemcpyHostToDevice); - int d = 0; - for (d; d < ilog2ceil(n); d++) { - kernUpStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_out); - } - - cudaMemset(&dev_out[n - 1], 0, sizeof(int)); - for (d; d >= 0; d--) { - kernDownStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_out); - } + scan_dev(n, dev_out); cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_out); @@ -64,20 +69,33 @@ void scan(int n, int *odata, const int *idata) { * Performs prefix-sum (aka scan) on idata, storing the result into odata. * For use with arrays intiialized on GPU already. */ -void scan_dev(int n, int *dev_data) { +void scan_dev(int n, int *dev_in) { dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); - // create device arrays + // create device arrays to pad to power of 2 size array + //int pot = pow(2, ilog2ceil(n)); + int pot = 1 << ilog2ceil(n); + int *dev_data; + cudaMalloc((void**)&dev_data, pot*sizeof(int)); + cudaMemset(dev_data, 0, pot*sizeof(int)); + cudaMemcpy(dev_data, dev_in, n*sizeof(int), cudaMemcpyDeviceToDevice); + + int d = 0; - for (d; d < ilog2ceil(n); d++) { - kernUpStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_data); + for (d; d < ilog2ceil(pot); d++) { + fullBlocksPerGrid.x = ((pot / pow(2, d+1) + blockSize - 1) / blockSize); + kernUpStep << < fullBlocksPerGrid, blockSize >> >(pot, d, dev_data); } - cudaMemset(&dev_data[n - 1], 0, sizeof(int)); - for (d; d >= 0; d--) { - kernDownStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_data); + cudaMemset(&dev_data[pot - 1], 0, sizeof(int)); + for (d = ilog2ceil(pot); d >= 0; d--) { + fullBlocksPerGrid.x = ((pot / pow(2, d+1) + blockSize - 1) / blockSize); + kernDownStep << < fullBlocksPerGrid, blockSize >> >(pot, d, dev_data); } + + cudaMemcpy(dev_in, dev_data, n*sizeof(int), cudaMemcpyDeviceToDevice); + cudaFree(dev_data); } /** @@ -127,5 +145,101 @@ int compact(int n, int *odata, const int *idata) { return rtn; } + +int compact_dev(int n, int *dev_out, const int *dev_in) { + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + // create device arrays + int *dev_indices; + int *dev_bools; + int rtn = -1; + + cudaMalloc((void**)&dev_indices, n*sizeof(int)); + cudaMalloc((void**)&dev_bools, n*sizeof(int)); + + StreamCompaction::Common::kernMapToBoolean << < fullBlocksPerGrid, blockSize >> >(n, dev_bools, dev_in); + + // scan without wasteful device-host-device write + cudaMemcpy(dev_indices, dev_bools, n*sizeof(int), cudaMemcpyDeviceToDevice); + scan_dev(n, dev_indices); + + // scatter + StreamCompaction::Common::kernScatter << < fullBlocksPerGrid, blockSize >> >(n, dev_out, dev_in, dev_bools, dev_indices); + + cudaMemcpy(&rtn, &dev_indices[n - 1], sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_bools); + cudaFree(dev_indices); + + return rtn; +} + + +void TestScan(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + int *dev_out; + + cudaMalloc((void**)&dev_out, n*sizeof(int)); + cudaMemcpy(dev_out, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + + for (int i = 0; i < samp; i++) { + cudaMemcpy(dev_out, idata, n*sizeof(int), cudaMemcpyHostToDevice); + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + scan_dev(n, dev_out); + + cudaThreadSynchronize(); // block until kernel is finished + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + + cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_out); + printf(" %f\n", time); + +} + +void TestCompact(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + int *dev_out; + int *dev_in; + cudaMalloc((void**)&dev_out, n*sizeof(int)); + cudaMalloc((void**)&dev_in, n*sizeof(int)); + + for (int i = 0; i < samp; i++) { + cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + compact_dev(n, odata, idata); + cudaThreadSynchronize(); // block until kernel is finished + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + + cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_out); + cudaFree(dev_in); + + printf(" %f\n", time); + +} + } } diff --git a/stream_compaction/efficient.h b/stream_compaction/efficient.h index 6e5e6cc..a92f1d9 100644 --- a/stream_compaction/efficient.h +++ b/stream_compaction/efficient.h @@ -5,6 +5,10 @@ namespace Efficient { void scan(int n, int *odata, const int *idata); void scan_dev(int n, int *dev_data); - int compact(int n, int *odata, const int *idata); + int compact(int n, int *odata, const int *idata); + int compact_dev(int n, int *dev_out, const int *dev_in); + + void TestScan(int n, int *odata, const int *idata); + void TestCompact(int n, int *odata, const int *idata); } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 5ce77f0..530ebcd 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -7,25 +7,26 @@ namespace StreamCompaction { namespace Naive { __global__ void kernScanStep(int n, int d, int *odata, const int *idata) { - int index = threadIdx.x + (blockIdx.x * blockDim.x); + + int s = 1 << (d - 1); + int index = threadIdx.x + (blockIdx.x * blockDim.x) + s; + if (index >= n) { return; } - int s = pow((double)2, (double)(d - 1)); - if (index >= s) { + //if (index >= s) { odata[index] = idata[index] + idata[index - s]; - } + //} } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); - // create device arrays + // create device arrays int *dev_in; int *dev_out; @@ -35,15 +36,61 @@ void scan(int n, int *odata, const int *idata) { cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(dev_out, idata, n*sizeof(int), cudaMemcpyHostToDevice); + scan_dev(n, dev_out, dev_in); + + cudaMemcpy(&odata[1], dev_out, (n - 1)*sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_in); + cudaFree(dev_out); +} + +void scan_dev(int n, int *dev_out, int *dev_in) { + dim3 fullBlocksPerGrid; + for (int d = 1; d <= ilog2ceil(n); d++) { + int s = 1 << (d - 1); + fullBlocksPerGrid = (n - s + blockSize - 1) / blockSize; + kernScanStep << < fullBlocksPerGrid, blockSize >> >(n, d, dev_out, dev_in); cudaMemcpy(dev_in, dev_out, n*sizeof(int), cudaMemcpyDeviceToDevice); } +} + +void TestScan(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + // create device arrays + int *dev_in; + int *dev_out; + + cudaMalloc((void**)&dev_in, n*sizeof(int)); + cudaMalloc((void**)&dev_out, n*sizeof(int)); + + cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy(dev_out, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + for (int i = 0; i < samp; i++) { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + scan_dev(n, dev_out, dev_in); + cudaThreadSynchronize(); // block until kernel is finished + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } - cudaMemcpy(&odata[1], dev_out, (n-1)*sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(&odata[1], dev_out, (n - 1)*sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_in); cudaFree(dev_out); + + printf(" %f\n", time ); + } } diff --git a/stream_compaction/naive.h b/stream_compaction/naive.h index 21152d6..4e4614f 100644 --- a/stream_compaction/naive.h +++ b/stream_compaction/naive.h @@ -2,6 +2,9 @@ namespace StreamCompaction { namespace Naive { - void scan(int n, int *odata, const int *idata); + void scan(int n, int *odata, const int *idata); + void scan_dev(int n, int *dev_out, int *dev_in); + + void TestScan(int n, int *odata, const int *idata); } } diff --git a/stream_compaction/sort.cu b/stream_compaction/sort.cu index aa2137a..f780d05 100644 --- a/stream_compaction/sort.cu +++ b/stream_compaction/sort.cu @@ -2,6 +2,7 @@ #include #include "common.h" #include "efficient.h" +#include "naive.h" #include "sort.h" namespace StreamCompaction { @@ -19,6 +20,7 @@ void printArray(int n, int *a, bool abridged = false) { printf("]\n"); } + __global__ void kernGetBit(int n, int d, int *bits, int *nbits, const int *data) { int index = threadIdx.x + (blockIdx.x * blockDim.x); if (index >= n) { @@ -77,9 +79,8 @@ void radix(int n, const int k, int *odata, const int *idata) { cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); for (int d = 0; d < k; d++) { - kernGetBit << > >(n, d, dev_bits, dev_nbits, dev_in); - cudaMemcpy(dev_false, dev_nbits, n*sizeof(int), cudaMemcpyDeviceToDevice); + cudaMemcpy(dev_false, dev_nbits, n*sizeof(int), cudaMemcpyDeviceToDevice); StreamCompaction::Efficient::scan_dev(n, dev_false); kernScanTrue << > >(n, dev_true, dev_false, dev_nbits); kernDestination << > >(n, dev_dest, dev_true, dev_false, dev_bits); @@ -87,7 +88,6 @@ void radix(int n, const int k, int *odata, const int *idata) { cudaMemcpy(dev_in, dev_out, n*sizeof(int), cudaMemcpyDeviceToDevice); } - cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); cudaFree(dev_out); @@ -100,5 +100,74 @@ void radix(int n, const int k, int *odata, const int *idata) { cudaFree(dev_all); } + +void radix_dev(int n, const int k, int *dev_out, int *dev_in) { + dim3 fullBlocksPerGrid((n + blockSize - 1) / blockSize); + + // create device arrays + int *dev_bits; + int *dev_nbits; + int *dev_true; + int *dev_false; + int *dev_dest; + int *dev_all; + + cudaMalloc((void**)&dev_bits, n*sizeof(int)); + cudaMalloc((void**)&dev_nbits, n*sizeof(int)); + cudaMalloc((void**)&dev_true, n*sizeof(int)); + cudaMalloc((void**)&dev_false, n*sizeof(int)); + cudaMalloc((void**)&dev_dest, n*sizeof(int)); + cudaMalloc((void**)&dev_all, n*sizeof(int)); + + cudaMemset(dev_all, 0, n); + + for (int d = 0; d < k; d++) { + kernGetBit << > >(n, d, dev_bits, dev_nbits, dev_in); + cudaMemcpy(dev_false, dev_nbits, n*sizeof(int), cudaMemcpyDeviceToDevice); + StreamCompaction::Efficient::scan_dev(n, dev_false); + kernScanTrue << > >(n, dev_true, dev_false, dev_nbits); + kernDestination << > >(n, dev_dest, dev_true, dev_false, dev_bits); + StreamCompaction::Common::kernScatter << > >(n, dev_out, dev_in, NULL, dev_dest); + cudaMemcpy(dev_in, dev_out, n*sizeof(int), cudaMemcpyDeviceToDevice); + } + + cudaFree(dev_bits); + cudaFree(dev_nbits); + cudaFree(dev_true); + cudaFree(dev_false); + cudaFree(dev_dest); + cudaFree(dev_all); +} + + +void TestSort(int n, const int k, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + int *dev_out; + int *dev_in; + cudaMalloc((void**)&dev_out, n*sizeof(int)); + cudaMalloc((void**)&dev_in, n*sizeof(int)); + cudaMemcpy(dev_in, idata, n*sizeof(int), cudaMemcpyHostToDevice); + + for (int i = 0; i < samp; i++) { + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + radix_dev(n, k, dev_out, dev_in); + cudaThreadSynchronize(); // block until kernel is finished + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + + cudaMemcpy(odata, dev_out, n*sizeof(int), cudaMemcpyDeviceToHost); + printf(" %f\n", time); + +} + } } \ No newline at end of file diff --git a/stream_compaction/sort.h b/stream_compaction/sort.h index 36c72f0..ec3bd9f 100644 --- a/stream_compaction/sort.h +++ b/stream_compaction/sort.h @@ -3,5 +3,8 @@ namespace StreamCompaction { namespace Sort { void radix(int n, const int k, int *odata, const int *idata); + void radix_dev(int n, const int k, int *dev_out, int *dev_in); + + void TestSort(int n, const int k, int *odata, const int *idata); } } diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index d05c772..176e40d 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" @@ -20,5 +21,80 @@ void scan(int n, int *odata, const int *idata) { thrust::copy(dv_out.begin(), dv_out.end(), odata); } +void TestScan(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + thrust::device_vector dv_in(idata, idata + n); + thrust::device_vector dv_out(odata, odata + n); + + for (int i = 0; i < samp; i++) { + + + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + cudaThreadSynchronize(); // block until kernel is finished + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + + time += elapsed_seconds.count() * 1000 / samp; + } + + thrust::copy(dv_out.begin(), dv_out.end(), odata); + printf(" %f\n", time); + +} + +void TestSortStable(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + for (int i = 0; i < samp; i++) { + memcpy(odata, idata, n*sizeof(int)); + + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + thrust::stable_sort(odata, odata + n); + cudaThreadSynchronize(); // block until kernel is finished + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + printf(" %f\n", time); + +} + +void TestSortUnstable(int n, int *odata, const int *idata) { + + double time = 0; + int samp = 1000; + + for (int i = 0; i < samp; i++) { + memcpy(odata, idata, n*sizeof(int)); + + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); + + thrust::sort(odata, odata + n); + cudaThreadSynchronize(); // block until kernel is finished + + end = std::chrono::system_clock::now(); + std::chrono::duration elapsed_seconds = end - start; + + time += elapsed_seconds.count() * 1000 / samp; + } + printf(" %f\n", time); + +} + } } diff --git a/stream_compaction/thrust.h b/stream_compaction/thrust.h index 06707f3..5790b94 100644 --- a/stream_compaction/thrust.h +++ b/stream_compaction/thrust.h @@ -2,6 +2,11 @@ namespace StreamCompaction { namespace Thrust { - void scan(int n, int *odata, const int *idata); + void scan(int n, int *odata, const int *idata); + + void TestScan(int n, int *odata, const int *idata); + void TestSortStable(int n, int *odata, const int *idata); + void TestSortUnstable(int n, int *odata, const int *idata); + } } From b24ec21c77fc1ef9230387616567b185c029b5d3 Mon Sep 17 00:00:00 2001 From: Michael Willett Date: Tue, 27 Sep 2016 12:13:12 -0400 Subject: [PATCH 3/5] finish README.md --- README.md | 86 +++++++++++++++++++++++++----------- benchmark data.xlsx | Bin 0 -> 31816 bytes imgs/compact_benchmarks.PNG | Bin 0 -> 21066 bytes imgs/scan_benchmarks.PNG | Bin 0 -> 28854 bytes imgs/scan_performance.PNG | Bin 0 -> 12527 bytes imgs/sort_benchmarks.PNG | Bin 0 -> 25019 bytes src/main.cpp | 12 +++-- stream_compaction/thrust.cu | 3 +- 8 files changed, 66 insertions(+), 35 deletions(-) create mode 100644 benchmark data.xlsx create mode 100644 imgs/compact_benchmarks.PNG create mode 100644 imgs/scan_benchmarks.PNG create mode 100644 imgs/scan_performance.PNG create mode 100644 imgs/sort_benchmarks.PNG diff --git a/README.md b/README.md index 5f50304..359dd20 100644 --- a/README.md +++ b/README.md @@ -9,40 +9,47 @@ CUDA Stream Compaction ## Contents 1. [Introduction](#intro) 2. [Algorithms](#part1) -4. [Performance Analysis](#part3) -5. [Development Process](#part4) -6. [Build Instructions](#appendix) +3. [Verification and Performance Analysis](#part2) +4. [Development Process](#part3) +5. [Build Instructions](#appendix) ## Introduction: Parallel Algorithms -This project explores introductory concepts of GPU paralization methods for simulating flocking behaviors -of simple particles known as boids. Boid motion is based off of three rules calculated from nearby particles: - -1. *Cohesion* - Boids will move towards the center of mass of nearby boids -2. *Separation* - Boids will try to maintain a minimum distance from one another to avoid collision -3. *Alignment* - Boids in a group will try to align vector headings with those in the group - -These simple rules with the appropriate tuning parameter set can lead to a surprisingly complex emergent -behavior very similar to how schools of fish or flocks of birds move in nature, as seen below. +The goal of this project was to implement several versions of basic GPU algorithms to become familiar with +parallel programming ideas, as well as compare the performance overhead of memory management on GPU vs +traditional CPU implementations. ## Section 1: Scanning, Stream Compaction, and Sorting -The boids flocking simulation is naively calculated by comparing euclidean distance from the current -boid to every other boid in the simulation, and checking if the distance is within the desired range for -the rule being calculated (we use a smaller distance metric for calculating separation, otherwise the boids -never exhibit flocking behavior). +Three basic algorithms were implemented in this project + +1. Scan - consective sum of all elements in the array prior to the current index. +2. Compaction - removal of all elements in an array that meet some boolean criteria. +3. Sort - arrange all elements in an array from low to high using a radix sort implementation. + +Two implementations of GPU based scan were implemented: a naive version where elements were added in pairs until all +sums were computed, and a work efficient version where the total number of operators is reduced at the cost of performing +two separate stages of analysis. In the implemented version, the work-efficient scan performs slightly worse than +the naive approach from the extra down-sweep phase, however, there is room for improvement with smarter array +indexing to reduce the total number of GPU kernel calls. Both algorithms run in O(log(N)) which is theoretically +faster than CPU implementations at O(N), but performance analysis shows that this is only significant for large +arrays due to array access overhead on the GPU level. + +Stream compaction shows similar trends in computation complexity in GPU vs. CPU performance, but catches up to CPU +faster than the scan implementations since GPU accelerated reording of arrays is significantly faster than CPU +implementations even for relatively small arrays. + +The radix sort agorithm iterates over each bit in the input value from least significant to most significant, reordering +the array at each step. Again since the reording step is highly efficient on a GPU, the major overhead is due to +memory access time. -While computationally this results in the correct behavior, it can be wasteful in the number of comparison operations -since we only apply position and velocity updates if a boid has at least one other particle close to it. For smaller -particle counts, this method can achieve 60 fps on a cheap modern process, but scales very poorly as the number of -comparisons increases. Detailed analysis is available in [Section 3: Performance Analysis](#part-3). -## Section 2: Performance Analysis +## Section 2: Verification and Performance Analysis +*Code Correctness for Scan, Compact, and Sort Implementations:* -Code Correctness for Scan, Compact, and Sort Implementations: > **************** > ** SCAN TESTS ** > **************** @@ -119,22 +126,49 @@ Code Correctness for Scan, Compact, and Sort Implementations: > Sort passed 1000/1000 randomly generated verification tests. +### Benchmarks +*All performance measurements were averaged over 1000 samples on a single dataset* +![Scan Performance](imgs/scan_benchmarks.PNG) +![Compact Performance](imgs/compact_benchmarks.PNG) +![Scan Performance](imgs/sort_benchmarks.PNG) + +We can quickly see from the above charts that run time for the various GPU implementations for scan, compact, and stort all +have logarithmic growth, while CPU implementations observe polynomial growth (for scan and compaction, CPU growth is linear). +As a result, for smaller arrays the overhead of memory access and array manipulation results in significantly worse execution +time, and it isn't until arrays of about ~50,000 elements that performance is comparable. Unfortunately due to implementation +choices, the GPU implemenations crash for arrays larger than 216, so performance analysis could not be run for these +large arrays, and estimates must be extrapolated from the current dataset. + +![Scan Kernal Calls](imgs/scan_performance.PNG) +The above figure shows the room for improvement of the efficient scan quite easily. Compared to the naive scan steps, +the up-sweep and down-sweep of the work-efficient implementation do not utilize nearly as much of the GPU per call as +the naive scan. Saturating the GPU here could yeild significant improvements by reducing the total number of kernal +calls required. +For the thrust library benchmarks, it is very interesting to see what appears to be standard linear growth due to the +performance loss in large arrays. Preliminary research on the thrust sort implementation implies that for basic sorting +of primitives like integers used in this example, it should also be using a radix sort implemented on the GPU. The charts +suggest, however, that thrust either not properly parallelizing the data, or it is running on the CPU. Documentation states +that method overloads that accept arrays located on the host will automatically handle data transfer to and from the GPU, +but that may not be the case. Additional work to instantiate the data on the GPU prior to the sort results in an Abort() +call from the thrust library, so additional investigation would be merited. + ## Section 3: Development Process Development was fairly straight forward algorithmic implementation. Future work could be done in the work effecient -implementations to better handle launching kernal functions at the high depth levels when only a couple of sums are -being calculated for the whole array. This should +scan implementation to better handle launching kernal functions at the high depth levels when only a couple of sums are +being calculated for the whole array. This should improve peformance to be faster than the naive approach, but I +exepect it to still be outperformed by the thrust implementation as well as the CPU version for arrays with fewer than +30,000 elements. It was worth noting there was a bug in using std::pow for calculating the array index in each kernal invocation. For some -unknown reason, it consisantly produced erronius values at 2^11, or 2048. This is odd since variables were being cast to +unknown reason, it consisantly produced erronius values at 211, or 2048. This is odd since variables were being cast to double precision before the computation, but the reverse cast was incorrect. This bug may be compiler specific as running the index calculation on a separate machine result in accurate indexing in this range. Simply changing the code to use bitshift operations cleared the error entirely. - ## Appendix: Build Instructions diff --git a/benchmark data.xlsx b/benchmark data.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8a939a59783618659687f94c709505ba50421407 GIT binary patch literal 31816 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 JZkNCQ`yUUq2zUSh literal 0 HcmV?d00001 diff --git a/imgs/compact_benchmarks.PNG b/imgs/compact_benchmarks.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e759b1a855d078d0d60793892625c7845aa3a460 GIT binary patch literal 21066 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 jss&t(7P%WK5IGTV zO(@Eu;_oF}1XqZxy;6QP=)=Wo_t}r&)4NY^3~@3(-n~bQY3IxRfRgLhT{`A)pAUC| z`Y&J21>Ed-Hx`Z;xRi*v4;G3Pj$gRu(`yyffl9ozXWg%|-S^0S8S(yV)T}-fSmMOL zn~EPc>L2fze}gX^Ux+newsH~tX^0KJEMEkLzDHz2*}$DZ$pdiRPwW-q&KIj(D-Wt9 zhcCGNO^BE90vF$N4LX_S--h5ue45DyXs_V5-cRUkA2<>FiJ@?}-)V8*dUJK6>SG6O zw(hbsm$5*nwa1*^a-~4!1t8E+P(7)JLs6r7YiNzB>gvN{Xwc$&{)o^!qUSMl(-(Hc ztF~cn8i)Oh_|haJ0nt4U`)QhKPe3adZGOKlEIWVUIIqhfvr>%9W1nlL zZ`ICj9rb4FW`8lz^>TTG>}OVQi!KHIUbeNhXQ*kPVULtGu9~yMcE{SRl(oB2-5mqU zK*@@Ptr2xHI1vL~&CVSHePIHDGFM$%F}o-thUXJ1(?ctvgFXMPlkj0dQ>ea2Gf-ah zam^I2+BJ*1ZaTL153S%2j=YB_)z%!dXDFsfp#~boH-Q z=XbiDCEDr~Io%vHOIx?^>Jm9U_hT26GH_AFYPFV61H0tw&-(DD9IOr-*_W~Z&I9D)ahXthT?GBOl?29G-;?l?^~K3>o3|HW^c@3 z&}sZ8=yjm0Q+_66TzkJhDAn5L>%^Mq&$jV(x~)8?d{*h?J8KtDM6Se>2gdR;L@%m_ z;0)7FhV$|#4c|BuI1cFDJO8()JQ5Y2|oQ`*lvRakO`j{eq^zY;zfR1GXo$m{Fg$ z7q2L`IVG|8xMl0uYcpkD{176#9xU&0Eb3!ONi_bxSYAy7CG?1gGH>V3ryeK}g71#y zaXci%=8*y@u?9lA%eiP%U3E|OEKxk@1$0;NL0Co9D={QF5Q-6ql9|zLD@L$K4Y_Q$ zZI$QluuM6k2$t&Z$T6ymHiYZ?sr7)H*L8Bm9C+cUGhFK&i)t5 zHxd#lG@>yGrvrx4*P>}4i=dNKPye1Gv9D=2EL2_wqtSfE2O@lGKGAfLvXi6XzM96b zbPd(!*RwpeQh>+acn38sb1GP~aSD|r~Gx8bAA^(43A zT!u~u!rllrK5=Yp@7z;$vA9GmUrcOmO`NE>o>AB6@OzcY)fKDdlpRE#ZCP}NURZ{< z7z#41+AUVyfm4RRf4#K0JMWR*qVMUu8gH}+qKC*tSig9!zpW)*&$K@ z%+0dP78@e3`84B(=`bzlAh&w&>)qt~i%+r^u~J$Mx*O=tF7h)GP|}V8PyWVm{mN(X zz9;JW8yx#YhXo)JQkEN5nd?# z!YOfSZwR^3yN*rga@7Bf&!n9wc6VoOJ&r|aFo0$wm&ih&1LEU!zF4dC0$x=c8qa(0~%(U@+9bjXF~oEeRf^iMOz3(93qcmc;f*_&!7^M<>qoxL^+VtVTQv{ zzJm@LMqhmlq!W-)o@w;=5-}|f75NqgD*G8~gi}HmYvx@1m5PpL-NwVi6+=4uS_#o(_6?N`j%LFNruAvhv@Rl`C{VA6k=hY*n`lG|O<|H=quAP`p5=3ei z1^sqn70U& zmR}5If$|`F9Ld$C&NMBb-8=FAJj#`+ZS;|@=yQMOKG|9B@8oylADHJCY>O>h(m>~@ z@(!rWkUa9TQq1*=fimXRkGlFhYLorZhW!K5r#Biih*FvPxr^YwtqY!d!1IQ*kGxI? z6Vm7D(EEENobkw0$^DEu)`iV0!D~IBM%l&B&?Rx5fd#VmU2V?EA#3%ZJ4 zsGY}KpEx|!=BQog1sQbT?k1YfG3@%Qy5kND@ZCA~^WST2D9}1SRevmIyaEJz(ywAv` zs%M|k-$&iwxJ7k-`2C#+PgwL?#yF3s=HCn5a6)ED`0?9pqn-qBDt#s?0pw1wV1g## zB4z*3nXS`7P`?vQ!r-QuQ+7qfzN;&}*R!V^eP2sQWK z4q6mW>dCDjC~8X%~*GH;uvn41bT06%gxlld`+ zXE8nzSla#gyukdW|F(Puw%6g&GtK9UDe&V+dLrolGdiKmr#sUq!P!V4l%-M50a~}% zqUflb8bJ?DIeo|GR4FP^P}N~DZi^b(1v|n3A=WxB5-A952;4X8!@~ni&e0s&lL4Vx zs}F+YW>e2}jc-GyDq`lr&X|yl`U#HpbXS_n5oB7vMm{B6VMg3l5$nB&C;38_o&`e_ z7eSCYFCr_C6`CUK>D-2fUe+aSK1J{F^REsS8mr`ssXNHacKRAQ=A}w8=_-ZYFBGR* z{__u-dSTLHeiZt>i^$e8Jr9{&N3LYC!)nwtCWN1hTYZ+Y1j^ic%-_O?isD+CHebi& ztSBY35J(9TPbK1xTU5Qe3y(dIyo6 zQ9}aA;76D|mB6u~lVwht`9VdC$aO1HiZuluG@u_73ThIZ`@Cz4{|lz-wa-BNHO{KH!#VZJp$&NVRil zF!ZNe+o{U_G)P1SahvM}ZC1*Lu1yPU-J+DGv1I%4ypE*HZPb)a_!+NyQ=IUgzM@Ny z5oE{hbKD-%g6N~kd;Zxw~oqr zg~L>F_2CUwqb!^_H~7WvXrFO$d2Hbn62?l$+Uu>QBl<|q4+s`QNP{pXe5|5{r{t6~ z{t{NZF3OdRiC)m8#V20Q{d3I6{dw=8X9pjt> zH54$!g>Z`%+i}iLp5fQ3`M~<*{XofL?w?b)brq*iwR}JVjk-4!`fTYI?XNfE7&{KL z&OTsX&uvjT6~*JO37-{TzLZ(jPAjNrO%G&q^|ECXfey-@t4Yn6l)v#oPyg*u;xt>mwWqvsvA#E}4wZ`=O3p+XFL(gglmMAPUmky>(yf0LLZkZ0bl?9eJ^kPREm+lZ9Q4vs3fxR(8wEw%7b-qYxR0F9 zoG|V&3ND2ebgpI6VWszLPjQA14uO%(H`nv7Bd3=a#z!{2xa%>Z0%*k@T+YNVsf8=@ zvm>vk+66J}E%rSn+*tkkjg|XSmF8%&h96R}?jPEMvYYz4|+nP#o@XMm%#IAe`|@=WQzw|KGzT`{yu;v7Y$>AjC070sNv8 zw0rTbF(ETVQhTk$sp6lQ@O%@+0i1X}-~SK8Oy6!UC{bX~;-DVNkz{cyrCZNXtx-;U z_hg_dD?q2b4Bq|{#L`ohm8HOAF-)ITtOLgMjmkeUekVUoSqt}MlLA?pedQkJeIBgo zncu3dsq4{it>d-hntBAzwfZ!$cHlfkJfi-Rs6(VJ%!J8TRwd-ZH); z9MxX*E~*VtF;s2ruCc%stCQ1gT{|^Xw{m-Prnq&3*ec#RlBn^swXXZipfdFtGP}1C ztfRD3Twxp|Cn2o`v(nuSyDc;b)#R22P1w~CPNH+9+*WVNUCsdpj>fow#%rpBW8%)d z(()nZlg47n*l^2KDHz0Fto;i^$l6qlp5R@?$I|JOI{##(Y&Xy5!4IeM)>*e;ttsKH zkr33=9&fnIZO@Gt@}@9R0Xh7f(JQ_iaCW$U#@I3XhJ1Y4xVj|?h`6eOC_jP)r{%dE>9E(AVFS2hh z^^E;sS+hu~W4LUAOlge5o=-e+PC^Fe&JlR3IKx>{gMTI~ActiXR&agB*Wkn?8%NR! z(>BO@*ksW``&hyMr&2b42fG?)?S-4!0rryV!)*=ul!ptt!?Gdz*>6~Ulf^evp-i%Y zk?GF&6`v?)-WqR`&rEu<`uzzZ<}g-ll`j*voZl>{ldIiTKX=cl%}n0zhrvl3UN#Bh ze7T!*v_Yga;k_PflceyVz-g0wn+y9hRP$$}h1bBrv!yntC0Z2gW5Zs}Y-0+c(wa~t zGWh$dPimq5hMU`K(01#Jz|gTr%VPFSDIu*Xsp~A`xtDI-LKbkdH>1d=6v)Otle1LM zw<14(za4@B@8PzjN+CBo0^_v2o6~=kRA0J_iRYUx%z8n_x!Fq(hmsYw1EO(+&p^EO z`I&o@8WV;GI(F70QY&{iGCXJ}*$jE=JsB*F6@1lUUDBPC#*SO;y+xx5f)}EzI(i6E zZ7*lP1!C6ZO)sj+5lu2Ufjaj?MfK|GJR?LuDIef$#z44j- zxLBhlkhH`)Hd%vH47zU)tM3^X^M;#L6grQqJ`OCxi=ouv^A9H7# zaVFz-Hu4gTFnNp+LN3*>_>fx~F#*P{4?ty4BO)wQ<%f-%=;1m8}jQ z&M9wD+iQRb0hXOkO~q;r&vNI*4cb7pt{=ACY}u()s}*L&+A}pI7PC-vH5a7 zn>lB&1hMyR;M=5Z5TSmVN}6m0%UtnVF?~Dua-035+{Qxt8B2WW=npoHr=&2giYPY2 z)SW;c-3sM$smL$p5($qItpe^ov>zKMGki_xYFCU6AJL4l{1I6oT`S7IRxr*iPsmcI zNs{_h+n8s6xCcgV^FsY|yRCMVot;*pLXQ{$Dx{J~XZqG?CF3JaIORb0!zvp7(mQCQ z{~*J|59X7oiuL>{wwAdBBJRX~mkO8=2cWu$$zo)=rq?V#eyR|86(2(0{2T#9K~ z`{|L`3(p5)##C!@pjATzjk)Id6%S8a2y99y*Wf7zw>vi56I5xtk8uNT2O@FLD~uE% zB*X@pD+){<&*DR=vT3uz$x$TSW1bbM797bhSAqvZK*%}nF>a`y74VDayiC}fxhda0 zD7Rw@gUw{bfX!;P|IYcR2~bH0^Pv#O>h2Qcf|8qz47}IR`YFPC#!3E{$E(lXAv z8)>dj)!WPkkX4VSs_}0Xy*fpqR%-w<;U0d(~Br|`4md?z7Ws~ zdDFutXmjtI2VsPAsh3(mMVPqc3w83WB5(hg`E{bP%z@z$Q27Dxn4H!#)InjCv7WQb zN>3Muw=NF;h~P|9ripR5lQjrV87~(c%bHhCRuY3+u9VCQ{GAD&t6ewu(pJUqz2tt_ z?tQGLqQ(YgE(vQaU~Wq{ZZU>TL|DX}!l;7)oEL7+j^3WiY7MW12n|c&NcuJBPI zU!qXo%;{~@n-g-Smmw9{DnIm6>9P3JrBRq`;`16VI{cWJ1EAxN6)Gh-o=ANDgG6&bgfy)0 z_7#N8R7rVrNK%6C4-ldgW-$Qbil!>LH@~@QZb*o3>g7rQr^cXI6S5kc&}-d5u40K~ z8uZT9{l5c3|2glOYAv*rzSHzNOJSO`r3`HSizm_Lvw!d;L2Lhl;RnR zBHEo0_zTuT{|Rf9fl18(PRfDrlK=a282F$))*pW}>A%+((R##T`)?+-{}(1j7n>*- zuv?!y|IMT||H7n()l>v%NT+@AHN!6YaC3t zfnX=m-%R>%=naH$B^w3(&7}V}%miQ1-Wa*L*_(z9S8r-WUi3~ufXe-_es6=lwkm2r{uWVLgd~MDCcNoKI2 zg|e!6)ScW5YK?)o5`~P%F71deA>A3Jq_4s;G#b}m8LToUyoZeUXAstERjFpibjLzy z(`&73@&gQzg?-nu=xdUWbv8sGtTQj#sK6r`E^%z$Jw!@4ev>x6~cKI$o9b3ElrQ7qn8?R+Eka>5>tKi>VeQDn=Ae>IvPKi z0ux{-J8z!e6Lo$lOf9fuJXI2L(}~#2JjPAt>+l9E3S7>L4I3RWYXT<`C3OiMdWe^( zsT-2n^Jl;(89R-8FBP$#6Nu_xf!v%Ue7P}sMXPxjkH_~PH16B!J}83c_!i!o;LNbb z1bZ;(d(E%8Z!Hz@VTA~jAN@dOe_@Zrg`Ff~^%+l9VdQ%mK$fY#(E=iEdAj|%b4>+} zR8CX9@?b;hA0DymuEESEC#khK$UPaxTfZ~(-1}5xU}~_Frtk~cy?mXMWhVt8t!c2gvf%pJRXM$%_a3+QG4Oy0lSqP<4eq{#ft zCkWL1KrrgW{y_O9qoi?iP>=Gs%LjkKhJo;^gNaJetU@xzOS1Qx_{MXCq3&C@WjZ(l z5nEnVuEy_4;P6Gb#7d$4l}%t?xvkT{=i^qgEfT{H&Ou7_oQ}>UJ(Ky4YqoNyS%ktZ1C{{CSP%y@nm;#P+Jnt%!P2>23yG{0Qz@1h2RT~#w zp@!FRFSMgnnI2{;RpL0G-tgIm_A|Ka}?VlZqe-c#F_$l;@28NDfO09GeSa6X`7fmLL3$ePBQPa39l`ZAA zo=-uIQC#nZ&llR$k1_G#lx^2F81Q9h33~5_@ox?okbw}G?iXZi_RhN!X!m$l`OXKT zSInSJ$_3UcV6AP(>elrnkwcGJ3kjQXsqSSEqkCwky|ZU;`mu5_S>CMt4Kolz#(73< zTbY?jvw6PVvnxml7fc3uC0luv>Z;lNwEhx9FJ-#Of3Z8}Ejydaa-UmgUzN=%_y7Zr zzA=I=Sp|5uD)WMBusb*cz!Dcw7Q!iPr|iScofD8Rs7a~XrIQ4p630^cTn>} zUqUBC82~~1tz-lD;7GCu-ta+m7{P+@dvHM~eT)Jk0PwyLG!LoTR)&)+tT+I5kT;M> z$O;$}nqaHvMFCehv&4ii)at)B$L5?S0E?A2_Qn$IrEtG))YW$OGGICZ6MF8i5x^~T zELjSD3aFR1i#||y&BPXWW$2oL38M>4dt$-V(QDd2tx#Y>D)=^gK&@yt{iAyD?l2B# zU09T-su=~kh>z}K>;Mt`C_hr5I7IC+7*qMu8w+e+r?=u6cn1=^;}Ps0Tsq0vZAajH zo_t6}(>&4DBtO^5(Z~;DFJ{vR-wLz+QG8*XX^pb4q#&Y7g^_VqgxMCNY4H?BG^@|m zhd>8|8}l*ZCUtDMrNMz0LO#&K0O^sjXSshnZca2_b?)IO?F}c7IL{;iap1lB+xMCQ zjd1Dt$WpH#%{bIbMqX$N)t%?;Eq(lS*TN4(z}rohiKjzyoUeOEqhb1e`crR#cV8QJ z>64-h%qJ|ybmPC{cwYjV=Wy3bkPRBN==4}bs7&%v!Ha-#U*ul=7$&5fr9p6rjEJDje}H;RIqI~3J#$%u!D(mh)l%QCqY-8Bwbh8S4D_vB4~29nlj5GDHnkDs z(*$Gg#IjzbHgFk{!TLtAJ1j3WJxye03+(0T8G5Z;YoB(i#d0%6|-G zsBcZ7kGl#lM}7{%{|n&z|7PSrJlAxB=7NYGcJ{Fhz1`7f$tQp8$fAU?FAes=wKzw? z_h5BTujwRoQ$x;!E#&oLx`=#v)ayB!z*8MVD?8xs`g|S_{E9H{!NZ3-$9Y6bgovq; z*G3#RIYq%VsAn)Wcb=hwe={PD+V|P{tS9F}xCE>Psq>Z4PS|LGKyeo4!S-+S(cuPv zqoab>qEk;iWqJksmK|iZmW`DpcWV~4d?iA?on62Qk9>ExoX9HvHO0n7HKM1QO{lxx zeZuo%@xa1KZ!l*oZU1O6sCPq0uj8=9J5V8nDam9wOXxD{LLc#EW3jNX*SgK=TkonG zPygLtwj=AY=KfbzuL4i74#)kqxLf1uK}<v*njlho({4`LoEZ4YbF&K00R0 zbl3YrL!GsKfq4|~B^#`S+7q>zro03eh-zcE^O$TeiZLkhtRJiBfzKR|UiNmpVb%{u z?GHFPu~sMr@eh@UVlf6+rp4okCcpRfrNl|RMV$z@ZS}bq!2M29rX}HH+LKk5=+2=N zM7oPqLJuiB3b`e>e`avUC-(j6)lzh0)uFE!BTE_QI_ra8CevtBm*%=x+^^ncl#9B)gmgmQ- z&UZb9tz(*oWhAzkYp-<2!MJfb$~1bozr4d-y9EsC+te0CVIYtv@2(x1sGSOpNOd2^0CGlHp~Wv4984Za`(_!cU9rs<9Vrc^0F)B0uzspGx~RZ?&!% z<9~u;XT|8=zstF6j4w(_aN6!|aHwluF|sd5P(nl}drP3FboL0%La?@xlRbOmtr^Q;QO1mrBO(ZuH47{iD2 zNCQfcCW>dSaCVkz-fJ-B+0u@z2Ep16&+FElgZxv z_|{AABhy=^`DAbs1Loe({xX4rE?J80IkAdpNaT~_$CFTbhBon3Nujqk*gqaTPkGo8 z^npeH)CVuLl7gHV?lDaq0Ieu^SIHXwQgVU?+vBtj#630!fDq^nYqml)dIlboqU=Ev zd}C4=(D7m&pSI}0rJW+rpSSg-W&@}-z1XXS3*ELpf0l_T3PcxbzEWdCNchdcy_fxjmln-fd`R@>gQH`_opRC0HVJ^7Zx z@S|%>Q!=mspH;I~OVUcQP)~BA8()-Lkj1rDhxO&Qfe@m81pgPQDba;BL9S zQ~`SUX}$1rQQ&1td5ft+q-+wJJV# z?T%4h@>bIYm*MI=3(Qq_bj;W)NpQW_9lx!h{rG^c(8)5OVm9lSpCs3}U z5LyFxd{_#CS6dF3&>n=q4&{Tt0wBBQA=-4oJ}f|tOOg_AUBA&p4f0!0&sRjzu>j3d z%RVrC4^>U-;;OpDl^$5O;R>P@R8pFYnbR49zInSpce^Rn@ZotcXlLF~F2&Lc8ujh% zLI5ZZ+4zlRD31=VWnPzF>6sz}V#QCYUe8z-St<&A#HpsQKRFfxdR}X?^Vi7!uoJJ@ zZ+0sD#ZDDyb}Bffz$Zk@3=TbtrFD~~`uQ{J)WE`MTsW&{WF}>{qc-V+Q6()a9*@)h z)hmxj3(Re1eV#hB09ry6Ny*;uN$7SOPnG^TGr>B~6l0Uwm{WJ!Q)PS;W3aQllP^g3 z^-@o1I60KL=dJjN?Uum_Y;SFYX5v@T^SP>#CVW$+_z@VVq%SYmzTiIAc>WONwS-5F zxn&CLmnS8x|AIv?iLq)hpp)pj;&U}qPNw?}y3fjg#cxkuHc;h3?t>Gn;j~`j4vmiv zm{{VU+5TTEc@pBo=R|BD@Y>9s)dh=|0{ zAxaJ@(s}nVsHorjJoouo5}uHi}WGl7k?mOU00Ndx+S z>}KmsBP=XKB^e1(RcF10kwE)bQZCb5doi@%o_==;DR}Vk5{siC3BTjR>Q~V{e1g<` zw^E~!>MtKcN60IU9f*tNU=#`!Ww{aUgG|h&LO4OT&69)(18#la-^*c+L zS|(05S2p1ibZt+<2!E{?>UVJ&GPd!?^+)@`J#0XeNIX8+1Qu48q$m0->^2TA`0^}1 z13~|o_8bd+5yTP~AN}K_OCsnmX&P)C@Fg<%|GMGYMz6W6JAH7-0(GIO}0k9 zLPh?W;HC&6Yn}l-EHd;hFJHG?xyCxj%*Hx zr9N5FT)%gYmwW>cOM(GBnD7|^R+c2KFTsT@Nn9pZrW&b25lEfHdgS$pr+`|&K=5($ zJud#8a>XWGU)6|{r@@v)$RJp+-^E6HJj#+}oS4uXv?D(uQYca|yu&oafJNqpCPw6; z(YxZt&ZmzR7AZEuo7Z8oat;J9AR<^Ih8MvH_DzcPbY_3-7%W5K==@2y7@A4v(G4st z2~t2*kZ0TvgQ=@&_IB5HhMeqVU7we$ei&^AO!cgn03R2_c(3q5BQs-Q41k)mEa(rf zM)3TRNQpv&Fm|$7xwJAO3aqoF=)au5^GNa4FgxpyK(BYczKxL{MS>%NeTCMA3urp! z4~ezAZ(XX&x)SJQ9>K!8-U$i8Vc#c4_0{26R3G!XM7h%_PMv0QXq)6uI?6NKJzW`C#>>w za4g1D()COqf#_&yF~9k~!9iJ1^j}skKgO;nIqfJ?AfpuaF6=yHUM@DGngsmCGpXey zJ}gNUTU`37w8It!`f8r=O5aC7aBMFTcNsx1K4Bm@A2%~Iqok3qm)>c5i~6}_YUjlP z|9}9Wn?u8ZoUk(|2yoxqMP@erkc3PWX2{bDx$uCPezG@tJ6u)03Hag)KTWQ zx$9X#2Kf@Ca2`DHXd`SmzJC7~kmi^EZ9PqUs4IUo2X)_Edwcbp4&y)DJWE1Al>eR& zsdyW4t(fO3AJT7*qK?Qa+O5roWE}#zeDaVCN=z^n@MK^=O2EvQcJQhWD9CYM*I5rec(rK zH}D|Ky$BrE2g_WlFS?WX3J7%z(^ttk{(J8AZNC1bjyl0aC$Gggu>1Nq7una1{xGU9 z{`kbXJVzx|VB)F({y)d0P$n`*oB74N=#s~=|k z@3YhsalKPDQqNFZ{3BQiQH(M43G;M)*rMLv1jOW!dfNYW)oz>{-T$T(tST3n7()J$mCf+LPh8`7zLUsW zt2<<^P&ldfC1e?N)QkV40$O^_3o?k3bbUN;ZVtEYxAOkcCx?u?+6uCF@w$_*qJ8}>GySJ>?79To#N@zfhPgh+?XUnMQ6`Fsq zl8cjj;cljzXjDoiH{>H23^l@$V5d~cR?qFqO-p0yuTXVz+OXe{`Ab(nrR-3Is_hNH zP`QIvbZ7OPDbJEO_}nVzX1hoZk-vOhJzp;u_)%}E?@YY0(O3Sk2j^f50*MJKtgvQe zGc2#!54ZfB`TVBkn#ka)AhyZ(#yl24)akwYs z5(SsGsHaGcEmpvh;k%kK7~bihs?ofPYYI~}9QlOpM#cPuewHm3%W&x%1!N*L5SVDR zrKa3-oN$fV-pf;1)3&g^ze+Q?fXWt3Ks7eyrR4{@(9qM9e6p~AW58*sUFo<`&t%?h z1Z6qz<;k|?b5ck7&>X8J_lD%OLQ1vnRh`TJ=n&Qx7G#)Qs)OEUIq|GQ3Z+iHuj@A~ zk|WcqW{T*xUrHXdSydkXtXHy)zn9eQJY2d=3p32I04y~2(Zv3Uw*m(u7XKbn3cmG0cGRmULwEN6VIL7=ir#z?A#^r1V0amx!{n^ErJ_ z6&Nj{q~ceCu9Ms8Q@a-meuMd`=+CnC$zx-0!I6P z^J^(+DbH4^2O!29PGnREoF0xIA4#7_yW ziOqH`LlzSJ6Ai2-PX2vmc@ACN=J4Z1z2QOP{1~VA_ab_SuJWy2w0TPTH|y<``R#a^ z%O~O0jy}(XS}yxOlhi2_N*=YGccWzDTWdKn&Im$kv@8C75FgYRT1Fxi)kUwDewLC zW#$rMwm-LQ>-3W4tQsD_Wckbc;TY5S`<_=L2gBQ57}=JFI+C*co_drtUzrTdx&A%s zCLfFi|E=7Q&3;M&tH8_829$1<{6ZA-e*gU~E=W7XB{s2`q_R{!HhrDldt#0I>u0$4 zHXjvG1s#PZzYnt2Y`W`9=yo9!1$CNR)pSM8-c#2OTNO0x^Um9ABgJgsTo8_W7`?VchjyluZ`us1f- zT>h6R8{D}RiLt`%@XfXtj(YU!F@u%f)npFjl#;@mD%>l(bNdM{!w&4f;*nqS z@OU^;)f~F*Agzg39jB4F`$-J6s@E*_bU3zs==#2Ls-!p)HgEO`2NIEH2yX7?dkYo@ z|LJYd@}NT_%09EO^{%O=e0|P2rI#Ti_fIr**RfG5^ZSW&LQz{Y*N~suli(R2?jus? zZ|*ry>sw{ZUH-BgB+ChAB?GpbbwlS@2=(^xW8#_k0UUZHiPQO~aU5DU-B(lRAh zyWy~weLz1SuKQ;k@rJiGZ6>n%8|qCjv#etzKf53)LWOs|*a}U$r8%7R-{KraR!K$aR(VmUKCW)M36SLE$?SCS?~kFGnFXg$+gAbMfNwM2>E?}6il|Df`A z7CbLc-NC`ZWr9&-TofbVKcoGm`p?3vO8JC)Rh|A8=(GJeSq*;+mk$+?SG-njY+d5V zDbo1g4TKr;>WHkYtat8rqsjrPb*hn(5u%~AT)k>->kiywX#s0R27@qK;bVq_xT#W! zTLBs0MngItJDcX^lax%TXX5x0VEg-emXwry=@b}f#wCJzYz;D_W3k_fL}$0XwR`b6 zVx_6IqnzGS+8=O{Zqod*CvOESPhpJpbO_-n5t-12NZSY{(wo zsxLOmhtVGkOccu;fI6U^&9xW~&P^6`Ue$yTOQzynJqm`g)!pfiFQR9#Y!CML49u}q3~_PtCP>fQ+(l<>6eLB8 z)2lA0(Z*75u!?tZS>3V}E(}u~Nphy0zd#RYD;wB-OG3(DI`I%WYb16KV$>)iN%G7n zGL!jie;ouVEe;P4oIIX=sYm0UAiyU-D{mTx#V9I<6Cw)x>2aL_jdD>Re{{*mh8?gBB@!HG*?z0i=XO}H_ zUCa&NmQp32^lQZU{4eMHyaoeH%4Z22zP!{&VcF)^Uh9wld+GC!y8Ta{Xm%JEh}Tyn zoPe*d>i!WO8@_f-S+HGtgg|_yp@Y1C|K2-y>nJMunPPKYqDY&}lX+MyI*3ALyKn?l ze!E$xC!0UMZ1rHNXb;2(;>mt^q6-p3Ev4yv0=IhA@t`XKf7*6|q?|U$+)Zh+>&bQO z>nwGUkHOWGe&~J@^`n{emvTNYOA!OTWI{+v#tVnK0wR=dQ6ZvI%yXr(a8N z(a!Ma>8C!v9CnAuP9{QdaJeW$&uumWgwg3>UHmL>8g=%E$DXmTJ|0}1Ies*u|K%lt zO$J!LU?bLq?s} zx}a%%hF9w}$!zjcVBV+rErOna5}s4gl|Q_rGW(i6$oBP;M6Jrr#2U;e6F_8YSxrXE zg?<)mtyb!Z#tl}Dso|*Od{=H+`{C@UfKG^mD+f}Em#pVBfQ3b_etT1wXd^mCanD}m zf!RjJ7z*nKFYkGa_M@O2Q{<6i?Ck*In40_75SJpjIpeJ=<<9idR{Iu`?L<=$1;`^c zngCbg;|WWqs4#&D=Im0HP*T6ViYX?d6bPM9SqjXk#GBH1t(Qg6^GBwACTcsp@?x&# zY+ouND4b9(M)UyR((xed%qa?LqeP#NPGL_-Tj;=JQ8{~SK?<)a|KRB5o={&ujRy&~ zmx%Hk`Fi+?1#O2WKFQhzFjKXycI`#U&A|fi8Cc4+?b58@xl9c+ z9K;`4yf4E2l2cT(PzKGY)_V zIlKfvsYta|_jD?BDGOW2!SMrk5dBE(+o+*95|Ni2% zQMS90gs3E?k#C*vsS8iS&fs4N6d4Ch3n`Dg;$YAa@DBk(YnM`qxdKF|xcXCx?M#>f z6%1~Qj|G5>h`%Z){x=VFP^<%{KErAQP03|Os)@7=untCo3bWIMd`lva{x@1w9a>Ur?b4zEuCui|4Abb?ShaH6HXr=aTU zOgIuwPNgI#-=mP(YEUS2*)(zff%o07`?D8+6;!`hZHGs_Hoj}Bbz$(ZMyEFLeaOtf znXjLy>1hos-zTg6bP_VEP!AdlEv;~TGOl=oP`;LLlal9|bfjBa&i;zGLXFlU&58{z zb*D8uV+dni~B{5 z4bm2NzZ%+?ycAswT&|mwrH1bv;ycYXBb@0R}sbn6Gg0ORT2Qy<8mgJ zVXRy%7x9^2g=0=8fhy`b9elzrz0110+tnP^W*$3F}yDsT3EK*5*U2ZnawFM>}L%*UrgqQ-dl?wPTju{z|$a@uWU$#Ic62t&<^CA90d!XWMeW_n4D8~U z*&MzCTir|+rQjCar>?(B>(Wi^n=FxO_e8?-jfckbl#l!}JDc2z+1E=1wFCURX68*i zd?NA+3a6kN^v4WW*bMNEGS+qRWnW$n!`D32oibzE4ik0kIP}uMC2a?1)6=bgnmfpZ zl??wi(m9BMn9s1c!`}GIwo&gWbfukjce}(b`9!1FRJ?&4?=ds+rYugTlbllCCQL2W zSTG=Pc6#jR3QpCJim!#7Of?FaX^*M4ifhEZpy>6^MQjHv1F?Lj1Wt6tKVf;tv3r+8 zCx7knDQLadF(=h@X&`Ne=W2!ORvyVNRu+7iNsxm zdO2!Zb_T=p-Xi!HiZ?VOO6hOP6AfxGe1|(hNQwvDrAv;s#)dbyX#kGkFPBQ`FhW| zh!vs&9V}D&q=v38sOz&W>EILBlp3_3!kk%!n?Vu1TmOL484CicV+8vKp#5zyw&-B? zTaq=a2a3bD3C>I9hfKPGoatz(&c<63GS&?X;u2q?W0G5c>3d;XQ*W{q{fCe-bx>RP zsc!H(6v3*H828sFB&N4i>Y{CqHBJ?x6UyUEV7#*PqRNkf$HAlS*;+x_JWg`v}^Ry41wMmp~EK9`0D21T{R8etP_oq ztONS|EXS+aksfwCYH_>l6zTV*!?3<9w!5LC`oD5$rB~~9W_?2E=BF7j=UD+ulMD!; zTN{7%hjWW$K$lY5{F0f2tE)@XHLl^Xb`WW8F8s>Q@$H?-Fm&{EF7wktLKZSfOMH)N zIfo&Xom8(ySS52SHL;^b}Y8|y*YpvOW=Er|tmb@FIH@^|nuS32d4Fgh z*s^2oBUS{u%HLg{{)2iVB7JY+(l&X-;q(XMr`9xSy^GBY=anu6Mz*+_+h8R*AvxG=72zqhk&lN#jz-K z{@TROOUtf_9it$OL07(vj`|A8i>p7sl#k~e@DQ|mS=`6Qx27BOC3OGLPt6RWT@GG} zD)3Vr+1O%|42e>b5GEz22V$O#j1XIWbmgh2c2^@_&a~hRrW2i_Bdl_>2)Lq*+@U*U zPE_eceXKo|&#O_Bg$jZzBF-)J+vw(a{WqP-d?3zFHn0NbVKHtzF-eMfWxOXAlIQP- ze-`n4C!0#)A9}f05#tV^wm)c$ijzUcPU+Hi^XLv=GC)<~^kZETz!L==-)9mn#`0nc z^ZHLF&y6JVh=rd&Z}dq5-WKcoOd<9&WURTy_fdIY`cY&3sq9`0h~mliMx#f|Kpa{m zDtR`RteR`a1FhfzZ@xJpdxL<4#Ky+rm-wUa|Ej;u6`zrHg=<>0-t(iEra}hx#Q|mo zf8VF79qAXb#r9*-O|6ptn!P-^Ju9g<+p|+mpO;G~7tHoX6&$ASuk?Ke7jRVf7S(D8 z1o+1XrFEDL9sw0v2k?Dg4Yx^{-4rtj2vTJdNP`hXR>j~xQ=M>&h6!DegnW_RH^9~~ z%t@eKvUh~dJW z>-V2uZ`nyBr1Y-})#-h4RBn=rMM*8D70+t!4xiGRx18JBF*91REugCRJrn>NUX4BA zxlA@naiR6sR3kI}p3%lWInBZpyG6XBPrx+w1XkO)5EmkZkEvcY%l3s1UzwdjzAfXw z(rVi9X2*J_lJ&+QnH5Hg8|v=;a56wu+Hv-iyWb%qvn#ab6y<84OcP2vs$Q|F3EP!2 zm790boYN($Gjv!}lcc%me@IN!#%l98(5S-*Xt_D|RMQFVBw1q2+MW5;IIb03oT#B;DX{I0@MZmEtLA3L`7fM0zHH zmRES?uUd->LZy$&kUFS00Chnx<6H!n%Y8K49U3{}ttQs^sjY!e_4RcOIfYXh?^Wft zH26fGg@l}frZSpfe|=&um zu_zq|Ql`51NQ zKC;%93A6I3>>MRJ$s^0%xLt1Jc5zm=bNUnurFu}b8)z1)0qh4V9IsyddkW?RxCie=lfU zA-`N^yL*WnBg-LiBGsv!+H6WRcPf2bbM_3hot&JO<$+s5LxJ*`XkAOPZ(1#!A~-|N zqFISO`x-+)EJ06m2@=SU-<=~Q(IpDI3lTaYOfk%$G>neWMZh>Ke9?iAQy zIx6lydyIys04PURsaYk}Vbu+_QOSqQy}_yCO@krKU(joNGyz)IYx*-}eKxsDD%j(s zAnOoE{u8yt){rQ&H_;ktU|#(5TqIKfMg|4U26MI~Yo&bP5i}|x>TLmzgRWKqvx^-i zNZ91oQQcFNd4`gQ<%IJatn}IF2I@<-U~`@pK9a&jad_K{pGYXJ3q)*6)}d3lW@v$P z(ushUe26Dw5ot+?{n%%SsYvKh?<%_$E=dlXHh>ngL%{>pP)YWHUr#?Xc`oz$D_ndE z%6Z_CL>N;kI2E`&45uf@naz1zpO`AG4a3gQuett&ZA~Nzdbt>xUhbzB1SY0X>6IW3 zW2mv$9Zo~BJhw)^#Cw@JN_y+PwDQ=ja32sn^YHbz-b>!8>g$?g)Wd>qxLLYS?{(DlKjH3wF=+#a?xb8uJKwPd#)mvMp_9yJ#a?J}5e{%cpy-qq{jf<+%3W?AHebRKU0;@?YUo`Mu{jkrB6j=K|(<@KmE3pK{mMnrBx& z9$Euhx4=2>*!B7h62$&;`!kliCP6=$yIaVr6iPyh2Ti>K%vrB%*cPcBf8XUpqiXXw zuAEqSq>7hu(25Hu-2B(G`s#5}s34%wlk6f@KR)r-rOq5y5NuW-kHmF3-FL}#-7ie1 z%8`qjXDKyp=l+SrTWOPjJvv0TK+tp1B<4B*FV3*c3CFV3T|6R__$Wy!bD&9Uos=_f zPVx&3+Pp)SFAT%rW)^>f|04At;;ao+LjjwjfLN89N1egfA`+VtjD62Xc3Jgt{Wxfo zXYsi>$h#H8Ae^)4wa1>}_8f>AYeb4P&?9oNbkdfcBHvE^_*R%FCu>u~{dg&jG> zsMt#`7Fa;vhP%B+Vb>vSVnZN#DlQiXqUuj_N@(u35zViqmnbqz)m=fwyRlKCfV;wu z59iFf-#79@Tfj>@p&&EKy4aXI0J%r$F=TJt_$cZyCq9G}vU<3mW>MnlmGHnpv;cQn zc@2C}1~*a0=O`@5>RDUivDvdlgfBc~7!X!D6h6H>g|S1uKQ>}Mnuj%WjWI&ejf-ET zbeEQbglO-1XOaj9kcIg#2Jz;eVH=T31}(#c2tb8Imyfgd4-3?5v*cXSGT>Dc=Si;z)#7<%3 zY>BbHf^l8Y?lli<{md0p=j#t@C#wsZ`NOYHuRo*zb^6oqra%#|kN+Ale&{e(p?|V* zbeHFCd5B1CAI*wT7Uu(UE*0Y58slD&B(~`&h3m|!(wFJV55UX=ze`ca%jCxVn7&@DfZRKwA^UcpgFU~H!JLy z{4k`(kjyTB)=@eCgF!<5gzvD!6{_GS8E5GYauzM?dhSeV&oE&Y>gUl1j#FJ~2wRUg z+m-O9caVi#6bWK>!10ODz}z9qmB_wQ^B{|aY{!g;#Z73)(%G4px2QcK=M?@{?Y-C~>i=mjTSL3Ke^6xB3Ip6E zxf>{#ib+Dqtvj#A$i`K2v}XD|5wfj)jFBot$WWvisFsqPL<2h?Xo2ib*(JBip7B;o zqiI|p#GbysxCJWod&`w=vLMqM={sW)Sd)YIR73B{-GHY?mY@d@+L;n)V?I8CHZ{=G z1YLUUy1Uwgz#6;B^@_dh3nTb>igru0{Tyog=en`~d&sgoN1o0$hb_i=jm!Ql`4Di- z-hvGnNNy_{pGS#qCSHn|V)r+Z@Ez{`=>DridMp<-{~0quQE9<9=tczoJW<=RgGm1Kgd^ zgEoPJ!VK>d2v|}`4M_hzvJkPZNs z*YxXNbDixZ=q4?^XIRH@L^tfP%m`o}^)aVBR6(2L3=bSK$NLJj!&1+*(Bj!*@WZXS za{+T8+Evle{Mm%e7F+UdH=Njhp$veg&3}QQ!FV8lFLx-gaK~9g$i4CA@WYf1 z)sh|R0VJ(w8V_#`^_hj&wcuNPVy%)zw#4*_Ulb;QE-zA53x1+;V#25ZVS@=GuKFry za;or+{t(GOZ7JrXlTp0Ce2>z#?FPwL3~W1+pVXZf-p~+@(kw^^uxT_}JIIc87X<#u z$BT*Dx^51Pye)kkRQ{{>ErP#S3Bo|Tleb5e(&7qOf(CD=^b@qDSnJaV)ym8C+J!0O zFJ`>Zr)@-o1wr-LNdfr+baA(qLk#2q{2LAJJ{P6XuajYPZJPxfM10b(-+lPagraPV zeG>W7dC2dPADmd(FA;HtigJn{;sHrR zgzf{+k_n*ERNg*1g^k9tRt5;<&}EtPb;tjPykT7mDL4V;!$+9?J3r0e(6e~^~A~< zI{Nr*BKtkOBk2+18_$Z<65P6DTU>|fTAbnjbuK%6P8MC*Ut6N$=Y(53<-m;wy<{IO0TA_SEo6Fc@hS)VSC*K|mVA^RROyX=qsOL066Se_miv?cMGc#2nQKY8v^3nI1=9@Aj@VJHz3lm^c?X$-F9S| zWud=M&SaQ#Ao;QBl}NhE55h@F2cO+<_ut<{p2HYuzA!@+X=MZSeOgr-XYfPM4ew@FpM&WRhU2+RBy5~Nh?WHLPp_>(%UYF6FsKI52BKQO|od4O6$m%I0g-uWm>v~%|!7`NPgmefJ13k?C_!nW$vDHeY<#PFhDOq(R#0(PKaS0E*?6`XUVYt z;er1L70qV*uTM0lp8$yp1_mqU6Qk^s{pYj_jLif`E&Hg<^AQ* zG|9~JXKmeAG1mytZHl)mOtdX&raP25g=WRWptyEd^SFdteY&07hy7KxZP&%P=_#5I zInSo=1%==oKhO5{pe?K$&a*}Z>4$xzr@Ij{o%g1{vfix)m?AU9F*S5%UY+7~jKQtL zjbGG2G?)DWId9>d|Ez@@O-;BiT`4PaFxq|X7fhdxhjk6ddOgil3rm60JF@fX<)0W; zbb!g#S6(WM_teLM28cxbBeoE z3%IWW2c3v0$QcQ{cZ}f*4^MT>ca9rMIoRsgFriJZFM}4bwz^;R9n#;O%XLonKh}is zxaOW{+_G$f?Wi9{0%hUQ+z%WY3o@Mt*^6+OJ0+W%gLCH+Q1S=P%kX-!V}o{S)>i9* zX7ItB6OA?4-vt$FJlvt!IiBStyv(T2?V%FKnc9D(9md>6g?rg0RFM#WOehI zCY=K^0_p~bI4%KwbjJep9Ocx5M)%ze?*)B2drW#GYy8?`w0ZELL5D6$T7l(m8#QsE zI%vapp>u;!uCrg$X5^1Ea&~+rJsr&qi7Ir{4qyXZ983Wg%)2<}EC)r#+o#6_MQgM@ zSPGd3&sP&ylMe{Uje6;AB7&~xyr91qR!+#UK>w7+(%A!A3%n(yknQd?^>NzbeFG{C z+VQ$ka5m(-IJY|bCg2mVOkv&N+S0THki%;$4}an94%Nj^^_Z3=^k9(Y2z6JwdF5ez zvgrR#lWABfz=08cKjOW149&7$|KszqLB_1OJEVcRB^6iuQ_Fx$Zz6{W+XiS}vvQ?jboc)FS#ihB zeOA&vLq(5bYf~NjPIt7yMl8n2#83eIjzo7FSxOBAdERko79F>6T6)lLl4`oTxn@3~ zsM~N6$2UC;$9MRlnpNr5QeE!kx?%6N#ZCDFs=_~4*{fyIKPj_~_!0)pL7)p5zQWb& zzb8pG-|3Iw*mF7S4K0`=Ier9#?IHF6H>%;9I+eIAtVaRZp$xD~ty(x<&scKMMSbJ_ z`t_xN9uO}AZ1B__3qD7=4w%T|af!)V$WCH#E9c;_MM!t^4XgH44G#Q?$SLA7< zdqK(2mJl^TA~)>KU3>fhC#*JfddHKq{!4WcaN+MqM@{Ip02S!0Y4HOXS8loju0Iy6 zBRlf3zq@@~^5KvM3hpZ?(od1Hu>Rj8SOK0o;jxVs^(>f=fQ_j~Wd-o?`{YWWjl%-d z;yrM+?8j_Zt`L-PA{-aA5iV<}FUk+==^?$hHkgUY(6a+DgY@IaF1KL$6eQ(&>yZXT z5rGn(GmDG8b#-B$hZD!zOhW)IMs!=Gg)aKo1i@do_`aR@aqNlAl#l@JE@OX?5dbs& zih{xmlryo#q@OyMzSXGK=>-apPdYAg%w({~$Hgf_%{~ARlzG70AEbJb0y6&&l6o3c zk*=;{dD$E{xRh>A&iN#ymA7Lu-#jSw&XqoNj(?03-?V=GSQV+R0rq6hGbfbhBNxm5 z3(*DoiRjp|cIXSn1L~ z(F+5p0qFIZp;q<{*b-UEvBg^OeaoL`UvLX6fP_xg)(cq2>!5x%1CQp4!5kraRw4ke z+;vuRWT#F&?GhLEMRc*G3H#?D6Fk=3#Ln(MH?j}JWR81d+2vVpzg$Q=0{{FTI0Dmz ztcr=5x<* zZ>pPQB8}>Xj;N zeLgOUeJ>|rZ^e>(x8ooPikLc#-Ts`ROfc`u%K`&BAhVy#R$Y?vNS1W}E@f7@cA{RK z{%?kzYZ`pAIuj0{58ZX5gQZCQ%*v&MKB7@(#q0O?L@%bTDmMb{d?K{Ym0?vxiH0?-$Ixxu`pZ>1G5B`8v5}}sl+Ev zoCwaA5hfy=R-jjT@Rny5X_|Kp}ZCAV3K$vEET_egBL%(+a*`1dX+iyZTGD>G1L@MzzF z?GIeY?^|rciQNDFZG1jREyO~LTibSiyNs-)8Q6nI5U z-fuCOOu^W~h(ebcj4)*ei>!4<;HiGiN|t?ixX|Jn za7ikJ{i9Qv=0R({v8?dzeVz7p`IE2|vcpP4GAMJj8p~&`%T)PZ`R)L~o5yXI1@hBK zrIVVRTrdggJ>oU^bnusmbo~R^p(0&78*x-@(A!@XIPO>1cONbmGFeKsvpD?l5$+xz zUyKn}uBHlMbtC2|wRGm)AX+*1ky$>_h=G7A?x=r)86G>KA%wVj<3{rO{J>3M42m2d zHgq7Dg%8-OGa6RE1B4BmO)?=F*MDIa-o+dVZoc!EVz`abJ+I%&tYiMBwA5*wQ?8xc z7`~BO`#F{58;$<`BBu)PYNZyyF5hRgEv>b;ZWbC$@x+RRS%3k7>qBYAeA}Q9b^IK= z2g3uqR)j6vndJg*SLH9(9I%?LrV49>e&WW1C7%cE%V7ddsblO;bDR(CdXAh#1&rl( zrIJMEGGqehZE)@E5f1M+kzH8{<>hxAzJI+d*dbcquz0T)Ns-=MyA|kS_stGW0u%r3 zRy(p2l9L$`=0m%g;+76kd^0;?RpaOC8~+%-2QSG0XsD@`HTXf~ zLCR9ztqmgxe%-~n_fhqejIb`-hxN*jdg2PMURhrDMnA}|JMHs0AlFbfc4C_*Y>0mlheju<;hB{o_xk@ zp!Js-;9~x9o?f>n# za_Hn0zbV&yewz3s47)?>&r$a;wg*lwx~H_xY}akqc7|FO>NSKu7uN8Z+fU^y+-IQT z)G*7;R<0HtsnZkL{*#eOUs((dSAB$$*u)zxBm z4)E3+3^#s8|8L6HT(`$mb&V|yg?vZ|)h5lB-j4|ok^CU4L-zGGpTzSS^!n>vG%8hz zr*%{-KOB&OH@RfHQ_|o6pI7MsqcOvO<@w&C@az_e-N5}qEJhABo9q9+DaUVVc2k$z zEk|DQ>1~R1YxiXn)O?1=eFXQ9m@CX+Qn=(KdUW_7GGY^lE&7rTt5%}`Fg-G*Bi{oG za*CuMN>C`xMp<&QsBW#hp!1ua|M9?aP$zO5Qq#&eTD14>sb8I1 zf{bqR!;KrWLG@hWzI7|jYP2c~ZLCXMTg3UsMV0<}Ur)*-D;Kf$$F%n&hdgrWUDspy zuAg?@^vG;Ws>&R!an)GJ*|ck&lmXF-GONO?&)DOwk*sz6<*EO?SxCa?+4YY)quCjb z2MYPrZgUO7DhaiF9zCtsMte(jqM-SMhIPfdm5#IgS4M!hZf|ctxfD&;^Y=@Ol7_=5 z)~jI~sDrs$*oM^}ude+~6&Jsmfad>D3JcW8wSc~!@{WCVgxeY^dQvW<%05EJzo)l1 zgO8sdxwo@X#Py+o)(r2jR~0F)%V=ii2Pef2YwEw5re?Z%r$71BP&|^fT^}B4JlEs( zoB`h3Pi330Xu-kJj+*IAMSX~3LxE`3?BPQe=hEHee>QA1mT1}Ds<{0Ug?yeThgwOt zgj=>fL=>TDkecT_q?js^ed^f>&^hE=9~iPJm`Y7wKPho_7u@;_e=hsK6gsvF_`F9= z{5W$Tk@4Z4na7$2N>xtYfp#q0ig-1vwYBvNT|@9clJreM5@%v1IV6O{VWmYBkzKBg z?CZ@$p(cWhKth*wBE44mO}3Ew!Vpi^(Aa`ncY{w|7EWB9kW1Ny25U_$k09`ZA{Zj6)*Fe>~(8pE0SV8;=4 z2S3p8U2z3U7Li&kLAQqzbTHf-g4eFcpz7*~QR80M)HGT>R5*-1l-4@d9|gLl^9jLZ z+IMXro19>T_D@}rQbkQqHKm$qAp781W<`U_<$A*FH-P6mwF6V#8AwH~)=(l$wHY=;&UBrdr*~Q~4(KVHMs{B;NPJg^#9+e+Kp^%g*?1hld9k zvF_xa8_RrKmu=PUr%hzr65iowtNAc!EIP2VS?9(r)Sten!gReOk;r9ibo#6ZnCdg3nQXfTMo#Z92Y;Qs`@HU{s z*SqEFR^IN97s8Qn6kI$|c<%9of_u9&DI0I@KvI|!fSi`c?+YvM>|9-sT(O9YkJsic zAJeP8v1lL960@#TLI(d6*Zz!p|xHl-@M=;uDWumoSrh53o%;HETk)?rwNWS3borb$e+ws+U znY|5@kdCAtyoDh;D3!vZlVz?7#~QLjJIc&`eat9{HRhg~?4l;qJu`D_tw6r>I|+R1 zS}B8O2d_WzxMgu#gl6TraidVl9A?V{OB;^OPG<8DL*o-=tG5exYbK{JQ@n;=y-h|h z<_Y4$lN1mpc7SVIDFd&Da(q!0EM9f%dYOaZcCG$wr0Z#v zgvhAS=GL%Iv{p;c+Gw*ihxeD+*d=M^XS6a)nAEyRC5$ZFuZ_IthF5>3s&MG>$jP(Z z($uxylh++9k{KDQReqj02$$?AhG#LVUXw7o<**5$ElhN-}-kD<_%dVVtGJ9Qa)H>Z`IX0Dcz}ZdiLE;$M z>b6eM8Umk4>f0aS>>KQ$m8lr9Y88^|T5nKx6H!D?96V#)%bC>A9zQ|TZ6)K8@u)aa zHyv(2JyonNr?w);PR+(MH?Q={1#Iw)%h+kuCj@i;YsIvQ(xrb z={R-YtiKm(%aq|Y%^Q9Vh2O23*xr~J(#T5(TI~=?;b?#Vnq^0Zhn8tT&AuNx%tLnG zW61pWax`>Duzycqzg;-nE5oC6VX8N@=ksX(>ey_H7ck)A_t51kqI*_SS6O%VU=QHQ zd_oc}X05wPZfU_lmT>Xq`cL@ks2eg|Mh_bh0&u;~Ha~?3`A{N!&85rgzajlS<6%?p zkx#d*WvNHg1}>d@Q)f%hEWQv39yD{zA>$We?Osv^AGl?+SadjTV^> z8f6ZOlT_FAdKfR}SxXkwXjYj7DqGV) zb^K+tCI4I-GQ>Z~SbH+!M8PA64idYek{;$K?f(Vu=mArXD6vSk%pNVCxH+eeSRE^y z93XgVMIcPtT_VKaAD;xZR9oqwIVf5)Zxs^%@K6XAQ8H{|2@gphiP)#@HsvBODP3tR z))qHj`OG#H8okG2%&)$k%vbZB>JfrM|3D{U7p1$z664Rblf$JQQMUA*_~6BAk#uB7 z;`|uXg7o|Yhs>2C=7k}62hwXWKeD9y`J|k-xWnD94sCsvEJto0fzk!5M@l=|K#q1w zxwEWGO?pG-hf_ipZN?dz~ zzbvtpPE5DqYGYkK4NL@he;l;zxp_t^sq&e0pMqBt7GpKKAlA+Xfv(^7grvl*Nt;PK z@qP^!mU9izMlA539S{ld_5b(%t~d|R@D)XHFAIRTy7saq4jEyK7sV#`oHYO?fY}OZ z9mcVq;9HdkG6=e+hE>+vSqv&vjt7ymj%Vp-StmALInRebh|0pA4R~f@DP?<6MBq|F z@orr!10J4PnmC`*o$Et*Mzc!k{-*~5T)eL%F1kH;wT!Ki>R)?d#vc^0`)ShY+TPRl zMU9U_7rPfOvrlba+ViJ_OqkCq`no;)(Cc^>yqe+eK7@B%ydsSPp*TMuDa06hN3$VX zlOm9@`xSe}V93`82aD;B^FmVeFXKNL@e{_-`pka|d)3$S_QUn*I|JYXP?KB6^O9!* z=zh=JhHGw({@s1p>NR0csS#gcrS&-L_` zfc=7B6ii!W?}y4j5-bFfV}w<2!8y-GfI(c!sU?YYF9ZTlKOgZPb{{P>yDoZ@s-|D+ z<5scqjOO{4v+IUZHK@@NX35q}g`37LZjc5BX3`{t0-kjh$s7VLAOv{vcK`9RB;UB# z`LMtRZMNQGRR!00i{I7VSXF^do6+EsFGCBR%YK>H4-yh93LYs6(n7O(qhy5(9|H&? zV*1GUQgr~+kaeQV$IFI|vE4l&8lZwg(2Rg$6#i5}FB7ZTztZSW$Ik?0_42q@#iyO( z?~PdoMZ!?@1lnV>=eR&-UjP~QKG?a}a5!KPV$SJ1T;0~vG^i$ft(SpS%I~AuBSho< z!JeMhcKdNxD4=D5H}tvLJg=fn{ui0I7^o2kSb{cX=D05iG>!M3>dAfX@h1`A5)QW5H1x7t(XP6#szJgwz#I~3V?zAEAO{I6r4d5xM|fdQ#PbC zKC3&4d?5NPD*&0qG{uXZeb^(1{Xe(gWvd?~W_ zE}{0EUPMLt`k!^t?Qvs-0~tTonHp7TBf6$R6B^$5;_%V(pm1#wf(NUL~-nP z=tSE+u5vYEDyc1^+x!GZ^;~T?EJWOCzxo7}5hp;vQL6L0#(`t!PmmsQodZd}7ma4t zy%IW|JSItEcHDdC83mv{TOnYFr{dN8FlCV#bM9VVp9^AjvYh!;~q)xOp ziP;g3z0Ce2Z5j0g3=PlzoYkRUZ{?k42e)q+Vhr_@nWev(#(@7v&6D=LdZWUV2H3h# zB2nSAWAMS@r>*xD;oU+(u{D_FKO#u1lSqg>>n;uMIMeQPLqW;0ybb~-&yD@?wDmzM z#qVRgK5#-XI2YY8pbsZ~W6eTGt5z6s4Ommw8`OX#7?7k)$*W?#V-=g)lp!su-81ds z?Y?lCSNrpG+W=tO#PXlMxg0GH38H2-DX_)UN~|dwG0k}iJNygjnN{iELLPv&{xhCi zlXf|^`prqbEB}HTW>ykDLS4J$E2PR{*JL`Z?QXdJU{OQ*=_sAStkvh|Jvv@jR< z7$jZKyS5`}g7t9DXdvyhA7DCeo8u}midNq{J6l;XI^G`w(rl-B+hX+K?$R(`+AX|!%>`Cfc(+O@6asMuD(PhLmHV^2#ikp6G~FR+W}xeporFv%fCs{HaM%*MQvUfWmJ!ap?*STg+q#I z(Vo(Aq(MhfQAK2w4<@4Eh5mhavH*ZaYfe%=j`b5(!x>81YmLjfbsLDm Date: Tue, 27 Sep 2016 12:22:44 -0400 Subject: [PATCH 4/5] update readme with formating requests in instructions --- README.md | 133 ++++++++++++++++++++++++------------------------------ 1 file changed, 58 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 359dd20..5e6610e 100644 --- a/README.md +++ b/README.md @@ -50,81 +50,63 @@ memory access time. ## Section 2: Verification and Performance Analysis *Code Correctness for Scan, Compact, and Sort Implementations:* -> **************** -> ** SCAN TESTS ** -> **************** -> [ 38 19 38 37 5 47 15 35 0 12 3 0 42 ... 35 0 ] - -> ==== cpu scan, power-of-two ==== - -> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] -> ==== cpu scan, non-power-of-two ==== - -> passed -> ==== naive scan, power-of-two ==== - -> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] -> passed -> ==== naive scan, non-power-of-two ==== - -> passed -> ==== work-efficient scan, power-of-two ==== - -> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] -> passed -> ==== work-efficient scan, non-power-of-two ==== - -> passed -> ==== thrust scan, power-of-two ==== - -> [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] -> passed -> ==== thrust scan, non-power-of-two ==== - -> passed -> -> ***************************** -> ** STREAM COMPACTION TESTS ** -> ***************************** - -> [ 2 3 2 1 3 1 1 1 2 0 1 0 2 ... 1 0 ] -> ==== cpu compact without scan, power-of-two ==== - -> [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] -> passed -> ==== cpu compact without scan, non-power-of-two ==== - -> [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 3 3 ] -> passed -> ==== cpu compact with scan ==== - -> [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] -> passed -> ==== work-efficient compact, power-of-two ==== - -> passed -> ==== work-efficient compact, non-power-of-two ==== - -> passed -> -> ********************** -> ** RADIX SORT TESTS ** -> ********************** - -> ==== radix sort, power-of-two ==== - -> [ 38 7719 21238 2437 8855 11797 8365 32285 ] -> [ 38 2437 7719 8365 8855 11797 21238 32285 ] -> passed -> ==== radix sort, non-power-of-two ==== - -> [ 38 7719 21238 2437 8855 11797 8365 ] -> [ 38 2437 7719 8365 8855 11797 21238 ] -> passed - -> -> Sort passed 1000/1000 randomly generated verification tests. - +``` +**************** +** SCAN TESTS ** +**************** + [ 38 19 38 37 5 47 15 35 0 12 3 0 42 ... 35 0 ] +==== cpu scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] +==== cpu scan, non-power-of-two ==== + passed +==== naive scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] + passed +==== naive scan, non-power-of-two ==== + passed +==== work-efficient scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] + passed +==== work-efficient scan, non-power-of-two ==== + passed +==== thrust scan, power-of-two ==== + [ 0 38 57 95 132 137 184 199 234 234 246 249 249 ... 1604374 1604409 ] + passed +==== thrust scan, non-power-of-two ==== + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 2 3 2 1 3 1 1 1 2 0 1 0 2 ... 1 0 ] +==== cpu compact without scan, power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] + passed +==== cpu compact without scan, non-power-of-two ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 3 3 ] + passed +==== cpu compact with scan ==== + [ 2 3 2 1 3 1 1 1 2 1 2 1 1 ... 1 1 ] + passed +==== work-efficient compact, power-of-two ==== + passed +==== work-efficient compact, non-power-of-two ==== + passed + +********************** +** RADIX SORT TESTS ** +********************** +==== radix sort, power-of-two ==== + [ 38 7719 21238 2437 8855 11797 8365 32285 ] + [ 38 2437 7719 8365 8855 11797 21238 32285 ] + passed +==== radix sort, non-power-of-two ==== + [ 38 7719 21238 2437 8855 11797 8365 ] + [ 38 2437 7719 8365 8855 11797 21238 ] + passed + +Sort passed 1000/1000 randomly generated verification tests. +``` ### Benchmarks *All performance measurements were averaged over 1000 samples on a single dataset* @@ -171,6 +153,7 @@ bitshift operations cleared the error entirely. ## Appendix: Build Instructions +**CMakeLists.txt modified to include new sort class and update compute compatability** * `src/` contains the source code. From 7ac543a410df3f306f0fb93361ac98dd9b9d76f8 Mon Sep 17 00:00:00 2001 From: Michael Willett Date: Thu, 29 Sep 2016 08:04:55 -0400 Subject: [PATCH 5/5] xlsx update --- benchmark data.xlsx | Bin 31816 -> 31744 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/benchmark data.xlsx b/benchmark data.xlsx index 8a939a59783618659687f94c709505ba50421407..aa3b627213115ee502c791e47be0c5fe77948bc5 100644 GIT binary patch delta 12978 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 H{~7-mr(e4~ delta 13086 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