From a4fef4f77b6984e4ca9b96b274eed3b5b4bf47d0 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Sat, 15 Sep 2018 20:40:21 -0400 Subject: [PATCH 01/17] Fix build errors --- CMakeLists.txt | 1 + stream_compaction/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f654c9e..2930067 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1) project(cis565_stream_compaction_test) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") # Enable C++11 for host code set(CMAKE_CXX_STANDARD 11) diff --git a/stream_compaction/CMakeLists.txt b/stream_compaction/CMakeLists.txt index cdbef77..e31ca3c 100644 --- a/stream_compaction/CMakeLists.txt +++ b/stream_compaction/CMakeLists.txt @@ -13,5 +13,5 @@ set(SOURCE_FILES cuda_add_library(stream_compaction ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_30 ) From a651d1a17e0e4ab91ededf7dde1e1bf6cdea8376 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Sat, 15 Sep 2018 22:15:35 -0400 Subject: [PATCH 02/17] Finish cpu scan and compact implementation --- stream_compaction/cpu.cu | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 05ce667..b67cd2b 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -19,7 +19,11 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + int sum = 0; + for (int i = 0; i < n; i++){ + sum += idata[i]; + odata[i] = sum; + } timer().endCpuTimer(); } @@ -30,9 +34,16 @@ namespace StreamCompaction { */ 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; } /** @@ -41,10 +52,26 @@ namespace StreamCompaction { * @returns the number of elements remaining after compaction. */ int compactWithScan(int n, int *odata, const int *idata) { - timer().startCpuTimer(); - // TODO + int idx = 0; + int scatterArray[n]; + for (int i = 0; i < n; i++) { + if (idata[i] != 0) { + scatterArray[i] = 1; + } else { + scatterArray[i] = 0; + } + } + int indexArray[n]; + scan(n, indexArray, scatterArray); + timer().startCpuTimer(); + for (int i = 0; i < n; i++) { + if (idata[i] != 0) { + idx = indexArray[i] - 1; + odata[idx] = idata[i]; + } + } timer().endCpuTimer(); - return -1; + return idx + 1; } } } From 9d147e18f1f413483c1fac11d1cdf2ac8edfcd12 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Sun, 16 Sep 2018 05:20:40 -0400 Subject: [PATCH 03/17] Finish naive implementation --- stream_compaction/naive.cu | 44 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 9218f8e..455c20f 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,23 +3,63 @@ #include "common.h" #include "naive.h" +#define BLOCK_SIZE 256 + namespace StreamCompaction { namespace Naive { + int* naive_idata; + int* naive_odata; + using StreamCompaction::Common::PerformanceTimer; PerformanceTimer& timer() { static PerformanceTimer timer; return timer; } - // TODO: __global__ + // __global__ + __global__ void kernNaiveScan(int n, int pow, int * odata, int * idata) { + int idx = threadIdx.x + (blockIdx.x * blockDim.x); + if (idx > n) { + return; + } + + if (idx >= pow) { + odata[idx] = idata[idx - pow] + idata[idx]; + } else { + odata[idx] = idata[idx]; + } + + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + cudaMalloc((void**) &naive_idata, n * sizeof(int)); + cudaMalloc((void**) &naive_odata, n * sizeof(int)); + + //Transfer from host memory to device + cudaMemcpy(naive_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + int blocksPerGrid = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; + timer().startGpuTimer(); - // TODO + int logValue = ilog2ceil(n); + int pow; + + for (int d = 0; d < logValue; d++){ + pow = std::pow(2, d); + kernNaiveScan<<< blocksPerGrid, BLOCK_SIZE >>>(n, pow, naive_odata, naive_idata); + + std::swap(naive_odata, naive_idata); + } timer().endGpuTimer(); + + // Copy back to host, idata because of swap + cudaMemcpy(odata, naive_idata, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(naive_idata); + cudaFree(naive_odata); } } } From 9e9d5b8b5b68728a7b1d4164ca39a348cc3b2496 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Sun, 16 Sep 2018 05:21:19 -0400 Subject: [PATCH 04/17] WIP: Efficient scan --- stream_compaction/efficient.cu | 62 +++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 36c5ef2..5917bf6 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,8 +3,14 @@ #include "common.h" #include "efficient.h" +#define BLOCK_SIZE 256 + namespace StreamCompaction { namespace Efficient { + + int * dev_idata; + int * dev_odata; + using StreamCompaction::Common::PerformanceTimer; PerformanceTimer& timer() { @@ -12,13 +18,67 @@ namespace StreamCompaction { return timer; } + __global__ void kernUpSweep(int n, int pow, int pow1, int data[]) { + int idx = threadIdx.x + (blockIdx.x * blockDim.x); + if (idx > n) { + return; + } + if (idx * pow1 > n){ + return; + } + + data[(idx + 1) * pow1 - 1] += data[(idx + 1) * pow1 - 1 - pow]; + } + + __global__ void kernDownSweep(int n, int pow, int pow1, int data[]) { + int idx = threadIdx.x + (blockIdx.x * blockDim.x); + if (idx > n) { return; } + + int aux = data[pow1]; + data[pow1] += data[pow1 - pow]; + data[pow1 - pow] = aux; + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + int logValue = ilog2ceil(n); + int pown = std::pow(2, logValue); + + cudaMalloc((void**) &dev_idata, pown * sizeof(int)); + cudaMemset(dev_idata, 0, n * sizeof(int)); + cudaMalloc((void**) &dev_odata, pown * sizeof(int)); + + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + //cudaMemcpy(dev_odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToDevice); + + int pow, pow1, blocksPerGrid; + timer().startGpuTimer(); - // TODO + // Need two separate kernels, one for upsweep and one for down to ensure everything stays in sync + // Can we just use sync_threads? No, becaue potentially multiple blocks + // 1. upsweep (note it updates in place, hopefully this is okay? Just summing) + // 2. Reset end of array to 0 + // 3. Downsweep + for (int d = 0; d < logValue; d++) { + pow = std::pow(2, d); + pow1 = std::pow(2, d + 1); + blocksPerGrid = (pown / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; + kernUpSweep<<< blocksPerGrid, BLOCK_SIZE >>>(pown, pow, pow1, dev_idata); + } + for (int d = 0; d < logValue; d++) { + pow = std::pow(2, d); + pow1 = std::pow(2, d + 1); + blocksPerGrid = (pown / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; + kernDownSweep<<< blocksPerGrid, BLOCK_SIZE >>>(pown, pow, pow1, dev_idata); + } timer().endGpuTimer(); + + cudaMemcpy(odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToHost); + + cudaFree(dev_idata); + cudaFree(dev_odata); } /** From 31d150aa67904cb115579f1658600ef7dd1b5739 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Mon, 17 Sep 2018 01:49:54 -0400 Subject: [PATCH 05/17] Troubleshoot efficient scan --- stream_compaction/common.h | 1 + stream_compaction/efficient.cu | 56 ++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/stream_compaction/common.h b/stream_compaction/common.h index 99a1b04..7533ec6 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -12,6 +12,7 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) +#define BLOCK_SIZE 256 /** * Check for CUDA errors; print and exit if there was a problem. diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 5917bf6..331f775 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,8 +3,6 @@ #include "common.h" #include "efficient.h" -#define BLOCK_SIZE 256 - namespace StreamCompaction { namespace Efficient { @@ -20,35 +18,39 @@ namespace StreamCompaction { __global__ void kernUpSweep(int n, int pow, int pow1, int data[]) { int idx = threadIdx.x + (blockIdx.x * blockDim.x); - if (idx > n) { - return; - } - if (idx * pow1 > n){ + if (idx >= n) { return; } - data[(idx + 1) * pow1 - 1] += data[(idx + 1) * pow1 - 1 - pow]; + int i = idx * pow1; + if (i < n) { + data[i + pow1 - 1] += data[i + pow - 1]; + } } __global__ void kernDownSweep(int n, int pow, int pow1, int data[]) { int idx = threadIdx.x + (blockIdx.x * blockDim.x); - if (idx > n) { return; } - - int aux = data[pow1]; - data[pow1] += data[pow1 - pow]; - data[pow1 - pow] = aux; + if (idx >= n) { return; } + + int i = idx * pow1; + if (i < n) { + // Swap and sum + int aux = data[i + pow - 1]; + data[i + pow - 1] = data[i + pow1 - 1]; + data[i + pow1 - 1] += aux; + } } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { - int logValue = ilog2ceil(n); - int pown = std::pow(2, logValue); + int ceil = ilog2ceil(n); + int ceilN = 1 << ceil; - cudaMalloc((void**) &dev_idata, pown * sizeof(int)); + cudaMalloc((void**) &dev_idata, ceilN * sizeof(int)); cudaMemset(dev_idata, 0, n * sizeof(int)); - cudaMalloc((void**) &dev_odata, pown * sizeof(int)); + cudaMalloc((void**) &dev_odata, ceilN * sizeof(int)); cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); //cudaMemcpy(dev_odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToDevice); @@ -61,17 +63,19 @@ namespace StreamCompaction { // 1. upsweep (note it updates in place, hopefully this is okay? Just summing) // 2. Reset end of array to 0 // 3. Downsweep - for (int d = 0; d < logValue; d++) { - pow = std::pow(2, d); - pow1 = std::pow(2, d + 1); - blocksPerGrid = (pown / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; - kernUpSweep<<< blocksPerGrid, BLOCK_SIZE >>>(pown, pow, pow1, dev_idata); + for (int d = 0; d < ceil; d++) { + //pow = std::pow(2, d); + //pow1 = std::pow(2, d + 1); + pow = 1 << d; + pow1 = 1 << (d + 1); + blocksPerGrid = (ceilN / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; + kernUpSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_idata); } - for (int d = 0; d < logValue; d++) { - pow = std::pow(2, d); - pow1 = std::pow(2, d + 1); - blocksPerGrid = (pown / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; - kernDownSweep<<< blocksPerGrid, BLOCK_SIZE >>>(pown, pow, pow1, dev_idata); + for (int d = 0; d < ceil; d++) { + pow = 1 << d; + pow1 = 1 << (d + 1); + blocksPerGrid = (ceilN / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; + kernDownSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_idata); } timer().endGpuTimer(); From a598f1272ea91506adc35c04675adccae96f7d6a Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Mon, 17 Sep 2018 22:12:16 -0400 Subject: [PATCH 06/17] Fix naive implementation (convert from inclusive to exclusive) --- stream_compaction/common.cu | 12 ++++++++++++ stream_compaction/common.h | 4 ++++ stream_compaction/naive.cu | 21 +++++++++++++++------ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 8fc0211..42f2620 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -35,5 +35,17 @@ namespace StreamCompaction { // TODO } + __global__ void kernConvertScanToExclusive(int n, int exclusiveScan[], const int inclusiveScan[]) { + int idx = threadIdx.x + (blockIdx.x * blockDim.x); + if (idx >= n) { + return; + } else if (idx >= 1) { + exclusiveScan[idx] = inclusiveScan[idx - 1]; + return; + } + + exclusiveScan[0] = 0; + } + } } diff --git a/stream_compaction/common.h b/stream_compaction/common.h index 7533ec6..bb14497 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -38,6 +38,8 @@ namespace StreamCompaction { __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices); + __global__ void kernConvertScanToExclusive(int n, int exclusiveScan[], const int inclusiveScan[]); + /** * This class is used for timing the performance * Uncopyable and unmovable @@ -115,6 +117,8 @@ namespace StreamCompaction { PerformanceTimer& operator=(const PerformanceTimer&) = delete; PerformanceTimer& operator=(PerformanceTimer&&) = delete; + + private: cudaEvent_t event_start = nullptr; cudaEvent_t event_end = nullptr; diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 455c20f..de5cbbf 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -19,7 +19,7 @@ namespace StreamCompaction { // __global__ __global__ void kernNaiveScan(int n, int pow, int * odata, int * idata) { int idx = threadIdx.x + (blockIdx.x * blockDim.x); - if (idx > n) { + if (idx >= n) { return; } @@ -36,30 +36,39 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { cudaMalloc((void**) &naive_idata, n * sizeof(int)); + checkCUDAError("Failed to allocate idata"); cudaMalloc((void**) &naive_odata, n * sizeof(int)); + checkCUDAError("Failed to allocate odata"); //Transfer from host memory to device cudaMemcpy(naive_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("cudaMemcpy failed (initial)"); int blocksPerGrid = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; timer().startGpuTimer(); int logValue = ilog2ceil(n); int pow; - - for (int d = 0; d < logValue; d++){ - pow = std::pow(2, d); + for (int d = 1; d <= logValue; d++){ + pow = 1 << d - 1; kernNaiveScan<<< blocksPerGrid, BLOCK_SIZE >>>(n, pow, naive_odata, naive_idata); + checkCUDAError("kernNaiveScan failed"); std::swap(naive_odata, naive_idata); } + //std::swap(naive_odata, naive_idata); + + Common::kernConvertScanToExclusive<<< blocksPerGrid, BLOCK_SIZE >>>(n, naive_odata, naive_idata); + checkCUDAError("kernConvertScanToExclusive failed"); timer().endGpuTimer(); - // Copy back to host, idata because of swap - cudaMemcpy(odata, naive_idata, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(odata, naive_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("cudaMemcpy failed (final)"); cudaFree(naive_idata); + checkCUDAError("cudaFree idata failed"); cudaFree(naive_odata); + checkCUDAError("cudaFree odata failed"); } } } From 3e69d64cf569137031edbca2e065c3dae28e3f11 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Mon, 17 Sep 2018 22:51:03 -0400 Subject: [PATCH 07/17] Efficient solution passes --- stream_compaction/efficient.cu | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 331f775..214a17b 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -6,8 +6,7 @@ namespace StreamCompaction { namespace Efficient { - int * dev_idata; - int * dev_odata; + int * dev_data; using StreamCompaction::Common::PerformanceTimer; PerformanceTimer& timer() @@ -48,11 +47,10 @@ namespace StreamCompaction { int ceil = ilog2ceil(n); int ceilN = 1 << ceil; - cudaMalloc((void**) &dev_idata, ceilN * sizeof(int)); - cudaMemset(dev_idata, 0, n * sizeof(int)); - cudaMalloc((void**) &dev_odata, ceilN * sizeof(int)); + cudaMalloc((void**) &dev_data, ceilN * sizeof(int)); + cudaMemset(dev_data, 0, n * sizeof(int)); - cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + cudaMemcpy(dev_data, idata, n * sizeof(int), cudaMemcpyHostToDevice); //cudaMemcpy(dev_odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToDevice); int pow, pow1, blocksPerGrid; @@ -69,20 +67,26 @@ namespace StreamCompaction { pow = 1 << d; pow1 = 1 << (d + 1); blocksPerGrid = (ceilN / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; - kernUpSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_idata); + kernUpSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_data); } - for (int d = 0; d < ceil; d++) { + + // Reset last value + int z = 0; + cudaMemcpy(dev_data + ceilN - 1, &z, sizeof(int), cudaMemcpyHostToDevice); + //dev_data[ceilN - 1] = 0; + + //for (int d = 0; d < ceil; d++) { start at end instead + for (int d = ceil - 1; d >= 0; d--){ pow = 1 << d; pow1 = 1 << (d + 1); blocksPerGrid = (ceilN / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; - kernDownSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_idata); + kernDownSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_data); } timer().endGpuTimer(); - cudaMemcpy(odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(odata, dev_data, n * sizeof(int), cudaMemcpyDeviceToHost); - cudaFree(dev_idata); - cudaFree(dev_odata); + cudaFree(dev_data); } /** From 2605c9116655b0bb5bb1c533a30379d6a212971f Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Mon, 17 Sep 2018 22:56:40 -0400 Subject: [PATCH 08/17] Add thrust implementation --- stream_compaction/thrust.cu | 1 + 1 file changed, 1 insertion(+) diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 36b732d..94bedb8 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -22,6 +22,7 @@ namespace StreamCompaction { // TODO use `thrust::exclusive_scan` // example: for device_vectors dv_in and dv_out: // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + thrust::exclusive_scan(idata, idata + n, odata); timer().endGpuTimer(); } } From 65080717cc887ea641556c2d410df2e115093b09 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Mon, 17 Sep 2018 23:06:42 -0400 Subject: [PATCH 09/17] Convert to exclusive scan on CPU --- stream_compaction/cpu.cu | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index b67cd2b..be73331 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -19,10 +19,11 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); + odata[0] = 0; //Add identity int sum = 0; - for (int i = 0; i < n; i++){ + for (int i = 0; i < n - 1; i++){ sum += idata[i]; - odata[i] = sum; + odata[i + 1] = sum; } timer().endCpuTimer(); } @@ -66,7 +67,7 @@ namespace StreamCompaction { timer().startCpuTimer(); for (int i = 0; i < n; i++) { if (idata[i] != 0) { - idx = indexArray[i] - 1; + idx = indexArray[i]; odata[idx] = idata[i]; } } From c074a563e11ec3edd3943d8ecfa6f38473669805 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Tue, 18 Sep 2018 01:05:23 -0400 Subject: [PATCH 10/17] WIP: Compact --- stream_compaction/common.cu | 17 +++++++++++++++-- stream_compaction/efficient.cu | 27 +++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 42f2620..d54e81a 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -23,7 +23,14 @@ 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 i = threadIdx.x + blockIdx.x * blockDim.x; + if (i >= n) { return; } + + if (idata[i] != 0) { + bools[i] = 1; + } else { + bools[i] = 0; + } } /** @@ -32,7 +39,13 @@ namespace StreamCompaction { */ __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { - // TODO + int i = threadIdx.x + blockIdx.x * blockDim.x; + + int outIndex = -1; + if (bools[i] == 1) { + outIndex = indices[i]; + odata[outIndex] = idata[i]; + } } __global__ void kernConvertScanToExclusive(int n, int exclusiveScan[], const int inclusiveScan[]) { diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 214a17b..798c753 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -99,10 +99,33 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { + int * dev_bools; + int * dev_indices; + int * dev_scatter; + int * dev_input; + cudaMalloc((void**) &dev_bools, n * sizeof(int)); + cudaMalloc((void**) &dev_indices, n * sizeof(int)); + cudaMalloc((void**) &dev_scatter, n * sizeof(int)); + cudaMalloc((void**) &dev_input, n * sizeof(int)); + cudaMemcpy(dev_input, idata, n * sizeof(int), cudaMemcpyHostToDevice); + + int host_indices[n]; + int host_bools[n]; + + int blocksPerGrid = (n + BLOCK_SIZE - 1) / BLOCK_SIZE; + + // 1. Create boolean array + Common::kernMapToBoolean<<< blocksPerGrid, BLOCK_SIZE >>>(n, dev_bools, dev_input); + cudaMemcpy(host_bools, dev_bools, n * sizeof(int), cudaMemcpyDeviceToHost); + // 2. Scan to generate indices + scan(n, host_indices, host_bools); + cudaMemcpy(dev_indices, host_indices, n * sizeof(int), cudaMemcpyHostToDevice); + // 3. Scatter timer().startGpuTimer(); - // TODO + Common::kernScatter<<< blocksPerGrid, BLOCK_SIZE >>>(n, dev_scatter, dev_input, dev_bools, dev_indices); timer().endGpuTimer(); - return -1; + + return host_indices[n-1]; } } } From 29894af4df539bd54d872668aaddbb8ee554559f Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Tue, 18 Sep 2018 06:19:01 -0400 Subject: [PATCH 11/17] Complete compaction --- stream_compaction/efficient.cu | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 798c753..40c72c1 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -125,7 +125,22 @@ namespace StreamCompaction { Common::kernScatter<<< blocksPerGrid, BLOCK_SIZE >>>(n, dev_scatter, dev_input, dev_bools, dev_indices); timer().endGpuTimer(); - return host_indices[n-1]; + // Copy to output + cudaMemcpy(odata, dev_scatter, n * sizeof(int), cudaMemcpyDeviceToHost); + + // Memory cleanup + cudaFree(dev_bools); + cudaFree(dev_indices); + cudaFree(dev_scatter); + cudaFree(dev_input); + + // Beware! Since exclusive scan, we won't count last element in + // indices, let's fix that + if (host_bools[n - 1] != 0) { + return host_indices[n - 1] + 1; + } else { + return host_indices[n-1]; + } } } } From 8d94030dd147c78153c400415713742fe77be8f8 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Tue, 18 Sep 2018 08:48:44 -0400 Subject: [PATCH 12/17] Add CPU scan implementation without timing --- stream_compaction/cpu.cu | 27 ++++++++++++++++----------- stream_compaction/cpu.h | 2 ++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index be73331..8e18fca 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -19,15 +19,19 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - odata[0] = 0; //Add identity - int sum = 0; - for (int i = 0; i < n - 1; i++){ - sum += idata[i]; - odata[i + 1] = sum; - } + scanNoTimer(n, odata, idata); timer().endCpuTimer(); } + void scanNoTimer(int n, int *odata, const int *idata) { + odata[0] = 0; //Add identity + int sum = 0; + for (int i = 0; i < n - 1; i++){ + sum += idata[i]; + odata[i + 1] = sum; + } + } + /** * CPU stream compaction without using the scan function. * @@ -54,17 +58,18 @@ namespace StreamCompaction { */ int compactWithScan(int n, int *odata, const int *idata) { int idx = 0; - int scatterArray[n]; + int booleanArray[n]; + timer().startCpuTimer(); for (int i = 0; i < n; i++) { if (idata[i] != 0) { - scatterArray[i] = 1; + booleanArray[i] = 1; } else { - scatterArray[i] = 0; + booleanArray[i] = 0; } } int indexArray[n]; - scan(n, indexArray, scatterArray); - timer().startCpuTimer(); + scanNoTimer(n, indexArray, booleanArray); + // Finally, scatter for (int i = 0; i < n; i++) { if (idata[i] != 0) { idx = indexArray[i]; diff --git a/stream_compaction/cpu.h b/stream_compaction/cpu.h index 236ce11..47dafdf 100644 --- a/stream_compaction/cpu.h +++ b/stream_compaction/cpu.h @@ -8,6 +8,8 @@ namespace StreamCompaction { void scan(int n, int *odata, const int *idata); + void scanNoTimer(int n, int *odata, const int *idata); + int compactWithoutScan(int n, int *odata, const int *idata); int compactWithScan(int n, int *odata, const int *idata); From 27072a2333da21057efd03ff17cf371a14bde368 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Tue, 18 Sep 2018 08:49:54 -0400 Subject: [PATCH 13/17] Add error checking to efficient and use memset instead of copying single value --- stream_compaction/common.h | 2 +- stream_compaction/efficient.cu | 12 ++++++++++-- stream_compaction/naive.cu | 2 -- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/stream_compaction/common.h b/stream_compaction/common.h index bb14497..9c74268 100644 --- a/stream_compaction/common.h +++ b/stream_compaction/common.h @@ -12,7 +12,7 @@ #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) -#define BLOCK_SIZE 256 +#define BLOCK_SIZE 1024 /** * Check for CUDA errors; print and exit if there was a problem. diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 40c72c1..2fdbca7 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -48,9 +48,12 @@ namespace StreamCompaction { int ceilN = 1 << ceil; cudaMalloc((void**) &dev_data, ceilN * sizeof(int)); + checkCUDAError("malloc dev_data failed"); cudaMemset(dev_data, 0, n * sizeof(int)); + checkCUDAError("cudaMemset to clear array failed"); cudaMemcpy(dev_data, idata, n * sizeof(int), cudaMemcpyHostToDevice); + checkCUDAError("cudaMemcpy input host to device failed"); //cudaMemcpy(dev_odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToDevice); int pow, pow1, blocksPerGrid; @@ -68,11 +71,14 @@ namespace StreamCompaction { pow1 = 1 << (d + 1); blocksPerGrid = (ceilN / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; kernUpSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_data); + checkCUDAError("kernUpSweep failed"); } // Reset last value - int z = 0; - cudaMemcpy(dev_data + ceilN - 1, &z, sizeof(int), cudaMemcpyHostToDevice); + //int z = 0; + //cudaMemcpy(dev_data + ceilN - 1, &z, sizeof(int), cudaMemcpyHostToDevice); + cudaMemset(dev_data + ceilN - 1, 0, sizeof(int)); + checkCUDAError("cudaMemcpy zero failed"); //dev_data[ceilN - 1] = 0; //for (int d = 0; d < ceil; d++) { start at end instead @@ -81,10 +87,12 @@ namespace StreamCompaction { pow1 = 1 << (d + 1); blocksPerGrid = (ceilN / pow1 + BLOCK_SIZE - 1) / BLOCK_SIZE; kernDownSweep<<< blocksPerGrid, BLOCK_SIZE >>>(ceilN, pow, pow1, dev_data); + checkCUDAError("kernDownSweep failed"); } timer().endGpuTimer(); cudaMemcpy(odata, dev_data, n * sizeof(int), cudaMemcpyDeviceToHost); + checkCUDAError("memcpy answer to host failed"); cudaFree(dev_data); } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index de5cbbf..dc2bbe5 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,8 +3,6 @@ #include "common.h" #include "naive.h" -#define BLOCK_SIZE 256 - namespace StreamCompaction { namespace Naive { int* naive_idata; From 745378f777aa84b631dacd3073405f4ad10d643c Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Tue, 18 Sep 2018 08:50:13 -0400 Subject: [PATCH 14/17] Add README --- README.md | 90 +++++++++++++++++++++++++++-- img/time-vs-array-bs-1024-zoom.png | Bin 0 -> 24958 bytes img/time-vs-array-bs-1024.png | Bin 0 -> 18741 bytes img/time-vs-array-bs-256-zoom.png | Bin 0 -> 25941 bytes img/time-vs-array-bs-256.png | Bin 0 -> 20285 bytes 5 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 img/time-vs-array-bs-1024-zoom.png create mode 100644 img/time-vs-array-bs-1024.png create mode 100644 img/time-vs-array-bs-256-zoom.png create mode 100644 img/time-vs-array-bs-256.png diff --git a/README.md b/README.md index 0e38ddb..7740243 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,90 @@ 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) +* Edward Atter + * [LinkedIn](https://www.linkedin.com/in/atter/) + * Tested on: Linux Mint 18.3 Sylvia (4.13.0-41-generic), Ryzen 7 2700x @ 3.7 ghz (base clock) 16GB, GTX 1070 TI 8GB GDDR5 (Personal) + * CUDA 9 -### (TODO: Your README) +## Overview -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 array scanning and stream compaction. Below are some diagrams from GPU Gems. +![Scanning implementation](img/figure-39-2.jpg) + +Compaction is useful to reduce the size if only the true values matter. Think of sparse arrays or only considering object collisions. + +## Performance + +#### Methodology + +Memory operations such as `malloc` or `memset` were excluded from the results. In the case of compaction, only the final step (scatter) is timed. Any preliminary data formatting (for example to get the boolean array or scanning to get the proper indices) is not included in the time. Unless otherwise stated, a block size of 1024 was used throughout the analysis. + +#### Analysis + +Large block sizes perform the best. This is likely because each thread does very little work. Specifically, a block size of 1024 was chosen. See the graphs below for a comparison. + +![Time vs Array Size (BS = 256)](img/time-vs-array-bs-256.png) ![Time vs Array Size (BS = 256)](img/time-vs-array-bs-256-zoom.png) + +![Time vs Array Size (BS = 1024)](img/time-vs-array-bs-1024.png) ![Time vs Array Size (BS = 1024)](img/time-vs-array-bs-1024-zoom.png) + +With the exception of thrust, both GPU implementations improve relative to the CPU as the array size increases. The naive solution is never worth using, as it is always the slowest. When the array size grows extremely large, the efficient implementation beats even the standard thrust library. Any performance bottlenecks are IO related. It can be improved significantly by using shared instead of global memory within blocks. + +It should be noted that thrust is really at an unfair advantage in these tests. Memory allocations are not included in the performance tests for any of the custom built solutions. The timing for thrust, however, includes all the necessary memory allocations. + +On examination, in addition to watching `top` and `nvidia-smi`, I believe thrust uses the CPU for small array sizes, and switches to utilizing the GPU when the array size reaches a sufficient length. + +## Program Output + + **************** + ** SCAN TESTS ** + **************** + [ 40 41 8 46 3 10 25 38 24 42 12 31 35 ... 16 0 ] + ==== cpu scan, power-of-two ==== + elapsed time: 0.14779ms (std::chrono Measured) + [ 0 40 81 89 135 138 148 173 211 235 277 289 320 ... 6428057 6428073 ] + ==== cpu scan, non-power-of-two ==== + elapsed time: 0.14218ms (std::chrono Measured) + [ 0 40 81 89 135 138 148 173 211 235 277 289 320 ... 6427957 6427981 ] + passed + ==== naive scan, power-of-two ==== + elapsed time: 0.163936ms (CUDA Measured) + passed + ==== naive scan, non-power-of-two ==== + elapsed time: 0.109696ms (CUDA Measured) + passed + ==== work-efficient scan, power-of-two ==== + elapsed time: 0.131392ms (CUDA Measured) + passed + ==== work-efficient scan, non-power-of-two ==== + elapsed time: 0.119008ms (CUDA Measured) + passed + ==== thrust scan, power-of-two ==== + elapsed time: 0.221088ms (CUDA Measured) + passed + ==== thrust scan, non-power-of-two ==== + elapsed time: 0.22096ms (CUDA Measured) + passed + + ***************************** + ** STREAM COMPACTION TESTS ** + ***************************** + [ 2 1 0 2 1 2 1 2 2 2 0 1 1 ... 0 0 ] + ==== cpu compact without scan, power-of-two ==== + elapsed time: 1.05048ms (std::chrono Measured) + [ 2 1 2 1 2 1 2 2 2 1 1 3 3 ... 3 3 ] + passed + ==== cpu compact without scan, non-power-of-two ==== + elapsed time: 1.05846ms (std::chrono Measured) + [ 2 1 2 1 2 1 2 2 2 1 1 3 3 ... 2 3 ] + passed + ==== cpu compact with scan ==== + elapsed time: 1.09453ms (std::chrono Measured) + [ 2 1 2 1 2 1 2 2 2 1 1 3 3 ... 3 3 ] + passed + ==== work-efficient compact, power-of-two ==== + elapsed time: 0.022336ms (CUDA Measured) + passed + ==== work-efficient compact, non-power-of-two ==== + elapsed time: 0.024768ms (CUDA Measured) + passed diff --git a/img/time-vs-array-bs-1024-zoom.png b/img/time-vs-array-bs-1024-zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..22bf6c15e32a574afa3e2aeda780faa2c4477f6f GIT binary patch literal 24958 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 i7iIwq@&EjV*LSXbUkeFAMEC)KN0O6Pk}8yVx= literal 0 HcmV?d00001 diff --git a/img/time-vs-array-bs-1024.png b/img/time-vs-array-bs-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..502a2248313aa1ddcdac0ec452d8619435a61c19 GIT binary patch literal 18741 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 E08nko_y7O^ literal 0 HcmV?d00001 diff --git a/img/time-vs-array-bs-256-zoom.png b/img/time-vs-array-bs-256-zoom.png new file mode 100644 index 0000000000000000000000000000000000000000..db7a06c844851544f6fad43369093e40bde6731f GIT binary patch literal 25941 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%0AWa02iUVf~W1192}gtsDCucm6eG3|My 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 literal 0 HcmV?d00001 diff --git a/img/time-vs-array-bs-256.png b/img/time-vs-array-bs-256.png new file mode 100644 index 0000000000000000000000000000000000000000..d86cb0b56e4109214422f0bbb6e0b5ba93b54942 GIT binary patch literal 20285 zcmbrmWmsHYwuKaGlZuU!3qe2*JSfVgjytW<8!aPVLyDXn=r1Un#?O2+J z@$O8YpgO3QhdAoDR3K}a>X>qjz??`0J@unAcq49fMBOIWcWHPZ&CohI19h}NB=kNK zb($>(RJAARSHCJrZLU2IFm=4s{`667bk}ZyII_x@Xu)c|HQ>AROVpTtUvC?`lWvYa z67SW4{rZdd^lw zjX0?wndP6QDFRjE?|8wDF?!{RTC|9vt(x7k_$ew-`#Cq)14vpba|$%=)4-D5_`QZm zEhl3GhkBAxd%GjQ*PW4XT3T73d2n+7`@3NS<80}(-y8gTd-!fGw+qNvt#iOAXL_2p?zHX>zeUt>@D;s#yUq@xj0U z)mtAmsO*TJ3fal zXnjaX@^*ikxIfFtRj|L9phNF$D6hkeSdc7lj$d7kxfZV)D4!gho)!;Rv8#Is9+I>h zIrLc>Z6WE&<(bz`<(z*WRZCTrN_(TlXD!V0h@ zEd9!!b%EQ(*~UmByG9%uh%r>g97)2xeFDeEh38GiXS7n!mEu;KgsCu>y=QaS{9bB* z_HqDLr+c!NunxcQ(bL4HDzD;18_4uJK20yEG4MPUB0bV+^lZCs-Z(tO7BtYtXQe(+ z`mOtmS4O5iX8+qRy}A2r9DQdFT}(!m6^t+=Q*@y|Vc)*aXZp->Xu)~*BYvzSeO<@& zsLQ=Z{|G_dljRWnnEAquuSE|ksPn*lS}GBx>+ngCCrwdFeFTm)`Wkwuef?OXQ9vQ2 z*mUpYQ)`c7mU+RlgT3y<>)x0)7?po=rj7U#I<}s4NDFiO`~Z8siAT-Rkd%48luo+r zZ`Fxv-^fR&2H8#MZ>_gkQgZ(6s9m@jq$_CiG0z58L)CHdsAihYNj0lxzTknq_e#{ zQrS$N!gNlcpOeyJY{^_T%?C|F-{YXA)+XaymWIs3m?X0Es|aVUs$&gTM`P0oPi`a_e3LT@PLwhqi$+1L2 zU)$=Dn$%W5$k-lBc5D|aqwIuMrTJL>eT)(F0_27%?)|%vvFT zzlpds6lJ@=6#cY&)?)Y=ROhbX?O|3$w!J47>@rk@@H?v9Ha7;RbIz@}!v_#Z!ivI<8RJ+~M}^R+*&Mst4Owq$w`RHuml z0b`>dEqmC9X0*38aiRJmZ;*TCj@ynFai;ni>=l3!%0ALC?*t4VX&dNQd034P4LL0> zHWfBc5}b}H=-z2%?|=8M^}v39&qdtiF*ffY9a3$HAV0c8}ZTN#N2hV#-b)503#6}ahrTDr`s_jBvc^mt* z)w650V4e3EAVPmdeIJxaq?r3kV%t1zL=R_i*V=1Cb4RqOxvay1OR-d0G-8cKE`mBz zufyji=A2eF$n`x;70zQcy?N_$h$!9v?BxT;Pn_@mAd5jSkQ#YCdJdI*v|meP$<*zA zr#2#iE;p+HzrVSz8fS7Pcqgs^%X^x2W<4HiUWrbSW;8c2m=q7!XdBQQ$fudh^Yp!y zp))+1(W+iN3)wADcHzOl((*PD(Vaf4PWG|VaI5j?OqTyeb)SyrF$%lf3z!d}fjhC7$h47|MBnPpzPWiDc*Wtxj%P&x z!RTPA#hsnr}4#P!EcTcet z&poV@z`?4{yl-r2V5;waOr~+fZF=+8K7BUyFMgCA)%mnH)&A>;xDPrU6Y18;#nGYa zPlHV^RZZUSB+o>H0&d&$TaS zzHc8-cDzwBC2Ju}|D7x0q$l0Lmh$mfOD1HIR+MGYV? zg!QXv+OQbmwDG)OT=E1LY|qFJfu~+NuHxYi49IvZl(M;OhMMxWH72FvHaBe?0opOLGt#+t+r-C(_2AfejXpkv11rp zXVrz9BFRQaSHtjJ521Js-JmHtp9&&s z;L4_1wPVM|Yib7OD_h)5;se>Qh`N%rJU#b<;v{F~fry_^n#6@jMi|7irN_na28KHD zw+?x?^xznhc1(vl+t%^44Y1pag9O(oOVi@p!^V)hjyOlK z(fo9hyVwAW1syn4VPktsbI~fW^9=;U=S4&|)!a+Vrn9{n(y`b1bUT{dLkT%s>{H-k zQIqaT+Acer5E`BGOV+0=wzPtU->z3o%kvKpQh$&JV+h2VePzs7U?22W(1>A0$i{zLlu-UOg zABM~RF|0~Rwh!Uv?4Vc&x%bxD}xuig(P0irASfL-U({ z4MUB#c%s@p-3FbkRBN>l<-s{R`~-EUfQfNa6YkN=MKoUI`5t`)HCo#YLPj*cMfe1*`ImW4e1^C zJ`)i(aDid2@tXUIrivgN?8U;-qsyVOyk?Jwwl8-2NNHKtyWV_^l*hl&MHDd2^3zDv z4kL}*rx#}`bf`S#CtV!~O=I@Qa%T6_e*_8cV_Z5xlOj*N&$I_zm#_jNn-kejo8{om zT-zQOLV5TehQZ5GeR95ry8}DyQ)F6|2Br?LPtEJFqo6MLdwhON-NM>oQp{|*E+eM1 zdVD_2eTD2_QI_J*WP0Dlg2ND{2g%h^*CR^0u5U&Z}N=mK*l`ZPPSAyU>)vY*78m*3tGr z>~WtAZxv5zO3y5S(~hT5^V>j+G@K#+409|nilBwl+Ty`~(nzlZ>yqGSbwM-O6JtdXG3P#)ZE$HvhPU{H=plEx=LzD5k zQ7G&d)fTS}1+C2;SqkxG@GroEWQ#uEU56rn86(%(+hxfa`+AW0T3q^NkX|5gU2?`_ z_JXe0SF=XhjyZ_1;081tr|wp}kT{KU>pbbohl#j~$$|*n>g|iJMG+wzUWdt}^r-A4 z1`K^43CQoI9>>ZP0>#uNh5Um#Z0ANxioJP8{C6g`&PLlv5GYeQ3GaGwiDo=B^heOq z1qPZ9;}KWZ{9W~79cx9!+YEO>*FSY@dUUnE$}=`|nIqc_4@ei(n+Vd9035aL1 zeR#L?c!h7^*ZHSAOU|)OJ(S*1q;>__)|M?uR3yXE)~GiBI7}sXWjat^m0CZ}L62uf zCX>7?odbS!VnG_kyKm+}P>VSem3bqT->3gPksHeMRzZNO?ru)=lpHe})GvNRb;f9x z-kB$8_5eBF%GLHWc<^9cq#@Pi6%qT0t6D`g*Lu`a)hHv_m9m!)Gs31S+;rf>`MeeB zpnKXZbrNm>PoKA+ymcp@9W z&TBk<+H)ISD<;!`-_X>5E7E^9SP{KyG7IxJEUX%Oz?Ud?dl>*jotMh<29=TZ^M8U(J|d_h_6mWoo=m zVQ+MtT)llMpX(4D-5-LV*+b|W*5Eh>)ga*U2~B!z`s}AXX6vMOV`CO*t(v!YHQ%jy-9RiI-^lja3$y9vonDJF zJyBq%U}!&tunKIHC3dbeKQA!Coa``tE2`v+A2D1~9EtMeMM+OYJKJ+C>t4^hP?_lF zo6*lg<_Vu_AOf@~Xx>TCEAW#6h0H9eca)HhQ<_a25A&^~46pCU)-^)fBhv{_c>U7; z;1!BzH0jkN;{MW6zX=YMLlEit7M#UOaGg#(?%1$1!-`t_0AVVe0G_YuPPYc3#K>I2 z2J7#-4b~S)kIgv>{20ojESBKL0Z%w}IDyI^cB(4P?oz)tny11qoZYMFAm zmSeY*-JJU^3UlkcB2I+r(`Gwc^rBpv79ZsnInO^8PM?iyG!;FQe$xx^RWsab)s((Q zAAVoO9oTHw5x$@}+E(T;QGhTzHfP1*eq$G9{P@gQmz|2!u^{NQ@VLchkH*@gFy;HA z!|ueM&Z9Bc+JA+wMuNCN(w@}ffu`bUBQ=t*?zX`>dYeCdu2|#QL4it*#%J^r$X`p= zowvPn)J>K*oiCbSJyX-J$28tjSht2jqd715qW7-PSF;F|-*d9rZI2WkvSuOu_esJ3 zHRX8c?--Nh;4QB-7IPo3Nxe*DsHXCf`FwBU+c)>VxUuJAXM22-JUG zxJAzVcmulsWAheIb)Inf?zDF8vj;;z)Sbgv$=`4PTo2xcJHMD}NT!?S{152o@}}a5 z_~%~sogFm#9;sH2Wk;6lyB3Sx4w{FsMvP6{V0lM9f|~o?fZLIsdTknjDOG=((#xYu zfze_tx|(|6VO*A8{6w8a*~!%&eM4mfzsN}~6s$N~%N9e=_A+jOflI;3sjjuvQ(ogW=tK5P z`r!qp)u<1^O+M-%J8!!}8*F!eeW+~twkDJChEahAmI_7W>qI& zeBRXqaBcoWJtZw(xW4V~ykqJ37K}#_$sk9P`{K~lLPfxX086;O+QgYTL z0R)=v1Izhxrg@|9^TtXkF=46%JzgKU9|;Xg7&qzpD|Fq}O}?4c$V(%rayI|Aid`g`o2QIt{;%SANeNB0zV~Ym_n=cx06%TJ$bE;?J!lk{} z?(z6L67+>vfO_#E2AH>aVDs1ZVE@+a-yaa|IG0@(=2s+Y>cQB5w=WPi=cLRB8?NoM zJr~8cQ|`x#0ldwg)9sE{S*zFA@9^;YjH(ACaEOV)T>wI;VY#wA&f8q^ls*5ch1q)c$eQe{wUqT-pNtdE^_-RIl1Cpi~IwMW;e|792yu<_XY=- zhg4F2#Iv-DNTqA%cRQGmeuwJrvz0-?0!6!fKWW{{0L(n?Z>YTKGYvLF!#^qzYLp5a zi(}ibK3aKC^aW5~SYtB6y%ZKolkA1O4KYxxbe!cQICinms1?+$1tJP6nMFF@afNb#Hd;jr-ha(DZBoCCHbP6%zBfF>GFG zJa*4z_|P;KPo6toRepTY;4LY%z{0jh3oDKwf9(haRFR;`Mov!GA{DyW(dG)%xc4F6xD$b!V1~8n4f|%CJeZ0pp4SJ| z{o}8n`a|Y0mqfB&liZg2Z;S#*#9MQEoX2Fd7n4S^sNnZ<7XW9*`nT!j6|+~_klhGH zJ@QaxlonJ{Nv{1Xt~vaM_wUZ*a(dss(sn*Yh*~8+XG+H*`r5ucqb=u5WD7iFX%T%U zTZT}Y~GL6iWUZ63Ek4Qv9CO;9%HF#7w_b_4SbG7+yAEf`~9L04Oz_lCi zjZGs3xo>@!ftuS_Br{jmZ+#Y?B8Q28MMb($KDPMy99_=)qc`d}v_@buApvJ##>9pP zGS7Op{NTL`lTXhXqlbNfSs7|^q&ul5HyL)1wy@c^tYQN3_5qK!`J?S!XYPPNl(W4r z&&A67b9qXXDJzb0tkp+_>TD73NM(?DIgvoLJ_xUX+Ki4CNj!srnp`d>}-J_rc%SF0VX!1|J##TbLUb)>hLs}1M|D=!MwW~ zUF?j@zTGDQaq*Ju+)K?bi!OD}^`HP6t4>*}(=kxgWII$!p>>gY;WRa=Z8mqF^g+5Hhz-w`Ytxi7yk~- z#l<(KvtN$al(4hw`q{EZD^l*7Pg z$wG>b2|t@*yN15PsqT#L+hp5YzY_-%kMdyBVwAz45Q@IAq`Zh``S8^{p=;&tvQSjl z^yX(;XjVxo&oyzQg~IdJV40(!2a|I1GE#1dwp&1jp3wJVX^NW2PxVM#+|ahC`&-)E zYZ|UPK|U%TGU2ExPrGno38snoeGYr+-%fPjuQ$(feu|(fc9=--HsBL$H6`%GvvOM7ppm-RvL zJ$$T9XlL+U0L?b|z)R6J>IIrU(dA6VK%??^v3wLRsuJI*%hc6IHqRYzUhZMXU#YFb zJR00E9MH^!8`&y+uDw*vb0?5H14 zm^`+Q!H!=8HG3ZF9~SpHI-h&FJHyn=I{!9j=AcR0V%)K*vYx;)>9W=!)3@i77wOEj z(e<>e9=MQfh)cm?{5SY-hF4oWh+=T}M!Qq2jf6rWWH!Cp&7;AowC}`Ru)#$WoVZ?u-6>J-CibWz_%CUr{osmwXUz)EgE@qGGw;CbKns$ zwBV2wc|GN--}v(z2q5t|X7vwFCg}CPOtkUSxsg9h9d}|kDS;7?)7skf z78AhZ*H~~}1%-;e{g%DCuxR_K_WB=VVeQhH6mt2p_YOiNgj}Z`eG{vuW39I_bwR~4 zqqQ40`qsCV()~j|J~ZmTx`nHe{9=Y5i?C4=WKP&HulPUhwJQWmRNeB6{51;wnOa!FKm&n{l-YTi(x*$rt7=JI&uyMLC0KVGuSedzuUY2y zdg*_sa$s?mD2hY?i^OkeSlSb|cD^bd(v^tdw!i6gOG|Ac%Iv;}v!7QiFL!!-judjG zmFsCI#p1I0uDQ31pbqeX)e3wnMa*Q)Mx!hn=d@W^j8;Vy($M6;a?*i>%m1jv*jwdL z)%d1p)@=QvvELsi;OdO(DVqA{vreh|ESYj6jT1}kCk|1dvpBufp1LoIhT*&k!ii^0FTxN6IV{#~{3sUL;Sj`?R()CuZj%+I|tP|}l;VHvvG#;p4AnscJXF7CQn zzCtCiGkEH;2&G`OqhOi3Y+H~ipgYu;%6J9eWn6U_-32hIFjs!y+dW5!k=F@B()rQC zp*IGN%Dc~P&_BZEd63h5UTe)u?D;zHZQh6~y$uI}epX@ztfj}bi*Y*J5bfCW<(dt} zfbteer>z#Q#7iVv%E!#EbIba*B_dKgJAT4~q(G!qrPzWT0K+9X?#&-(Z7#MbL@k8`dBO)jRfM+A1MnT; zdH%!gQBKTPl>Y4zMZ z_lM%l^;Y!v=2C5M(`68b)(HPOor9`QG+W*2K$`NoKtBx*9>|RDy)YghX)qtDUm3^@ z?NW}Awk_R1^m|N@2R(h4YmZ5aed<%9svb0u*t#vcl~!Iy4k&B6`QCRD>5eywg5I=b zzt2#>8_D?(=%F<);-1;`h?KYvzCQJn)Gh^lVzs>EW9+&#vUX~5M@)S@$2u66*)$03~7)ML-X-WrOboPmBhb3c=oQ_Ni88!KeEl;p8KU`vsUzF;DU|m#0W-j(*Y(( z7^0*O8wJC%rG7aWn25B<}P)isNmL z{bwA;>Fm+5KxOl-VAA_GM}EZL>=Uvo;;(*1A%dOyA0$5O3I7AZFl4QM>tW~aD4DhG0*vyvC{38RvMrc-`A;u0uZ1}c!@1@0QR(x6n}Gl&1yFar%M-gPV@&)-=oU!* zGAbi+P#I!ZBwF~D4RfZvDW=PC!{PPnCcYYD5?Q&#h+f-`YQ{H2v@rkZoS6dOQbjh6 zo|olEdGaG2#DZSRS0VCPM&^C zy`tE<(%EX*cd;{QKTi7D{cn=M7$6BWC0RfqjbLEPylZv7luPd$M%|yETrSO>m)Jf=P-RaR zad`s2<{Tn}cg`&oJAI*SRo1T`#{My;p(8yEDSRJ}@rbRKo3KD+-uy zmnNPcXMbd@a@5wn6)zvk)_Zyz7dNr{>Arh%zEfkHnVba=ttAp|pI0N=AYDzGF=9H4!R(%b=M#AnEv`Bro@O4(}9`N@8UI(2E4@ends(5=Q=L>=bO&y z{oj_cS&c7@4-AZt|EiECMzom#iq3yEe(Z-H!6;7{FFRMx&u?~!OSfD|Z|C+-lDOYu zT+97!Jel5E~zO`iFWB*bwwY*~-9R zFhY_%+|)Mbhi5W?zd_XgT)cBCYrmnpi!FFk)UitbzSY3G z0x-8uy6^;OoTd^?{h?KAv1r|VS!bzp`$m)*wX-A9`k;~33j9UXogexhSXo5(W~~#a z%bz&$f5J(T=5_{^-QABX%1GDtJ4bhAEFP_z>4|`Ltz;2ZY!ngW5nmx7N7d4Gl!sIx zsc4ZD`O|yn^<7qakF7wKVWuId72*Lno4vg;i{j+sa$HlV@qZ*LlsKG?;o94g?#8fR zK5f>TI`<%&k-?*1l+1WM(ytep;*jem7~Q`isp~JV`o_T~?4ue8`h?TRdVzHWdxElt7OSNfi};7m1e(w|EhH5*4$D1B!=MAdlfCAxw4CaSzn zK_o11WK}eE(tx3^PVk7Yc{L&K?NW!wfEDlVN+gq}7jIy@RD!dWm;H24k$y`;b7p4S zRIEKdL5>Nm`%7zXdrE=KJz51>0!$ct=SB10}QU#7oeL$2S@Z~+60~#aYii1r{$qr&-khj%t?(j78m-f zSt7ab3Lf9KU97OYoV?LmxVxQ5bW`sff=8GjR_R3rom$9La%?+WCk+33BpPD(A`s-(%!E0h`V z(oJ{vOI|ah5|UMCFTXx4$YG}va1KOLT_csV*E1`v{dgPNZ~1uECY>DAb~*e=^SL=E z)nf2cN~oh>RsffEt&E-6y!jvD5ovdEB0c(+&}=_FN<#5+XQ8x^@_k*Q_$;m1+n3)1P5if^BDINiVnMTppX2HitUTVs~5T z-JHz!-C`-=_e?DBhQ(HuerzIr8%1kgKg%JdL=4{68d43}7ayN)<^7l#% zk7M&a_+TC!kz~m)H#I#(N*Ui;ui)jF{vin-Z`%hDy8p zhC7i!Db)2mLu`|rYjY*!PxrT6Sn5DK|A!m_CZ@<9qlA5YfRe?kfAP~me!EAWo<)8P zQ3^a(HGi=DeltCpKoD0Di%SE@PEG_f(VOa`fNhMYflOnp(LmM|QaQd-K=hu4+Wk*8 z@yJGqSB&VVlwpYX%j}(_BLypb(1~J8H$^w46v7LYSvCkpW$5B>143~Hg~gU5!!WC1 z>2YIxLc&%UO5bmdZq4cF@LTDabIp0DGb$duTl^%hzC3<`*@jWpU!}jxe(33&Pjaam z8Phj*0mzz#Nog)bC*F1MRN%TB{t!Hw+T5m<7P1fEb4aYS`<=wNeA$mwM)5UZ#Dl39 zsob9x(YRV(fQo3tyw=wF$K?afICR6hOM~>{Id@pggl^*=znArt6_S>^lYl@cZ)4LR zi-0sX{q|q1y_gx(nIp81TB^~7C2_##UA^4ec_3fG0Wxr1*K;loeTr6HI3hI~6bynN$PcVgPKxG#Nd%*KFyt_A+nI5%pdzn`H%+N2WBR(b6s@jDY)qJ4Jv&nr%7ALH8N=wu~8eOGE;a2i$F&aEPo zo;kHL@Yi?-%)@R+bTfAGmFheQSq!cBIoc~2Wkb)Es6!qk*%)k?7$3ima+sbgb?)}* zQg4+m}d;l#e{Bz+&L_w_+vKtk_IYUrx2d3D7fL27C>?d5VW`*6HvJ^5!Q<<+W7} ze;k~z_jcFwWO~6fe?7hv@R{qF!!FDe!LYvCQG_&~eAUSv6qMfU1j+<9srp}y;6~md-V0T^p?wVaA|muzgJEz=k9A! zpk=&&ENi2vVRuR}?GXAFyV|zii*Ej}t0jnF+$@OXziT76ljefP#Th_U=mYH*-5(@= zb^0l4YTmnCyl(Ks*_Cs7O_>?x;W?`c9hx7~Vf!dV3hW(Wep+==w5R*lWA03=8f`9$ z$PUI&A5b#OcUC;PR(^VrBH+u{7Er&DkvQd~4wP8m%-_bw=!Uv@DbM^|P}?VW7B@dn^rhgH8kg(*2`C7Ov4E9Q(w!rO)HF5t2+~OM5bvJCJ#A5 zrY*;tJ&kYo^>#hHY24BYCsZ82VkCT4t-V-of`^K{3rzH>K>S|rR58}>@%;z#+t}pr z2!FqtmeV3IqdDUnwc&ck7#bpK;p^jZb0K=I!2E#C6zvtaLB>~li~ z=%*UBp^C?CCZW0)kjw$eWjRr6i7IJ4xbzVbjN zRgAlpjDxtLhq>hdtb24|n;wId^b8d80TN%#ZW+fQ*`m<8q)!DF$B#k}vb~=(D$3yf zi~;J8ePCfTvHj6c?dt)FBB(&JdJSv?3*tYldncE5G36YkjQEH*yUK@7-lKTq1UI&! z=vKm`${HZh_u<8dTup-zo0?K6xT{ClmW&zioP?N{xZQG~fyIex`q)!3VUhe;vV}H= z6U#5l!mSSncdMocGhgw`T|>(o*qkknH((v z*#0N4!hFu=sxtET>eud!{+KvM&hfaoxZKIAAciPF9;zh*#LrJCm%&&n#czx!6v9_c zg}CdErTsIP~_Y1Kc^YVoJiId8&P` z*@$?>~}qkjSfwb1^4@l-PyEe**9cMW9e_maFvC$oyuq>AbXErW@Usv-|geUWv= z%7YoLlmINrVmi)Wt@g;r6?gGK4iO{YzadvIR=%cfZBd+$xn?=-fn6|g8YpP1cIpd& z5%r{ifyVkD6bdVEIuo!@S_+@3y#P^;N6Z!)__{OH3{+=o=G)^UaG)qkz62F9aV@8J zoRG_y#5G5vK~Bg1{Sq0u5T5(N811eehc_b+zdi6e93@&xb21d0!$-KkB%IXk&2h5p zYT3%S{l|t;qcz+DpdDfX+M(awCmqI_o<|A`x}P$e6S9hUkfzc;Q4Vjf{M38QvDO&Y6Zrk_qqS<)SxhjhN&WCa!@o41I2C}z^NaS(K#HO-H?`|x zUp);s$QNvT((W0akPE!3Jq4ce)0+*TzA^2DCb)_v236DyUq^@qoktS#rQ@57nsiw zyB4WVFqv0JEdo=$Q3u4HvhVfTZvs^!`mk;%F8`So#vQXt{Z5sfP zpV1+0dK-(n+~i>k-MR)~>AQOg3UCE4q;%ylJ0_#>v^Rrb`DNo9&5)Fh^oQtD@ z%tY#L7fs3Uzd#RmU=l{>&(=PjKw5J}H0y$!b$!^sMMArPot+&k##f+{w99eR1!ntD zo0birMVpK+!>7u3=;kmrGYzx$VESMtaL%-y(Nv9ziK#Db9%5ruJ8vY5?ccQ;a_<}+ zm%(UPwz09X!(K)gW2qY$F&*Nkh-_=xw z`tN0|`@NX}9^{(|AV=o2>vHQ@eJ}(z*I7)A8OoI``a;|%;=dGqP&q&7h9bQs#_4bu zIYvJF;Gad}msWr!9rX07(2lyC+ey@9a^LT*bX4`Yqcx4qRZ9xUevkQ{@XS^zx;5G6Upk=-o0nxM4fY zR8&-U^=wUioD$z{muiFGTWl;5ODtNpc_sj>YRt68Ubq+%N>MT>NAz#k%=+G$WU z5^zJYxnKM9W&gy;PxeGm$iBlz7HhyB)NU{24YP?&Ql2rIdW=AM@vn#psY2jdDhTb+ zO`g-kKGt>_O-Cgb=j55bCRzJ%*9!sp{>#?)zkVxPDr+n)^kO6@&8Zh_kc2}Q?cv48 zdhkyJu^J$r+?_3jf3~el-=68ndkimjSbb~e7exCo!0HG%CBl5_vMN%IL=l|Fa~&K* zKaamf(DQV>51j21=ak=lPkZ#;7j%*VIGTj$nezJl9#S;fkT^az364#VSjUW%|sd z*G2FRW7)R_pr`#-umOhwdonYE0D%c6GrJTfi=s=STN=sg72o{8wFp2hB>##Xfo znl5u8b%Du3k_~g>WH&O6*lf+gnmtS?zOt6T4#YUcvDS`3OiyEE6-aAn7>^PcIYrDA zF3S9rM5H<)9jkPwdNybg-5eY1W6EJka$+=^W@xweG5^^d+qTl@c2&E->)}ETF%9~{ zI&?gO<#h*#!>wxu!<4#6)sd`jatDRceAVz1U|~1+(tz9#!;83Mrs8Sbng?S9f72gc} z{fspqEBXh zk9sH~lXird9h_780@(StPUhG(84)IyIjZi%kKFMDodt>XC&)lIYNkjbO#4(=KpxUE1FV6<9IlH$D`#l z(Q*16t3lsAV?@T6%w=30h=!Dqsy$&^gE)@pON{aZuFq3n_{!~~FHcTN;;X~8KV0RisB+kF@oB{^qsRE$mbWgI zJ}bint`$9YYAZ*a9nMJhPaxr4$4_<-<{$80cR02GA z@ikh&ji7ZXou-*nTu32e6Wi`OOqw!VFAw+i9h!2svQMtPw?s-BC>pe{lo?$}rZXmR zx(G@z%7dpK8$fC@C-f2s#G>}gE=q^;=s4n6e0VW2&+?9II*@xq+!ainlYV*oI?Zne z)skF(D)@g|xze{J&^_v%8eGcM)XJ5{NgbJ7)5w(^!ptRiN;8)!#a+wXFts$bTyoOf z2T;jWBrw4=5g9kiCCw$veU}tP6VWh)OV50`U*>u4hkO5n_j!KjyzlQl&pF3M97?nY zQ-fO5e#o_`$j6rxs@v*gpP8GrfczqgLBCsojIKbRz_JrfFX)Q03vR@$MKOQ3u9Dt9 zW`WjV=V&Z#D$CV2W0>LTo~NFXC$_meoMxM#j6uRRgWEcT8kBqZ4=O+O&u1p zO^sRy83$A;OuLq$makfH&H=_b@}J}{T}$s%ndZuqlJWqp2om?za6bU7w(hGcFlRCz z-+0$EbCQa42-Ls=oEFa33>k1yicMOg7awQDr!ittw|8;zjFIylAl)|1fX7n|r1JG%u(UaAV~_g%&{kQORPTgb zJY%dTXk=VpG7!tan_chrvny*T@yYzFJv{egR{uBEI^-&E^b2DK|C8WL`{ic|7k+hz zzHZB~-oMI^R-l=i4h2`wg{QwAb24mMkT?|SXGJNx#hDs#;w25P8AK-9QM&pw z9?2Y%x;-8eu+@ve!fOCS-|1<&=yS^AJ2(HR1R0HNHbO^yT-DY;oub7P4IS(eLGtP< zGk3N!=jw2T2L6w4VQS2ttpqJ%oQfehk%^U!(7#1WY0pW~S1wd3F}m4Q%AZuz5_(?n zZ<;!oPGFexaDBa@9BtWed~jE@#^bQAtxlG(8rpZ0Xt1j&?!50HcQ zYo%NT4sqhL%?=Kwa-}?)e~;W~W&(74kMm+?i|5lD2PVjZ#9;RzQnzJJZm^cIIKJlo z%9Zg@>$hR+_zTi&rPva#e|$+-n2m~p(hi-J6d1!x{R{85zkjT=@;28wZR!~8#V*(y zjR;J-MJLHlm$)WlWTvS6B7erlJMtgeXhR5?Yx(>MhLUPvziCt6I#3eZdpO_3d2J#O zpV@f)xc+Ff0mE!oPO1d+q&qNP&zwNY78P=z%9JV!qeO>L+d`xxW-Vf75Lt??wbdYn6ECbd4P8?*)NCn&@_Hu6*@5k9OF5-c7}xR+wHb3<>~H)O>I^(;ebA-0PQiL9YEh2 zOHg!9I$bflq#>B?w0zTOQnNV?TaO>SIjN`Q*QO`=?Qp1GFqBu=mgezP(NjZ2V7REp zn-CU))M8~FV1Ws?pyB~d$j;{~D`~?t!fNAP*=U9)5Utv>`6<&3CkJ6)B7^}gA;Pr$ zeU3i?!Y!{|>9<`?cwW-;6mn|P~lPcILb~0R< zKWH0gd13i(Uw<|*grHx4N<4EbqtB&2Lri-m$V*zpQ9`J17>4z9Z>EP`v;eN+;76hE z2&7SQ)Y^6LBuj8AXwvG}_2tU7LnmTKSh2kFvq5CCwP>EY`U~eM%3%Xc^U~}iB*X2S z-D%m(yZyD1qG2n@?Et*>Pt{vdA$JjW({-UN6j|-tM#@d^;_*m(RHe8j$CjSwJmlgO zW~!+_dQGMDp-98`IAMK7V|oV=tbicyX(vC$@8geFu^d>#mlroDTgbW(qM)+4t=VbA@#QERL- z#NX*!s6>fs6!%e zhmbUVGdE>?SNlwcO>H8as0nMmBW;{aE+23uzDw!b_CHb72sBL~MCQD|{IkcDJEN?6 z!Z*S;nl{inm7Qd*hh(o*MYGc!ZoOaSyuMdfGZN0EY%M0kB|5Wn{k>Knja~CrA5O5V zu(A2$?A}aGaArEoeW$OH+7@yo$Fbpv2gwCkF6Njb%=k;?h3WAV>0g0Jrx6M)i3OkJ z%-aX*BH5`kl*(-?7_v3Z3k?ut`Bq+c{gK)n6@uK>)4}-G)2EIrZ?u;2@S$*ZO?kl# z=>CRxIbpArN#qg)+2DgBZt0!}t4$JFC?gjgLnZ1bv6){)E~SU-pO=40I2=6p3W^&j zaG8p#h5(gC_#tm$_dw18)MbTW`_L;wjo Date: Tue, 18 Sep 2018 08:55:52 -0400 Subject: [PATCH 15/17] Increase array size --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 1850161..421ddc5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include #include "testing_helpers.hpp" -const int SIZE = 1 << 8; // feel free to change the size of array +const int SIZE = 1 << 18; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; From 2e608f82a9a61ab00b64df77c8ec2b98c9a0b4e7 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Tue, 18 Sep 2018 08:58:30 -0400 Subject: [PATCH 16/17] Remove pause (unavailable on Linux) --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 421ddc5..0d9ab4a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -147,7 +147,7 @@ int main(int argc, char* argv[]) { //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); - system("pause"); // stop Win32 console from closing on exit + //system("pause"); // stop Win32 console from closing on exit delete[] a; delete[] b; delete[] c; From 1ccb8413f20aeeebd3e637b697862c8d21bc5a45 Mon Sep 17 00:00:00 2001 From: Edward Atter Date: Tue, 18 Sep 2018 09:10:33 -0400 Subject: [PATCH 17/17] Add note about timing calculations --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7740243..5e390e5 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Compaction is useful to reduce the size if only the true values matter. Think of Memory operations such as `malloc` or `memset` were excluded from the results. In the case of compaction, only the final step (scatter) is timed. Any preliminary data formatting (for example to get the boolean array or scanning to get the proper indices) is not included in the time. Unless otherwise stated, a block size of 1024 was used throughout the analysis. +The timing data displayed below is an average across two runs. Ideally, there would be a much higher number of trials, though in practice the timings did not change much. + #### Analysis Large block sizes perform the best. This is likely because each thread does very little work. Specifically, a block size of 1024 was chosen. See the graphs below for a comparison.