From 4f9a43d2357cc50c199b261905f3d1e5b4f501ba Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Sun, 17 Sep 2023 19:43:26 -0400 Subject: [PATCH 1/8] Complete 1 --- stream_compaction/cpu.cu | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 719fa11..5c56ccd 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -19,7 +19,10 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + odata[0] = 0; + for (int i = 1; i < n; i++) { + odata[i] = odata[i - 1] + idata[i - 1]; + } timer().endCpuTimer(); } @@ -30,9 +33,14 @@ namespace StreamCompaction { */ int compactWithoutScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + int idx = 0; + for (int i = 0; i < n; i++) { + if (idata[i]) { + odata[idx++] = idata[i]; + } + } timer().endCpuTimer(); - return -1; + return idx; } /** @@ -42,9 +50,19 @@ namespace StreamCompaction { */ int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - // TODO + int* mask = new int[n]; + for (int i = 0; i < n; i++) { + mask[i] = idata[i] ? 1 : 0; + } + int* idx = new int[n]; + scan(n, idx, mask); + for (int i = 0; i < n; i++) { + if (idata[i]) { + odata[idx[i]] = idata[i]; + } + } timer().endCpuTimer(); - return -1; + return idata[n - 1] ? idx[n - 1] + 1 : idx[n - 1]; } } } From a94076779845cf46efd7678e0338dfd7ff10f3f7 Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Mon, 18 Sep 2023 02:14:13 -0400 Subject: [PATCH 2/8] Fix 1; Complete 2 --- stream_compaction/common.cu | 10 ++++++++-- stream_compaction/cpu.cu | 20 ++++++++++++------- stream_compaction/naive.cu | 39 +++++++++++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/stream_compaction/common.cu b/stream_compaction/common.cu index 2ed6d63..f1dcf67 100644 --- a/stream_compaction/common.cu +++ b/stream_compaction/common.cu @@ -23,7 +23,10 @@ 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 index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + bools[index] = idata[index] ? 1 : 0; + } } /** @@ -32,7 +35,10 @@ namespace StreamCompaction { */ __global__ void kernScatter(int n, int *odata, const int *idata, const int *bools, const int *indices) { - // TODO + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n && bools[index]) { + odata[indices[index]] = idata[index]; + } } } diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 5c56ccd..8752a03 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -50,19 +50,25 @@ namespace StreamCompaction { */ int compactWithScan(int n, int *odata, const int *idata) { timer().startCpuTimer(); - int* mask = new int[n]; + int* bools = new int[n]; for (int i = 0; i < n; i++) { - mask[i] = idata[i] ? 1 : 0; + bools[i] = idata[i] ? 1 : 0; + } + int* indices = new int[n]; + indices[0] = 0; + for (int i = 1; i < n; i++) { + indices[i] = indices[i - 1] + bools[i - 1]; } - int* idx = new int[n]; - scan(n, idx, mask); for (int i = 0; i < n; i++) { - if (idata[i]) { - odata[idx[i]] = idata[i]; + if (bools[i]) { + odata[indices[i]] = idata[i]; } } + int count = bools[n - 1] + indices[n - 1]; + delete[] bools; + delete[] indices; timer().endCpuTimer(); - return idata[n - 1] ? idx[n - 1] + 1 : idx[n - 1]; + return count; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index 4308876..e15cc65 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,6 +3,8 @@ #include "common.h" #include "naive.h" +#define blockSize 128 + namespace StreamCompaction { namespace Naive { using StreamCompaction::Common::PerformanceTimer; @@ -11,15 +13,48 @@ namespace StreamCompaction { static PerformanceTimer timer; return timer; } - // TODO: __global__ + + __global__ void kernScan(int n, int *odata, const int *idata, int offset) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + odata[index] = idata[index] + (index >= offset ? idata[index - offset] : 0); + } + } + + __global__ void kernShift(int n, int *odata, const int *idata) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + odata[index] = index ? idata[index - 1] : 0; + } + } /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + int *dev_odata, *dev_idata; + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_odata failed!"); + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_idata failed!"); + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + dim3 blocks((n + blockSize - 1) / blockSize); + timer().startGpuTimer(); - // TODO + int rounds = ilog2ceil(n); + int offset = 1; + for (int i = 0; i < rounds; i++) { + kernScan<<>>(n, dev_odata, dev_idata, offset); + checkCUDAError("kernScan failed!"); + std::swap(dev_odata, dev_idata); + offset <<= 1; + } + kernShift<<>>(n, dev_odata, dev_idata); + checkCUDAError("kernShift failed!"); timer().endGpuTimer(); + cudaMemcpy(odata, dev_odata, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_odata); + cudaFree(dev_idata); } } } From 13e72eec35cc93536310bd3925ca1eb2ff589f04 Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Tue, 19 Sep 2023 01:28:48 -0400 Subject: [PATCH 3/8] Complete 3 --- stream_compaction/efficient.cu | 101 ++++++++++++++++++++++++++++++++- stream_compaction/naive.cu | 2 +- 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 2db346e..acee83d 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,6 +3,8 @@ #include "common.h" #include "efficient.h" +#define blockSize 128 + namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; @@ -12,13 +14,67 @@ namespace StreamCompaction { return timer; } + __global__ void kernUpSweep(int n, int *data, int step) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n && index % step == 0) { + data[index + step - 1] += data[index + (step >> 1) - 1]; + } + } + + __global__ void kernSetLastZero(int n, int *data) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index == n - 1) { + data[index] = 0; + } + } + + __global__ void kernDownSweep(int n, int *data, int step) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n && index % step == 0) { + int idx1 = index + (step >> 1) - 1, idx2 = index + step - 1; + int t = data[idx1]; + data[idx1] = data[idx2]; + data[idx2] += t; + } + } + + __global__ void kernCount(int n, int *count, int *idata, int *indices) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index == n - 1) { + count[0] = indices[index] + (idata[index] ? 1 : 0); + } + } + /** * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + int *dev_data; + int rounds = ilog2ceil(n); + int size = 1 << rounds; + cudaMalloc((void**)&dev_data, size * sizeof(int)); + checkCUDAError("cudaMalloc dev_data failed!"); + cudaMemcpy(dev_data, idata, n * sizeof(int), cudaMemcpyHostToDevice); + dim3 blocks((n + blockSize - 1) / blockSize); + timer().startGpuTimer(); - // TODO + int step = 2; + for (int i = 0; i < rounds; i++) { + kernUpSweep<<>>(size, dev_data, step); + checkCUDAError("kernUpSweep failed!"); + step <<= 1; + } + kernSetLastZero<<>>(size, dev_data); + checkCUDAError("kernSetLastZero failed!"); + step = size; + for (int i = 0; i < rounds; i++) { + kernDownSweep<<>>(size, dev_data, step); + checkCUDAError("kernDownSweep failed!"); + step >>= 1; + } timer().endGpuTimer(); + cudaMemcpy(odata, dev_data, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_data); } /** @@ -31,10 +87,49 @@ namespace StreamCompaction { * @returns The number of elements remaining after compaction. */ int compact(int n, int *odata, const int *idata) { + int *dev_odata, *dev_idata, *dev_indices, *dev_count; + int rounds = ilog2ceil(n); + int size = 1 << rounds; + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_odata failed!"); + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_idata failed!"); + cudaMalloc((void**)&dev_indices, size * sizeof(int)); + checkCUDAError("cudaMalloc dev_indices failed!"); + cudaMalloc((void**)&dev_count, sizeof(int)); + checkCUDAError("cudaMalloc dev_count failed!"); + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + dim3 blocks((n + blockSize - 1) / blockSize); + timer().startGpuTimer(); - // TODO + StreamCompaction::Common::kernMapToBoolean<<>>(n, dev_indices, dev_idata); + checkCUDAError("kernMapToBoolean failed!"); + int step = 2; + for (int i = 0; i < rounds; i++) { + kernUpSweep<<>>(size, dev_indices, step); + checkCUDAError("kernUpSweep failed!"); + step <<= 1; + } + kernSetLastZero<<>>(size, dev_indices); + checkCUDAError("kernSetLastZero failed!"); + step = size; + for (int i = 0; i < rounds; i++) { + kernDownSweep<<>>(size, dev_indices, step); + checkCUDAError("kernDownSweep failed!"); + step >>= 1; + } + StreamCompaction::Common::kernScatter<<>>(n, dev_odata, dev_idata, dev_idata, dev_indices); + kernCount<<>>(n, dev_count, dev_idata, dev_indices); + checkCUDAError("kernCount failed!"); timer().endGpuTimer(); - return -1; + int count; + cudaMemcpy(&count, dev_count, sizeof(int), cudaMemcpyDeviceToHost); + cudaMemcpy(odata, dev_odata, count * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_odata); + cudaFree(dev_idata); + cudaFree(dev_indices); + cudaFree(dev_count); + return count; } } } diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index e15cc65..df6725e 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -33,6 +33,7 @@ namespace StreamCompaction { */ void scan(int n, int *odata, const int *idata) { int *dev_odata, *dev_idata; + int rounds = ilog2ceil(n); cudaMalloc((void**)&dev_odata, n * sizeof(int)); checkCUDAError("cudaMalloc dev_odata failed!"); cudaMalloc((void**)&dev_idata, n * sizeof(int)); @@ -41,7 +42,6 @@ namespace StreamCompaction { dim3 blocks((n + blockSize - 1) / blockSize); timer().startGpuTimer(); - int rounds = ilog2ceil(n); int offset = 1; for (int i = 0; i < rounds; i++) { kernScan<<>>(n, dev_odata, dev_idata, offset); From 766cfc33427d50eaa83c343728a7d52886dbf842 Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Tue, 19 Sep 2023 02:02:15 -0400 Subject: [PATCH 4/8] Complete 4 --- stream_compaction/thrust.cu | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stream_compaction/thrust.cu b/stream_compaction/thrust.cu index 1def45e..312a84a 100644 --- a/stream_compaction/thrust.cu +++ b/stream_compaction/thrust.cu @@ -18,11 +18,12 @@ namespace StreamCompaction { * Performs prefix-sum (aka scan) on idata, storing the result into odata. */ void scan(int n, int *odata, const int *idata) { + thrust::device_vector dv_in(idata, idata + n); + thrust::device_vector dv_out(n); timer().startGpuTimer(); - // TODO use `thrust::exclusive_scan` - // example: for device_vectors dv_in and dv_out: - // thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); + thrust::exclusive_scan(dv_in.begin(), dv_in.end(), dv_out.begin()); timer().endGpuTimer(); + thrust::copy(dv_out.begin(), dv_out.end(), odata); } } } From 8cd0809ebe3dd00dbc78b17fdf6a90d65867e198 Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Tue, 19 Sep 2023 03:31:06 -0400 Subject: [PATCH 5/8] Todo 6 --- stream_compaction/CMakeLists.txt | 2 ++ stream_compaction/radix_sort.cu | 21 +++++++++++++++++++++ stream_compaction/radix_sort.h | 11 +++++++++++ 3 files changed, 34 insertions(+) create mode 100644 stream_compaction/radix_sort.cu create mode 100644 stream_compaction/radix_sort.h diff --git a/stream_compaction/CMakeLists.txt b/stream_compaction/CMakeLists.txt index 567795b..7b34ba9 100644 --- a/stream_compaction/CMakeLists.txt +++ b/stream_compaction/CMakeLists.txt @@ -4,6 +4,7 @@ set(headers "naive.h" "efficient.h" "thrust.h" + "radix_sort.h" ) set(sources @@ -12,6 +13,7 @@ set(sources "naive.cu" "efficient.cu" "thrust.cu" + "radix_sort.cu" ) list(SORT headers) diff --git a/stream_compaction/radix_sort.cu b/stream_compaction/radix_sort.cu new file mode 100644 index 0000000..86d1b32 --- /dev/null +++ b/stream_compaction/radix_sort.cu @@ -0,0 +1,21 @@ +#include +#include +#include "common.h" +#include "efficient.h" + +namespace StreamCompaction { + namespace RadixSort { + using StreamCompaction::Common::PerformanceTimer; + PerformanceTimer& timer() + { + static PerformanceTimer timer; + return timer; + } + + void sort(int n, int *odata, const int *idata) { + timer().startGpuTimer(); + + timer().endGpuTimer(); + } + } +} diff --git a/stream_compaction/radix_sort.h b/stream_compaction/radix_sort.h new file mode 100644 index 0000000..27baf3f --- /dev/null +++ b/stream_compaction/radix_sort.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common.h" + +namespace StreamCompaction { + namespace RadixSort { + StreamCompaction::Common::PerformanceTimer& timer(); + + void sort(int n, int *odata, const int *idata); + } +} From 235165a279cb2bffa7947335c16214c12d267c6e Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Tue, 19 Sep 2023 23:52:31 -0400 Subject: [PATCH 6/8] Work backup --- README.md | 21 +++++++++++++++------ img/README/time-size.png | Bin 0 -> 68130 bytes src/main.cpp | 2 +- stream_compaction/common.h | 1 + stream_compaction/efficient.cu | 2 -- stream_compaction/naive.cu | 2 -- 6 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 img/README/time-size.png diff --git a/README.md b/README.md index 0e38ddb..3e52715 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,21 @@ 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) +* Zhiyu Lei + * [LinkedIn](https://www.linkedin.com/in/zhiyu-lei/), [Github](https://github.com/Zhiyu-Lei) +* Tested on: Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (CETS Virtual Lab) -### (TODO: Your README) +### Project Description -Include analysis, etc. (Remember, this is public, so don't put -anything here that you don't want to share with the world.) +### Performance Analysis +#### Roughly optimize the block sizes of each of your implementations for minimal run time on your GPU. +The following table shows a comparison of run time (in milliseconds) between various block sizes for each of the implementations. The run time is measured by scanning an array of size $2^{20}$. The block size does not affect performance very significantly, but a block size of 128 seems to be optimal. +block size|naive scan|work-efficient scan|thrust scan +:---:|:---:|:---:|:---: +64|1.6761|3.0861|0.1686 +128|1.5749|1.9997|0.1480 +256|1.8605|2.1077|0.1639 +512|1.6586|2.5638|0.1679 +#### Compare all of these GPU Scan implementations to the serial CPU version of Scan. Plot a graph of the comparison (with array size on the independent axis). +![](img/README/time-size.png) \ No newline at end of file diff --git a/img/README/time-size.png b/img/README/time-size.png new file mode 100644 index 0000000000000000000000000000000000000000..e56dbe4fa6669a132c5502fbc803efcf4417a807 GIT binary patch literal 68130 zcmeFZXH=6}+cq3!#Ct?#MpP73ymh1(>C&-Kqy*`mC{EHd$gA2{VX&eImd!f{ zz@LA8pl<30gNeO`{@cfU*K805+s59!an-=fYKD&Z$D$`iWDWlzJU*a8l>UHAcB~fN zravm(@t1Gny^bZ@LjOkp$p>8WWQ)e3B9!Mn{JXz&h{Gqd9*O?T{Px}8r$-zgu3uDs z8Ts^v>3zxlrV>$4|C!wXQ1F*WkAB(z@83>F3Fa8wQeUjxI$^q)+uf48SzOFtytID9 zkI5j`mpo#cP{rRTy=t}Ho`fN6Qkj3uPy#t>6;`yF+z+l9S^DZ&6u(wZTy%+CR&ev`bl4aZoSd7)cWsIdq z|6f-#J`%8+#JS-rdwc7$O8UxRSVsuc!K2+y`-+KbT>cVLx6d`zZB_&}oG{J=5AJa~ zAl|fa8g|>~tHLwAgRppA(aWYBzumti`u21~XRZIneS2G}1UXNq1@av6bDPJ)84-om zCTbpnp6j6*fBHYRWcKF49T(-7!}(^Vxn*{CVFI}7*K5TiB`E#blb7YR-TlzjHd!)? zOfap8>L%SO7)+dBIKznJx2Zq+5P|eh?pp~mE~x5FJ@1smiqbyMsy7!~OU>WTurPzOEgHT~=5;ra6yrHfjXXp6WE78O0&86kX)(R4tC>E{UW1 zo}HD$pB{Q9e(JWA{R$z5bi#)FKNin5n7ZLD9vjW0lXZksN}_s6vtE*pRPDjS|1Lf` z7CrjA8;^_P-{ME75!c;FZ8Btg(X>--e>5RsL36FXXRt?Mmoo;0+#CIuy3~%4Q{Ie) z3N+SsEKRBAFp;By#gnPDkSOH(;sn`m*ySh;W>@$9rE`gTJoQ0#haGxxJc~na9X(DB zXx%Ag}UFxcCf*wrf~mHjuf)O24g>tpyOVMhtYek$Zb2uB^B=@v}*w(}pM`B$#< zk^29pKds+x>U&|503RRZ?5}j6mQi}59*{)Sl01KQBYL#4wU2_qc7pi!R*yenP!{lUtl28(gC+>e8uADO7%#&xY+oiQ@{qΠ@hm)@j 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

5Du(_yqu_X2R#9ayj z2Q8H&xTyhcnp62N1$D*LX8WO4`SLrVX$x9Z7LPaCNx(Jht*GBG^w~B-2IL!$g)rHM1Sz1&D3S4iq~E z*T9J}jIX^5(I|}WjVPGDa7}Xl=wjmHwRT;F3rV*{nnyA|4$P-@xRodG3iAKVd2ghn z!y%LY4mY}mz}hF|U|m^fKUF^Err>7w1O%VFEol%aA^uN-3}Xubk$9-ngVv+{jzOHf z%DI`^=KE6;{R!H(En~!8J>O~2y21KQR+GUTeqOc;QNwH~F&CV>XUj7bLe6<)FUDC|Dq#&8~|aamBmk#pmM}0W!{V zYlQf)8gF4Hw&Oh^X*56?&PoX_x4fS)1bToaFVdZEnh-ey?hgqBU)b%!x37! z0XLSqIPs+FsZ(qCwL;dqr}{gv{T_+AxE0D%RofvN9x(r}IZK26x8b73se0-5=+Pb6 z^pCN00F5Kp&9p^)igFn0#ID@-O$t9>81iJ7$f|5sEdD_HvW&)4{Cjq}dz35A9=<1GBf!Le@ zw?lRQw`HQMJ4R`@#I3qsUiq9%r&?V$Jy$(ib=XPzIooWo7#DepBXn=}5h7)(I`3u? zBpgbp+b3n0t_wMMOb|!oT0p*E*VOMS5Rl)g1d`goJ*5h(rwM!vj82Rut9|EqlGU4N zW&35P-`-_5icu~u62kkis2pv$%6x~D>}fqSWmBv0XlA{~pvG%ZmIxkb#aNlcohAT;-9m~2j`{!_B^0D2)-ldCAwMs)zdz)9B{kx%n2MD1CFf?zhFD$qf}h%WMslv zHlKox zfj2ihZXG|FDM?=TJ)Wr^Wccz5PzRYLAG%NL<|^8J^70N)TfI;V4~e znlZ(dH#JTnr{?2}YKAmhCA<5-7ZE4YLaVEh0hN8Y)%=O?z`#xr(HuC3Wi%PrK{2~fPAby~=; z$zEUKjbFLrK9D=gUGO@D+Cbg_S-6_qzlD$?+fUCrP~3#O(vj>T z(w0FmfA!be^XHWF=90>Z?(;`~s`~!Zu$WPEDxB_={GXz}FCS2b9CyT#tMw~t2i#0^ zd$corA70BEFvg1m0;vmfimxY|srji&$ws|BE)M#r1-Iu$Ngz;(s1(2)svN=c8F-El zkA!iNE#;=CPwrsIYJrvLZX*nS8KKpSJIwn;-KdL~--$6=l81pGW+|We_(_o774Ch~ z(S}d989HxHUViRCS0o)|kWO(LAsKvd~EZ$9_DSc05aZ zCau$+Fo}=Nzc;O7bF}zLH#ISuEzqYl=Tg30_xb*q!tOZ4!6`|&da;hs!4K055av9!W5KTP~X%FsJ*NVX1{a#7SW@y z=}eA`b*9OWGJnvp%|-Lp*AHIQ+wLIJ&1~KIqu6$S@`?U#SKrYen#zuZKfuQs;4YpFF4IbKS{i?#=3q_onMkZ?(pkO=+fg}7x?y__(61B(6MUO8Yw6v zf?l#lf`;43iY^V?)aulvLLBR}=MZwknZD}zzO$Ev=6M+F?n_833mQf*aZnRkEOg{- zw%`FuuQil_oqiiA9;$j0CllTH&quteUO+eDOgmlD8AFrdG#8TN>Fct$Po#-Cu8nUQ z3iG}6O7+raTA3A>FBEeaV(6!!$4=yh8IcI7&y8M5CBzlSq7_F$>UWMsW%eqz;f|;I z@@FqiMd|a`i^p0O3t`(8jzoTS;{CKcC3A@AzkbW!Pg`6$Y4j=iYocsKojM{)roe*LN` z?jTFM-ZfHcy^%v+GPi8>IE6FTaO)v2S?G}7bI#p~nYyK%5!YF}w58XohG;U*d&`8` zXQ7z(ZMU}P8a+LL8a_~S&*8f6H!qt%ERD%O9@f{pZ`*m`>5x1vB4f#{29!9#OC~^yFEh#o5*4H&>)& zw=mIUk~x-6}g9w_n~o#aVbRSv~nBv%`GJ7nDh@`rU{)L&hIHIiIF=wv7+9 zC8%|5gaXjbZCwjiQwKnUo~l`cL@+K>k2RSOd#ZZW>-B)*9`7HypFcL1)$fjKk+bHB z>!}q#6++~Xven=7AXtDY_TxE^+G@)N)Pjo5M+J2C;J5zQ4o)6oR@v&kifZmxQkUpL zU7gR|l$!MDpT00qEKqr0XXMT7^*JA1S!>fnC&E`*BbxkM8<(Fuq0`UlK8<%%4wxLa z{w24bI{j4AO89}lol(Ybw|cEo$7lL9?tJqnIr*PfnXMzp;<0YNuf>?2LF;mJ;-Ne-qtd@yFK8~aZ;ElewJl!!AK8Ct*t2Vl_B(Y=%8EUscgA^&C%6ldK1RAb55;RCZH0>>7 z8YF$#PPY-c$@diu;Q!?$E=ZF(76v9`TZnexwoSmJU&yU@nyOsc6Rj_Mk;!(>=Szux zQQot)_nZR(5Yl~#+ve6C_p_CTb0T+Mk~uj0=XE|34}l`JRoNA)JF_-my%sV#T%0`8 zpiuG8%-JA)fuJwynL5~%0}E`9aYuZPRIO=W9N*WhI{1H-5$1LvLgC&t?g2tG)TS6v zUp}#%TN!^C{ak0SS#914ypMFpmE8k&Ak;%bAeaY@fO|T`zqheQA{~cNuKr z9-MY2>%JdsuWTd4liZg66t>YvBNx9U9ZFDsY|CH0s?~mq-hru|Z~ZS%E(&KS2p=7F zbX$)5+~`X)9@KI9Me@NA3xx`a2NsI?cbKOX*Who~-kK^vJacbn!x?zI7l|yPtcxac z(=U7;d87Riq*XM452Xutdnf^tSOND4E-4EU&Iqe^I)UR86Y)FTU&vAy!d!7ZPjnX9XX{dsX>HBp#6`l4+?tHESn zUeAKolSF%hK&NP@WlV&mpv*{pj}^ZyV_o!ZzPh}pkqcS_^#x0RK18AXhUwZZE$^Qv zi%zVa2^>&etDKs=L|*&j2z6TBtTRH{ALid|(i)Qk@$NYNmT}?xYX|Eh zSLjJj-)!A9Q@gu*YMEIMo1WsgZh5&Pe9Fl*|H2_It>^N2+T2EEk({Baa)Ba$i{}p3 zC_ZB%Y82#$Oo@G3ynnMFAMPV}A(N&DqVhCdZQ=CWP*TgsGqqlsD?a8%RoS|?6+esm z#$Wtqns3QaK)vx$uIV|JwYYN&fu!%2*($KSHy>H=>%aIFW=N2dqUG#?8vl=GV9Nr z+ia0dLG+V)p5s2c9No-pt{;gwwYj(WrF{Q>A4>sW&4-;-VLCaD2O6%!G=brX`q+6uYI~fi8Vt$;|7Vs^*rOa_(+tXrg zmPz7~F%!<{M*=yeNT``@l0&kyc4abLy=8({-!E+L{ql45Vxh)kET(5duQc!);%e1d zCQ}Tl&V*31gq`-96TUdm&*ZhHQVvNiasKF%@hTg6>f^KtRza57t1s&o&t}-Kt=RBE zK%|*4sF`XM@EE}(&~!=u3WD5$6b?+B=PNhoo*xzZ98eRjn4=qNU^Ym9i_ z$-NPdBTI+X7nqX(9+BFHMs2n=59P_;@LWdmCcHL zeN9p~FGNc`On^8hbYj8+(iMTVhPw4Gtl5P&$|JVquVZuOPuHT|A+TX5#Afpsw|U?h zB%c72UVW9#G$&T$eLGvn5g!8z+*gF|EuwO@!H#ZxD-i++G2Xo?u@f%9umy!bxEk$x zgoNrze!2*JlIK+GcfA`HD?N`XeDp4ss%dMg?@Xk~O$Z#-w z6X@=#KN!%fxODxLhb_xTdCe{9EtTlsPom#_8JY~9_2xih~{urkrO>D|Bu;gK6DUqu3IL;DNwnRxZ2 zE2`1STw_tPnE%RRIAUusUi5<@`#@XzUn<#W_zaFz#<90u5{QeCKdqn?VRJ@d>SUFh zk``qy*MW5jM@N~nfmQ45SPIXyE;sDO1)0omf?t@g zO?6dI*M5IA+^cR{L1L=8Y1Mpis5#7&iA8OXYi(&Lximd_%4ym~Eg}BMtyTK$tL&-) znrVlMR9~}82kz%G*nXBy<@<8A`OBw5Ze|rD#HVpbeg@mIp17Segj96qpZ2s(((y%3 z#`3Cpgr4}ag;8OZ9qX`7bRx)y_UTZXb#_zUjubu&+y8S&5(67=A3 zQm=1P@u*K)Ja>rCN_h)GoX22Ies_w^AEH^EyO~;PG>)lW>iMNAlyJDco@hrSRg(vr^-K2Oj4r6APNUu6(urFP<`&pec`S5_?kCe9ooJ*qvi#PHtIv zz8L!XFZf3*l9@!0LG0y#>s{m+(&J(AbF5}>S+Twq&<9+3FUgjonJ-sH zy7#JRxfVZS%RS%_5t^rc`pNF_tLGCR;@$>uuO7h`<@=gQ-u`7~w3aPm^!emKTS;M<;`jzG5xi9y1Jnb!&t=)?+ z?O0+mW^NBl6R%RPv#(?vfvw4Ts!3aG&E*|-WI?i4f-~9Fn`!im-VLMCFF0P2p9`m% zb*YB5oyjM7h|T+sez9pnn9gd)=p9{K*62^}?+cz&xV@p@$@9c16Za>nxIgFu$6P$O zmXmsrr9vBpaZV98t5}1vN^pZtU}2SDLt|r6hpVU~tB+VVezI`(<7g4X``EK8G4BPZ zWxw9f6gT0p*B)gSyP({ma^Of!Y5Hujf}N1ZA_rIHO6)y4=OQ1@v`SNT(wCWW2LJD0 za0g^tMJFblRn z5a)lTQm2D0d^jLe{aSgn`NdM{l$P7x{jD8%Kko^bWEBuvQ9>8#3OzVnn7X^T#NTUv ztaH9A*uiQcufTjY$6QcE^$!>3`-^(ktBYgB{RS3)ma)grO4bqwjC6K|^>@6=TiHJ9 zJYGFtDtGelNbNr1%)ma*tVCZtklgD>3wWW+qjqH0Xsam)L|b6#K0%?b;X z+z9ydZMc$yl+e1TZR)f8w(LIHL-o}!a;xHy*3urc+AE;$@bfFa??$Xdge-o7 zj9$kW*HheD|3cPe&$SC&SNHKg{@KM_CLSbYr|e(*ikw+-G4iH#dE2~q9;!dfRKR!2@l*CMUtjLv({A0boW&T{wR=1RMbCqm| zP7J<*lk$$iVcr}HoU%KHTbVM)h%|ck`O_Nk`Srrdoqw!& zYxc*Dvema@EetP`xOa%Wj}W9yuP*J->7pdo{Ne2G%bs=zt4eye)$Ii60oYUh8z$zU zkbA>!fr^JnEdU;wQwA44Bjv}|@9*z-s~ig}4y}r>Dx8X>o{KxNSCwOEbj;?>lxeG? zTSVCS?(>DeWW}E@9=iP zBaQaoiY6o<>%S!G?XmnPTj?I?`^`$dV~Hu)>7447&zzf%9DS-(F>|uwLqI6f3`&G* z0*d3Em9;e*M;c_?f^eg>fM7tofY7|!Hqq4rKMALP`MB6vrT8=Uabn3%9hD2c@krer z5cNGK(~H#IF9*;Fr^pR8Zh}PKnvU&!nqrsTzTlo37cSPWm=={}Wl{iL1WmM3n+}^g zsW6f&v9rhIFnmh-h81WQ+zjx0b|~QsPnky0M1wN>p+H8FwBe9^FtwIlF{vk;#SZ!ZO)V3Vn*5F@zdvm}SX zP{iNiCJb+t3qAxyNkQX%OzO~)B6ih4w-C$F?$6=*wyQq5X*~KeXwATT^Pa6^OWf^T z4sgbjaI5_m?;6aNoW2@8OVos;MvD9WTF0J`0Ep$O67+?q(9rpEu zX(4(e+D_}hiYoWlfE2FU(E$FQQ&&fGP5^#)HI?~8u=YEa%k1nQJbcuYMcBA#`NLl_ z8B6aEK;Wi~C&y{@{?DOT#gME-Yw!PFM^=0A#2SwyXnx^G28&xiNkR6r!z~u#}Yw zv$SL(*IHwM7OLk~EnWAVtX$}_J7e-cv1$FWm-H#C>pO8-KebIcm}6$zn%_}|exO^o zCt~lBz-sRSkI#~)l84Xf-cT&q5vUI^kyong2O@=zLp|Z5W96KF7r@K9N?oiy7r)=9 zO5=b3bgmuds%=N=hJSs3KYp4OUx}AaMCad}S>(DxO#ZD9LD~!Mv({Yp%bF6*nH}_O zfckJOO8$B_ScCqo`nI!a9;&a+jgpv?IckwU2lA^Y4+P8Kq<_+o-z3|;%i-AVK7aEV z?OdtJiu}bzaURE2G?GhWGP(VvZM^##VN5ovYJQa|=qiD8j;?C@vcdAQU8^0Xm6E@{ z(@}{w3iNy~%HJ>M22K#B`~Lbn!G<)`k}>pma*N9;z0Z$rCZo~DYd%CV6=T-9kFWv+vw@UrErhzbQ5|G!Lds9Dt6?WnD@$H#GRv9QbY`VHOM~Wsc z3IzERI3bo^k(~3UWV=Y}-4)CAcH(F5T6#Mz8lH7|LB=$u8z2iQPuHzX=VWbhK%Z4p zl1z5rsdEo^I*fe(p#2gUUWLWZD^>zVsteM-$Bo_Ii){Q(dT!6deJf4xSeWB&o`_se z$!BJGtF=v{j?Lb2C!lausE5h#WX2KVsWP?~wZ%wgLTG1fdZ7KW;-?(et<=oy@|>>F z!^3Ipgi^~n{6*SarlB`q_jV6F6gvo=(Fp!bZ!rgC zs`K?!*YS$e+>c`8anLwW;buO_-s#`WhYQ8bQ}Vv?%-4%o6ii+-aOs{{H=`NH_O*J~ zpBJv~Q&nv(H}?7-7_o0n6MQztB4T~l%u_9uWUCs$?@OK@=>9SqDmB3=D|@4DnCVFe z=kF+BaUpxBi|=#~E^;3wh@yyA;jV-xy}uHd>J>uQl%dNggFih8bhxj7`vqsXB1C1N z8~$t28SI$fTnMnf`D1K^sMzzQd?<|n5qjE=t(XWXAcs}OhkCoLTy6Z=Ez3dhf6Pj; zl(>B5C!Am|)c}mCc>s> zOSz>jx!~zF2--mR)@`}3W$_I7g^`LW`w58C<{w_M@XRZWh1ap+Dhk8QO>1POyykCs z38c?LMvz8(?6z6&%#QJ>Y*`;(v9i3;y8np=@HV&tE5oIFwpA_3qWOW%J(gdvRK!KK z*zpXPiSlgc*=;PsrbL=MO^s?wU`J2Zok&!`b?YIUrBb|IyCtzC|x894Z- zQt)|jncAKs?+4}5{$Mb!0%sNSh-!S7&!yH}yt}_`@yzGczqSV2T-B!A7UiO_``K54 zys%VD_%M17^@4@|cj7nu-ZbZ`xRx@$)#Na|#i~TDNsn8x6uQ6PZA)o#pdWhkT>VD! zQM%!KJicyy5OY&R^MYf+&O_E+zfBDD)05{Oz)~{5Ke=_+K=(>n7M#qn5D2Ltxsmj$~6pa{~_a(O-a_*@F zW9bLJOHcVbo3+da{`vzFkqG3RM4QPo!>vIARpPn5zRTnDrcCOy-`VPSn631u`%u63 zfF?81%2#gynQ_6q*6FGSZIS%t%VqB47NjL?g}~~~_qF{+4kZJPvjS^BduJ(E%1_#R z-^MM+x~L~sK6JqS3ZdpN5GjoFMlxrgHh8suR&)e+blhAz;XO9?sMkoHVGtP#xVUrvx=h|HxPIo-hz%8{#w*hyk^*o)-mn2VTcBCCMmo^9@Xho|` zm>mZO3gnrL4qU8WKN{jNj~N?Bqv!szVGTj zSC0P=SMrTEy|ItNPViUs#{LR&SH0uO49m}uyZonCHhvy{ zUAfMoekT;mkYpjKC;ABcsfJ!2U4v@9^jMk9j^{jc)AUVYHUuz~{Ae;Lp#fI;Sk1ezLDw z{(!9f&I5PG6*&)lm*4ZwbeBMsox%f#w`!-W)b1MPHARgsp4n|%Pk2K9vwmyn6PLEV zO$AYuiOgGfC3rPHGhDe#FJSbjrJXK{COBPpo1lOXciJjZ3AH@-GHVqK1z=tjRH)B= zs~dVJ0Pi)m@m|#*Y2thhd#X#M+Fa1-o_{)U5?X31@T1$1QGVvT%=PAS4nt3Ejc+E{ zYE)ES(`6&7yEeLz#eiR16=258w zao^g_oTDX5fl;ytnB8_2rCO?QGtJ|N?D{eC=7|cfz)>{arJj1Uz!y}Y`&(v~NU!ag z;e0%1_j}+!f8ybD_-X$|l`XZ-*~~6{^D5g61uA}>vr9fqOz)h|>G+2vY5y<~V_VmY zR)a2|RtFVk^S&NAZc1D)ZM177Ys+dSZ-K*TNuATaEA0V+L`9o=RzAOatF%p`ZHB!s zcH5JEf`L#<6`gx<$<(Ki;hXf@;y|`vKZpWz?_jPkG*1TK>q*#ca_Y-mR~FWTtI}=O z`0Bn^zOrpWQGb0-yjFTXY4DKgPTZV}*M97-sg;UT><#eeH%zbq$| zqZ%wg-}BF2sXod%Jy$!%p6HsT3o3wh_1lhek3FKPn3|dMjR;j~uVSJXDvv{R$m@@TAkMwH}=h*8~r}d77(Wt;C0B)?o3U#!k z9ZC{SOIrG66!e6K7;kqLJkzV4>x+;Ysm72X|DYw;n>XpX(*BV*)4w1OD%|?O=IlZL zYUpVgZn-yQKXeIJh3b)QGOMRWA*PlMPtmN$RKdBW5$tOC>`2G5-IbYkEJcb$hr>0b z4eMWRj5no}*N_yU@3Wh|HbP=4Kf=VYRJ8WJ$Iw%kf$uEHJJCTzqd^hP`asBuFUV7O zhlO?&+?AZHm{GSAbWe+NsVO-F`VGoBax|`UWXITBS~gYeDoG>gdyR9>SB^YKbGDkQ zKp)2CoC1xu>6iGfZvDdx+)pNBbIjuNpIl1K{er4QsO<*nG{ANo;*G9JdBH_L(18hu=YaMm~ zGs=GTOf>o4%bmx~D^EOpkn@H;<-xDaf<`!-xN6^kRr+PF`AflWI$g=ib6Mj}W9r0` zxz1U-HB$J@`}kuFe6Gs=jgPwU%h@MP1+cx2f4F=R9}O|ab?s76mzkT=zo@-SBTkzs zX;H)As?J^YWs{QJ5*f7o+RB+E{wMn^+-4fY=olL1F}gC2fbtqwtg|yXmpC)rlp$i> z$my1u`f<>7rN;CSjlaoUe)1|WtEB65aiWv%%kHC&6`v*96!%Xp4ulmOM9v!$+9_{&n9| zXIE8MAm>_ZHlMJwYI866L9npx!_NWk_|E`068o6$7fQ=we{QgM9tFX=?3g}|{=)Zr zsU6abe>a3oJ`Q``N`G>92-HUJ$MhZ2Amy5a4+r{YoQi zb$kKqEAV-02qh^aRx}xLrv7bmhKDYOk)9%U(TJxEChsii-VJVaSeY< zST6eRCukcFT=g6+d&NrX(BjUVOI>*NScF_;RipB1??ARujYlCis$^EY5^^$@9>#cm zH2jy^|DvSW`ZCuxi%n*cbnI9TjE{J`1f*4==_G46}uv0xS|-*eOH<=F5mS}3>D{~zZFqbT3;|ko_!y- zYS3>~geJb-=bU^fY{bKd_QNM^a`H@KqfcqiHu2yldBMK{04!mmB+}iNb&v<3@b(uV z!EwLmb~E9z{7pMXf0E^UZQmM!SGuf4Tw8xZd_CXAo01A~1`p5C3U~ZT!(KDDiGq+ta$UoCrT6n2t_RPThsqv^{OkCoXNCd&&!hg9`jERbLp%hu0kMAr z`2_<<`&unE+VpLCJ6nWQGU}{>h@`q*H&S0@GZ(!$Gk5#;=Qq7UpZb6SdbX1Id)JG{ zB7|Lb0@5u&T%eP}0ovj?anxK^wxL$Bt7+v=73L=y(n_O8KY)gk;z9|CsNX(K4dZ3i z&IJtBd)EhSTnILhH_29gJC}I6PBI$#Mf?5rgxj;nd~i)#YhHbOpc&zN0XbC>QN!m< z+Q>;5$a!KrXjR=^^|q zoZ5|;IrbpU-(LePs1Lcj^fezI8)W;ke6Vn`&}9O}XqO%jjypl@`~i?Tc}$X~;>Z^a zw6vM)&DKd?eYZaaD1-Nig@GyiLO4PHMz>CzFn*;&%p5tXx)CJZ5czGkGYh9YLZ0OT zIWw8TjW1TDD_S)^r+F1by8ky-(-s7vkzVD^VOu_w#`HQ{S9uez62{Bs8^eA~Tl>2! zrdfXP`n(65rvHXB*l@wIc5o?d$%@Lh;c;OU2Nh-Q{ODHZ*0*2AD*KU6<)->@lR6+1 zkv(&x@+c6B&FEx{ZWRPL3$^XG0xG_}AHoSy z6B}4kUx~un>=w>#%&xjwsS@>V7DxQywUD&aGpP3GZ1>bgM6r)z+L({a&GcY(%w=#u z2=&oy{A0cYKtAgx$Zixr7Dc~n+-e>q{xZOy7+%~w zHJD-N{s@7&;4miLwifBBSwSE7DMq5J>>LljyqOU?RXw%5eIZWf(g}gl0-}-1&&JI( ze(1Zyn|mComX2t0R|5ilPiixRO$Pb1VAA~^BFTcE2J3H9OlYJVd6TiCuWr}$E zlfT73MD3KZEJm`w5n6xRk5N7`iY&jI6b*HC@33Mz_h(yGJFNLbklH@(qir7}ZyX2z z{QVm8`o(WWl}TO7+nEI>(_8@B+gna^Kjsn56#Non3AqnF2L;T0cMJm!HNh%w7Ps`f zQsaPm37#=yB5Dv;PX6LM{)N&n8p>+(#kXl1T;q}bAtb(-rTXfAx{y_30NcW)(mS3*FFagenguWdy^}s& z``(X}L`!wsksDo8eytt{Wg^AXonn4%cePEGKjb3{!zAwq9uZ)V!M+w=@Ul0DmH#>l z`C3Walvu{_RO)_dHM(Yr;PiObAIo6TyMTCSq@nYKe%B3Nu%tzr2JpS_8pGSAHLVPZ z=7znSdZqJSud$@=dx~!lVMqV`qv(akuhxQ-6JLWesE)eb^ZFpWW)wtM>=NZ6M)Gu` z=mNzN+1!vum^%s?!sfS%s;X(A!ME*hq{Gum#r(99a>~-d$dfWlY61EO1OBj+_lE7y*V= zQhmLLeI2X|yUwfTLt5C+7wu7EdRQ7A)st6TA6L0{uH$N@TLF-OL|M$w*HTIVa}c=P zclV^_KJe`3zhNo-DGj6$`A9ABqTHfga)t*if2IgY#0F9YWQ<$woyMepFEaJ_4qKHQ z7^n$D6kD=#R!F6jc2LrhpW=@JIJGquHMZvu(+dc=Z=ep9D{&Z zDCbgK>*)_ckHyXknC?c6kwRy{{jjWsU5846!nTjK0VS||EDyV#zumK|4Iuvsb|v^- zXH{LKsU(7>uK<=SY9VeRp0$s8Y*lAI>)B49Vf>mUV;VIc<IF4N!hhx!8jJ zgfEBJCnuB@^+b7GDLaU53jjUyA*=%tY5sjPhuWcCc{)-+qSzFnY5R4Toc8;+$`MH2 z0%aIHQn{*Cc_m8VIxC1WS6|$WInWYg$-7VM#(@s|s4LNlJGK1Kjm4%<(<;faEs_V>0*vzR45Cl*LZI&nZ)SAH*o-{z&3 znHxslWiYB_9Q|`3w0Kzaw0GZ;uYFlvv5)z1&tCxl3S?w5D60#=-9vr@uki9BycW&R z4!pmz^#HGNWu4CW&w3J2AhO$q2W)xGwf^G&%@j;u>1s#tuAOIirN73q`X+nV{ zf92A57HRM+_X4JTx>F>b`w9S*WRF4+PvOo~@xP+JkwCurDtOWb#m)D*8ZG-i+oNo! zboa*B*y5j4qp?_v-H6;q@k$jG(QMWdHy={QKdC!5Y;&z&(2Jp^5Oc5!+Q4vr8`{8_ z@>^GuZuKcy_}&z-c>&%D3Otf2%L_CmADd=KnD-~Z=??*$-5*4MYpqSu1I7T!wDu@t z8^~A`rk#k{p8aueG?C=@{7J~P=hmnR91lQ?gZm%D@6>{qRAWYx{I7>I2+y-W@y=h6f*i$ofF?G1< zN$7s2OSx8biraZU88>Y(bNn*s>=~Xtf`sot1l+#v$3555NrK$IRQjh0;Dm;`LB@zH z7lz-sgo>iuc`7ZF(t#x#7Lwz&y+TPa1dL4n;#gpRL~1cZyQEe-m_+^RHDjL^^K%Nh zdNH0xyFg-0C|a^@`qe8(-4rNm;RIzZ!hw(d#wiBJM>*_72hV7JzM1yi63qE+G@(UFke^Fh+bb74{5!NIMK^ry>KuoRT7k4d>Cnox zq`Yw*t66d7^OfGn-Knk`Dw?mKD7a@^Rk*po-6y71=@RLzMM-ppobIzImZ2C%bOydf%d=|}etq9x866sZ+p zW{N?ZP}*8!qDCOD8qx}(9t7!P=U$M{XT@LU6KfO3-_i8eb+k?qvXf{_ac2Z5TWKl_ z{?)4S#5)8$kPGmSumU&$W_u%DjHJt+e3Pi#BMmKEpPn@k_F1o*y7*|;e%xohXh;og zlf@hbyXxM_2jUL;5|%`ulMMs?$#C|dk9yshm-raU zEJ_bAo`O=wknvWJ7yUD@+ty7kR%dGBObN1Ed^GZ2{(fSPvX_>;p{AEo`L8^}D1gYh znw#O*J8j?{zVw+>8N&D0)?g!;AWCrDst~8M^?*W&&Q<}1Zk?DlcmR$eS*Dvxu@&h3 z>bBo3e?U2Kn3o3L8zK~t?o0CI0;cJ>4&ls4zuMt~YR;n37z^L?s0)KdU_hDR+_Fq} zQXd?U$qQ`S%%s*s1jn6=N6?{QYLC^tRt)na03RE{c2Sr}UWIHIX*vS$xe)E2RgbN* z6Q6Kwl2LqY`ASC$XczJ0QS^b!TiYyxJ}TGScsO>29EWnmsyi}$?f_rD`4Y^DD^qLf8ZPEsr(d0EreP1Q zn~@auh3)<%`d-hMA94m`@h2mIjFvC-IYUwk{LJrr!lTRYKncs?(Q}aIXN;}c-HH9I z=qK3Ih3ZoJ92DAhOS&ym%H*L)t54hhVz&$-fbp^;m;M4TXV1I&aw4`H?K5eKmxr0- z90MOjo}ys9XtD>YVj=FA_z)=QdZ9Z&TK(Er+tE*0yv&)Yf{4o1Cj#3sNA0co!$*@I z+*25;O=?PJGw|rs3yjZrebpayE2q*L5ynt({m)D@BU^(Id)efuHH(IL4={y6E znCz=Ti|;v}L5tsc@}lEeT`TNW?2AcG2i7N$^5~*RYJI6;p%j1nS8lV;*vCGO!OUAA z*Tr@ACZuWZkpyhEvsyX1Ax1`0m_!|Q+IN6s>B3aN6x;&ead2hXfz83)Uc}NHm-pyn zo~Ozrl2S$r);1uj{`by*WnP?Ya%nde}QW-%_ zp?5(f$(0xl({ctV6Orib)qEGuz}w%pQF~h-;$#e7$ngmKI&&V- zI=D_tZib-4z&PZtiL(7W5Z7K9Mw=<*bB_T)^{JtiC_a=bFmuY){Ac4`nDY{c{c{1$!`OsZW=Ky+(tkw)o(#1HtF&fGym; z0?>3NtUe^cSAmiepZM{f_fV!>reChamo2k^Pl{&wtJ)I18p za~Mop9bk>Bldcl3$eXIHJ}%yZ_viu19xn5^UoYTeFk#+wT_sn=ADs1OyZ?2i)1s<- zmuEeoW##ibbVIx&C{(dM6ua7nh$hC|_aI7+DMD5$u?U`XqlLzgrFlLzoV=Md%&}p5 z*2ZI)IjQ4#e!PzIr)4?X40chh6%!y5Y-;0Q<&9?(V}ybw>zLB%nn9*;+yd@bHV^N% zB7O=9KK^jV)d#UWJRPbyRv=W-nc-u!V|vd>ZCL*h;w*yRwr z`A|zYMM#c}F~C&P!drc`i=4ZGd*l1_@q#3{9arb35;|=i;Nz-74d>X`d&_ z?yftxds1e^+ulYZd$UzHZPc2lVtss7Qk$_V0IsG#Lt^tX9G7+PudXU?6h+n^;yPTr7v;_}4&z z>;y*?pgRm~%YWQ(t!lE~~7@g2c^_4ecP{J&pI+r)m|Y2#2khLEaJY#j}SDD%sE; zzjqNCg*d=#tZk zziu!;%GCeQop(;7K@(7qUu{0eTn*NG_Mb<=24!s25uweF0$c!?!UoujcjO=so=dQSy*ZR^wEm`q;*wr?v)k0**x!VU zOD3Qz><__WcW+xcr2JYLHjrG_-R;sHTmQ-S1ytW(J5OcvGQe>DZ_~CTQ!V-5)0X>7 zs-FDYh0*}j^5G3=wDx10qbZLRPy`&&mqSM^As_3P`vZ@r_7lvrJod|TiF|b4j>(QL zRsiHq@cY_Lk-n_@AHY2%`&EEy?Y<~yP_rrd9R~2)361{0Ok9Tze9fFyWoqQV0`Hw(5AtFMtdU(jx=Sd zpM1RXdP;+vpv?i5M~wj*E~twbwLJt~)_WZu$h+hJp^P`BY;IEo6KmZR_;4?^{5Qk* zALC%x_iz4I{s|=WF>~PfXML!FDxit2VL(%{Ybrjoy1`MOQ>NcKi6|`QWua9-6XR0W zNqIfT(`o;yUqgX{$%TNKO{w41ED@D}4iEJzUm+QU)j8ops9hkpLs)Q)>3N-W&(p0mL0rEvaX=0(d z+I{l(xKr~__CaUKEnqwSDaAG2+*f~K0Y%m>0ukxl{!R__(qc7`%t~!>g=GAtBj%}& zYH30vn|pY#K}aiG|)1W;&oF1CXcz(FEz z9;{wcZhVx2SA@c6=k@?K(22u?Lm7FfQg-&rr~M8(4`rbMA?oir;AQ+;Hgh*fQIen& zrDh);#~gh<4OoGVE9q|hiZQL`)i-ve%KUF8A2vOkx_CuuvHNseCdthAg{it1X!W~r zZAXPrhXDD4;t}=pX)u)%cxwOJzyLiO(}6dbfE6S%j03o&J$!fXjFve}|8+|tQD*SI zOQgIjRO<1Ms6k9y2=Uw0m|S`=8%kqFtQ{~b#~}*O83oyHc>$-dmyWna0P^?0ROHQ0 z)lCI@)(C!7B{&6DN38uQVPN{b_Tc6(%&q%wOVnMGex#6N>WdR7MrYHY6bD!D9e8Gp z9!i;fQqV42ULE(U;q+r$%~yZ1V8*avSVNs$kukP5qcLrhaUI60@&r7a25HvNBqFNA zbdwbUQ6p}rp8COK0DDI(?$+GZ45ATx%>1CAZU(ei*9smLjC1A%Nur#dameD) z@Wo#zQ^VYA|2+@D*zR6|4J%FZNUFMa3r9RLoF@~{)1WA2Nut?}J z7J-r_3t4eu6YUP8rswcUS*uMt3N(y@8N-h&=|fjGC8$iS9m1_if#H>JKLer3J27}) z;yh~g5^Q3iVj!>=ip{aoK%Wl(Qfzx5?_(g(p*_z6s0R%34LHM*HD?d6q)^s>sR{rV zWO$-lwGoi-e{G_(kjBu@0+Q<|HT?vtgJH_yYye>+DUO$M$NrQF=^Y&qx5jWXZs7?XA_Gq|3=IRq4CP(LI< z?17=;uV8Z*{}^EK7Q0NHr-!te{PJ@aQLx3&y$lKL_I)z93SS<({V`$>&^$;$)}ZF% zK-~mWE-CuQkoc8a+OU;Xk95l5TUB1N{DO<0sE%1liq!U#w5VYPVbMuEdUnQdK2#D+ zd(ryDO=*C^6C$~GK|?o3LvycqkI){U=TTDYVr%~v^erDI)V7NRpSB654N)&W)t7Oe zs-5*dX7yn2fPrSH z)chyu2k}EiW_GIqTTeqONt1b(R_>Lpa&YbKM{wvu=vOMEAbAZOSTg2wA0t+GjWVG^ z{2T~YqAJXczu0e>MUsX~8rY$OQSy>?A+Yk;2|wYIZ<48(l2PGKD?qIoK%0IA>!`kX zo;T4~vD^7^npacp`Ud*c?^5QcvLERy^wJy0u!05Itp=5_r^59KUcNs8E4A`2I4i5w z+2LAcY9P?%{V9sBV@Cdh)LJ%!V#VJ>aZc}u`#axq^=K7nt=LhA4wn$F?x?zM7Sqb- zv}q`w!>&_u5D1C*x4dY5qg1niMD2j}b*~<@nD+{X%g3w4iU!P8S*DdjLDXJQBML9J zFYH5r)N{)Dvzq1>4;6eNR1@NL_d~HrDL=Ge7s7tUvvad=4phb%OQTsFoaGN|h@Kvv z^#hKAM_|2dFAvSlZ@HF}w6T1fJepN`XDrX67m5XcD<1`W&_VXxLm5V({IJnNxb$liL+?h7yw4nwQ}1bMHFr>B$d4&O5hLn;nAvsTLHu{41U? zTIjm$3KZE-8pTxQCMZjAmxC}IF8htmxo89+G```e%Q_=EkYfwt@L>3Bu7u4{jSSVL zLXK(R{m%91Ag90y&(l>iYSj@VnId<{JfY<;fK@Jiv_U6}`B z(3kffZDKQDAx*eh(X5xp08{ZN;f2~-CKiXhKJQC4;5 z;oF+b(K4Jp9=$A6{qp^I5pi9RA342u7JU zKktEu4u8HEv6;z&{^P=B_&MkZ>ZyWI_x)cmHk{k>{@s^19YtE;+ALvRV-DP;y~RbOv?SNtDlD z_cYk50ZxuhMz8?6%d+RQD6A&91Z*=2x}N!b;Y{MID_f7?re&Z;`rWC3HPX@j!TCT@C_sk%uA*EvEr^rR6NX@ZvP-Asg?$b$>&Tz-^#73cUU<09x z@3lf}caS`NzaOkY zyT=3wl9N6>0jHEziY|D9fEB(-QRY=QI7D=|@%syyhW_gxnfNH?$AP1G2^rKGVE-!z z-34C7{g(@R_LEQZLiF?lS7Y`+faV#Vh?D>J^Z}>+%ZWh?@#PCsATaX;ZwcgBPJJ=e zqr_cPB^jO+S3pXJ-@*LL<~yi$K2Zq+H4<{TJ>%jMKuG1^)ZQ-6;H;#|e)xAFBfRd)pxa}ec$?e#xzELJC6^m`4n1RbGLGFH-iJ;$%yjq3jlH6$=ju?mD7x&^;S0=z&alv2;e=*)*_w ztJE|8^L+;+R2+b`Q6EHtQa25C5XVD6M=|!ApmX4#eEhg{)a`A2V%i#>#!97i-g2tC zj+y0p3N8kX+0iv28W=cc2fo4=Aeehm;`5){vq>gU(MXY&uA~>}o$cHc*{U= zI2p!awGvm2`ouv=3;2V8(G>Cx$t(K|y}lb3f@${O?RHloQ;(Kz>ogt{{MBKC`le?7 z;A>c;Si`@$cl{H15)bNBpVC(lQjL+{o-PeI%^ohD^Sc8gfnqq+>HE=EH9zrg_TYPi zm@GwI6nEw3Z~MSLd;4qCaV4hypAW$Ge;y2wjQ<~dF!!9lfx`t!ASAytv_xnhkJnFv ztSJO{;=pbzUQmN|opZQ8PxGw?Py>;qk&_<%o9req3rLRNKz)RM7JDVn3I$Ghsu9iD ztLiuZC)DH#vrt3^n)Et2k;2V@<^kUUuB@wFhw6SbcU{=*r_(&zDp>w5@Ey>BtH*q% zlKhtfHpErX3VWMBqzvDXv>Y=YHz{Ovy}y_n&%pwkp4D%?(8fjko)Nff!KFvV$b}XJ zh-|lqX|wkC7g#eSC<|E}1PVb`bDEWHU>e*=CUWQz!oOP7`PCJ(bM&rq_2AV?k$B{R zDZ$<$0`ELmGzHL|N#M@6hoF`=@n?#2l?E-K+LVQ;o|Mt8>DI#`Wcgzh!jo}!U; z?o{25)aQ?^1s!+YpFn#aK717NCIU0eYJ2q$Xd` zf`@6*d=yG8g6yBRWK$SQh0((?EuX9<`S*X#!l%n-s`R4;5)3)pd>^WbwRwFKjZbrJ z{s8s*utdD6x%2fIy9-N4w8Fi8Y>YD0gR5Dmb6d|ws4UrbXQ}=2svGVOybu8F>Wm*CD6E{BTdA`XwiPjK z4dzLVb&KS?uP60MY6#@<;q0kgC{6S(ZC8)#w9NG5rUHVKoOdE8g*0|UN46709}$C>Q*3L1j? z7tOyJZW*vYbUEW*@65dC8quHW(4UI4%+2~dxTTeZG#?OWrwT-{?MFnG9tRd060Zf%?d z$lnXKzw2hrAs4#E-1(R*cc?ozR8zUu5#m&5)rSil$2Rc1d&?{Jg7f(}_>PwYifXU+c6_0D zd=cxC=l|ZjUt%!j^sBw6?Ka;rO2JpfulC$6^`FzeyHkZ&O~~=;V%j6wrf*tMW}_J0 zrMf3Q2sqll9M)P*CSUm2>=b@!fIE(JGKH{~+QNRW?B!wIy zC>P3wf$EuJ?2GDGX`@Z#z{aLeEegT|EtT{@Ct$((==+T`D9fSM0CBsYq|_Oxh#-3i z3QVcDO@q1)nDUfpiCv(d&&?9PLa)W37j8c5!H%p9)t-z?BU>Nf6lae2cpq7IZu7Pc zWRUW#+8sL)Y^$-!0DTnS$z00uID)c)aYv+coYk9P)?q=)kt}}ikA-QY!?$sc^VPT0 zrZ`35yl2mYzKn^ZrQRnp`Nnw(Q^w;0S?9||aJwts++20S3h|hwqk^B3{e@KvN=O97>ao@Jbm3d$(jS{#UW_J&sAoIcmS?as5$$x9vSu)c z4)xX);~h;Sne_g@?G)RBt5jBVrqrz(Yl==C$;r5sk$jGQgc|8|G_Fc{kw&dO*b_a| zs$i6XCnQI;7dk1;TB&3f60g!K-YD}J53Koww0VmRn2z)F6JtlK8LoZGtBXgsTh!<~ z*7f_Bt^1?nD6bi^&r2`UHbw>1!^NB)$2P z+2A%%x2{%um)j9^t(X72CX~RBD^dU1yd~*Fa!^s)#ZzI(hgWn8gsV3W^R?@huDPIhn=R6j z%Orx!HOX?3@{QYLhOG*s?Pxh~r|cm8jj$)qL~3E7Y#0*dX|f+-w@hCV-nu4j7jFnv z=?-6KAM7of+TQhYfXKkPicQ*i`+$ChjZ>jRqzTneZ$IQS%b)c~vNcy2hskI08@YE% zqeh&~%h8P*c;?sDo3<6}yW?0x9$@_sHX;wiQBn54Y1McriqeOoO?J9mE!Y{Ecmr|A zyUQjh-mj3Xp4rhOx-=P2s1UKA=5MzxUEE7oBh;NMTuIs2?qGb8wDH!i2C)V>@B>j3p}u#?-R z%^XS_1xxAZgqZtr+mlVOKR)*@JC!M*NM=mO zLWPW&V5(nr977@mrq-p@ORZ3KKf6X0PO&qhX`eW@y8WSMFS%AxE;0b!lG(Mj# z$_ghV=6$?3hQgDO*yF_pb_}hs!#lzc58U*2lA6U`MZ3nvi02VW5|+WB0p35l%|A}ov$Le} zR1sxuf}fw-rIkO};~(3QJ*1DItu-r8nw6PDHu~c;Kj2Y7+>qjOS8az+4@^DP(%p+j z+ab;a42DlG%E-omZpiP1p2&S!(mJd0hH&`|@`|xt(^T#?Su8J~Am5Ph59|PwMbEjmWANrLS}>d}K`(93yoC;%-}})FUx&i*{2EgA;!%-aw~SLxbvy$X(mJ zwpP>tw0Jb0{QEnVt5YsohrPUG2IXw)S%p$;Q$0b%X)@y7)M!*Rk(?Yu_RYygUsht< zB;kL~qqay)+Z~uPmy1j*8ajK^J1$>NHTN_2>>$>7x47nx);+~RH}-eL(c)%M+&9Sx z*We5UVX@Y`$Pq;U1Pm_l6GQ9;mDLwl&~BvO&1rJwQ)GPN3;8Jd6NQ1S%WfSA{K^ZZSay2j&%#A z4%3L}sgV8SnTs{+oS=N;F+p!+)QI#c&hAJ~sYvPVWRg$gs>Ktk4@)VAXhMzl00)D# zP>Zlh$bG97Z{$l@=AV<&ysmA8ZGBbFnW~UZObA1_aLg8Gw3|vP?Y6W46NQDBj)8QQ z7QZ%jPpiT{=E+3-XJy+8GTnP#tIQCex$e(KOLrYZbdNY#B&#Qbx@8V|8>jg0(d;&} z^7yro?`Au?f2?-PMfX%HM@+FS8#QyLHWEacH!(e(3TGIk(hlXlSOmfqy!5LeqYZus@oQ7X&;_(R&sO=tTmcqYfr&4Xy zk8g~FM9HSVrbVAD{q~jgL0sb-VT)?h^O^z}Iek zy-D4q)kjq?t0T(_U;PgGg?Mlrp-*Q&p(0V?dB?H26qM{|yT|C^z+O`QLWOoy6Gqsf zcD*xt&4Cm;-t$(XTvTVVAiy9gw-j5dllAch8u{1WQ+DWybm3vJ{G+++Y6~=rB++qP zw^TyVk=*|8S+4=N5F6yZ>$iug)>=t_g~rf~u;}@L=hvt* zVfyJif=I;lq?(_?s}0KY>;>mRRJ4yt$gnZ>NhqGAdR14r^fF6LB)&apWh|Gmma8o2 zevojMn0$)r>pXL(cSV7ej86iB~V%upCb`>CzZCx-`pFSt@d}`ZB5P2zZM;q5CJ`M@hrCF$v08lb(38UwCnvcRGSO)B{{Nrh2t;i;$w; zAHInmM>Im-v2I(-cJtAhZwHu-bxWyP@pVU{=}Ol~E(Tb{U1=KCDle^)CXB`X{<3bP zmaZ}sMjKz;Q+jclnPbJBf5R)bk2I?zboMoH)byEe_A@o?MwF9yibmdkIKJw{QXis> zd}cM(5WI$b%VWGhX%ZgOJNFVox)@Kx&JBCn&q~l#bTfopufD8!hsq8{yrEIr(OAt9 zuO@$P!>Ntoo=RHas!GXSIu&fL>2}YWo@!(S4%--0hoqI&TA6F?T{?I}rpT&H{lCsP z=H;t{v|HcH#VBjL;+0-!t5zeY+U#f-%{eCmv1CDMmi2+bFlVc}S3-j7^s}r_d7>j) zMgw4>zz_dGz4HZ+Z#fPS{U@nxw2o-(q)`VHSc2KK8yJz}v|y)W*o%Fn2o$T$%lAls=|=64-nRfWIy`v) zef?4v@QNl2N%a|gf~tcB_?AO64I>^F31ji}yM#tOrF|cbq)H-gC%#-}9a$Vl+Whc6 z(>?M=)H`OTgL2ET^dmqfU9>NTTfU3>FjRMg3G-uyTls?l>1y_L6+s1++c z__^nE_*UIohcz#`oDwTG{SY&>e|ZLFg}?r9%>?r}x9`!*$7*)p$a~K(gHb66`yOK# zwl@b|zMu00gXY7moRGoZ7aocaVR+k|Rl-HmvQBN^Ghikvqc$89m1v0D?GrOSuoj$9 zp0*(#`20p5`eV+3_i&F^ph(<`PelcrvhIM~H^l6Ww=-C)JD4Cm66mw9(YC(#;o7L- zzKga}Za28i`V0%x8dPIfk-OORU@Z1pVy} z&w?9vi^^-dD!qTu^;Xbc8pP6z=}xMjy~ryS>~MWNrK)?xXH;~%agdCMA+vnsE@e4U zGGj7NY}Q0iumki)KKun=v1D;>u8OW~SAkw(;+&vK1ubc)TeesI1anZAZ>PB$9JA`< zncC5#kkL(~tL^X%HB^_o-Ag0+c6Y>&=dU}Azr?Hml3{w2?Gw@bnJgl%J0&5&?!6r02R+vuV! zmu+ea)%u8!hmX-$Sk{9PJ0+(=MfnApOD8aeY8RxsO7W-|=V_U1+ICyXuRPstX%bZL zkBYLL^(O3&^?V0(xiIA!{#I1fLgZh0StA&GJ1Mxi zUBU$tGg{jp7kGDjOz=BVe2sYGD2p>2Z5L3T#vIuykrrp5rlBV z*c~8J<7h%x*c0zQKh4^~NHI${TDB`oJk|Y(X0OuyXbA6=GDNSjnn5_!EqvUlq7Y|6 zOuaUS2mFqVYkjb!hd1R-P1x#*4qfjoN|i`dkfo#ZPbKm%)}zrk6uL)F3ld+DH+aXb zOqv?<^ejyPqJ|!b%i2+WgT4^``k$-Q9O9maFl+^@`W~E+oua0p zafN5G7l#!RP4vv_B2)bvhe;zO+BetaLw>sH=6-C9Sn2wuUeED%)a08OLKSy-1NsH{ zJ@DP2Ry2a)5fa7;;mO{nrAQtE1S_DAv6nnmB03eaul+AcpxG}?xV@|yAl3EGXMQFrBP=YR!CIPkcRd6%! zJmjA|FumlP9rTD-jK^{rnZGP6MAi68ZJME`Dbf{&3uZ2(!x3>c=%F45%-?~%!q{4d zj}x0-d$hX(*Czag@@BM}tkz#Z9D#h4# zqz{drJ_ za;|CL+5AUEpyC88CXIU8q+C7Y1Ib|xKovGM>pz|u z7}fC%lq&oCGaM{K+6%jj>1)MEqAqCgavTB;Pg_9ntK+D=jFQ~(+vbQnc1-kIbOa;z zD;in(e7BEcc@}p%V^0DSA$N+tlJL&$b|L7tP%CsU|MLjwCNYlYR}r2S`kc4uds8@` zIMAwfH;kScZ3=Y;T1Dl8VcPV z4<;D0vg_I5QS0f_HpF;>Qqc|&U`EmtAB-~m<6%XE89}3LWghLhyf}Ia=_e_QZftzl92S!bj$KFt&oB)mFB}nFma_Sr+{jnp(Ls zQzV;(KK}@uzLF_vg}e9S!Q+F1>PkO0M@?h{C%U0s`kfqbx zc%p4`Dl6S@OJ{)Aw?`X)e&Akc#9K}5`-IOLqKkPwO~AHO7CGdIOK3I!UG~(HS5pyJ zu*>%f2B_N`m(nOJ6B~zxl|FUlqg!fUruvf(D@R@QYTB(5TFSEFOdQfGb=JGBxoU~D zX%e{!uR$9t^Hiiq#0G7la>Bd8g(jNjTR*vvtosQRj-$4i?h+YX_n18fm8-3GFP4BU zW_&qgqn7pXKu#ugO6AOh+1nyFF&>LR#!oUVY8spk@-7r6LAm?XYxA7`RkP(0?D%<~ ze<_{&3Q^5D2O?q&5o|Yik+h-5_QoOdJixi>?~(gKwUfX4Vs{Yl0lR%WQZheBH6P=- z7#9+XK6yqrgYC`1W+R_5f@{aMyiQ~_`QEhhQo&PLlN9m5UQ~?SZu-iQ-m{I7n#NQr z4`bUMvB9uVJ4TfSdku8GBcK;P9G`SLoxW2I;14ff_|H^>=FR}w&E1I5nfU8T&-$s? zkpOYQUgTXQDwh<7ftY77o{qNKGy9uSp74S2$@MN&5OV&p8~r9r=63psDjgk79vr(X z^e;U=DL91Bx#})9_K!U!k553x)Ma#W%g0an4ospeA)sqHbht>#mIc(XVd)m`qaWf7 z_npyh91OkFB~fITu*y7Egf7wZVWs!%$Z(a_Oj_~T+mRPf2o$1)nlWCDlrjB{8;51E z5eHwtxGRhOL9XJV{`cv%Do2lO*trjClv7vHW@T|9DHKQsNK}$=grA01v z{M{ehYmFwN6)i&h#CkQ9C7anbWds7 zv&M*hY)kEoukGK6uxQ`d;1%qrtIXI)pq|C2^)1p}`^n5#zQ>TsR2Pk|cXsvios=X4 z&ABAkwndOp$Q10|-A|Ut?b?z7ntC9hjF37JBjxo6pWQN*kh{{V&ME(MLmf*RTQJL~ z-K)#8d(##>vvEEnF6*M##%!HBB5tvP-!T1{HnGs5g=RG=)1mD3ljk1|!|=fMRHIa| zYnH>WR|zjc-0EdIa1He=Or+g);?n1uyusV3!=)CQzuh^3d^ZdG?OV8SwR?5(IA1OB z6xDXGrV3T-Tx3tquU#|M<3=qrxf(A;H9SHZAMW}1Wu$f>{_C={%yj%{U9lBwM>T5# zQIKUtC9VZEF83rOiJkxQ;divnLj~$EfxX|iD9~xH62J|Zz?}0frRNHHKI9|q5~m_A zTFVb%eTTdxuC}%s=h;gpJ~bF$5R|6NT~NWP#kQ00>l^w-F z{bwogJ%W-F6woN01p0NR3J&kKVy>yP_XhjSR&)7_CP5`MF^-WsQ+jzqe%?2|g)!^k zZY&8f1oNXrTLW+qMJJDIMDa&W_a0x;7q8d;%yMVsegq!ui4qY*UloICyiH*gp%%o~ zxNaq}+|R^;f)n56nKfzNZ>&NzBdPARHRX7eid}m{AxoHJ_qnEMoX3$YX8E5sU<=<* zsAOu%l`N)L1OkGhA#mV`cD&jMv&~{)1|gX}<#-Hx72o?bokdH1>2)9o)0FF?;#&4jR3!+)A@4N1H0_a)iKrk)6J= zPB~yaIMnlpcX-}FY^icCLFe+FwU>6Qmr#5XvJ9P$CsbRuvpCvh_;Wt73xXSyN93~5 z$*j-o5d5IT)|JZtY&G?qeWVI>u#ZFVCuRp_{WN)jRBMTi*mzfXN~FW<((^$tG?`^R|?Y^n;~7BFCS1$Foj*>}PuSt{J}x1|=?%JnAdG4J60& zFV@w_0=|Cx+qLh6&Epy8+E<6XR#0_FA)1mjYhO-Nm=hz}OT%VU7%oUw_r$6j?Yhk< zC>GTP2QaO6CRVFi$M$q?RLI4#`jx21kxe`*J=QEAwd!MGl#@(}?a2lWd+C&CS^iWX0 zZf!g*`U7`L_S2O3D@M(Yyz_7ZoxL&Z2!5@PT*sXlpRp`K$4m>hWWb(6Y&(@S^*|-= zem*1i^~O}wS**jb!ZfwGa9l90?%lUVUqba*qT#!njNgZK(0pKuD=e>u9+~ngM`pwX zO&%r!ubC#|^c`wfRV>#=YtA)OXUyg3G<8(fF%*P{iaqi-wqG{EefVlUF&IZEW^V+% z@jbFXzCCsZxlD_93f{-kKtSO4kj$+^S0+xLT8Q6huu$3Ir|PvA)~igMD^1vFGfk246gwW@ zGX*2}xA}pDera(!H|QXlk!8MYQ!+z-@7g#8Qgo4Ni{afBC3xx;Ycrx|)Jab}{`6^+ z3`H_AHXpfmB;qR;7vq7O^2uE}Qe@QXdPVj_K7(jwozto^h&7uTaa~%|mso0g&dxFkzKEgt%mC2_EIPxJb|5{(0@CL3efY>#?y!s@%en~eQdCe{~Ld~k06^g1boffTH2Y-(=NLRtH^ zs};7x+w`&qzR)paJ1-)Ej8$GV9Xhw?6gY3TWk4q! z2z{I8&#GxeA1ya-^-jhbW{th` zKGu);!XVs@gDX(z&hr*a2)((>XgP{CMw(Fm^tP=Q_NwC}kt*A6+*}Ggo3DS-y>ZIL zu5m{9^T|?+5Q){gz=K(zL2}Tps9hyxn$j%(#8eduB1e6TK)V*DVjr@$W>kLR*}b}a zmoskMKKRTm1pkDfrWrv1_1CHWJR@X&;%ns*+L<}_f=fC@fJ(0F$)4IcnB|OGQbDQ8 zwd2JHzhLuxRb6+Xk|p58{I>lv{*EzFwYQTkRNtT^HZx%I=VOfdFC3k2w zJ7b+y-eZZ*Wc|M9uOhlDkBB#{xWxMan9q*B=76_@jv3&f_SCfbIp@$tLL^K`*^F>bN}%On~O#2$4=bbS^RuY z6>vfC@^z_xo-*zq?bhA{4u|U(o;mU{*jN64ysZ1*RedkBb{0;y$n!e#|9EoYzZc*C zNZ9MGytZ`yY`dL~^Q51jv&cWT<>URrR;#HIbzALMN$Xv$)Qg+^quTy-Pi=T^u)S^< z+uXVfe`TLP|FtzWC;pGMJ^OT?&$s%L?fG`yvDh9y->~r9d(~y@?Yx0gAp4K}d!c?r zr}J&+153O8t8&-%EuZmz!Tgtt_oOL)i`3a&mG`H9kJ`_wkjqDYE}rbUA9w*+#!lhn zC&1Il>tz2t4B2|-rOLUTK#wq8c(uFwrLlhB;R^fm-DYQ>KK~r_`nbUh8|%(uN16Rs zOY7z+KiV_xyz#lrjdLR6WX{h$QgZcc(H!CAy25`Q^~ZNEzW=(>;KaYT$1Seweslc# zGo;mIW8Fsi>bd_P+r+PRFFh-ATk`q8SH`;M&R$LT-eWh{=TR+dH+b2VL-JAaH9sD# z_nHA-wyk3hN>Gea-jFrGI^~peUxOBw{d&DV|0?j{`{@7R{V@@KqZ=NE@^V4?EOwkz ziMo^QZ}@b=|=IlgqBe*S`PHzd@pb U_gnreQ_uolPgg&ebxsLQ07(m`@&Et; literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index 896ac2b..9cbe151 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 << 28; // feel free to change the size of array const int NPOT = SIZE - 3; // Non-Power-Of-Two int *a = new int[SIZE]; int *b = new int[SIZE]; diff --git a/stream_compaction/common.h b/stream_compaction/common.h index d2c1fed..eb1daf2 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 blockSize 128 /** * 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 acee83d..08faa3c 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -3,8 +3,6 @@ #include "common.h" #include "efficient.h" -#define blockSize 128 - namespace StreamCompaction { namespace Efficient { using StreamCompaction::Common::PerformanceTimer; diff --git a/stream_compaction/naive.cu b/stream_compaction/naive.cu index df6725e..7e1e231 100644 --- a/stream_compaction/naive.cu +++ b/stream_compaction/naive.cu @@ -3,8 +3,6 @@ #include "common.h" #include "naive.h" -#define blockSize 128 - namespace StreamCompaction { namespace Naive { using StreamCompaction::Common::PerformanceTimer; From 858dafa393be85591842e35fe607f65610e2624c Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Wed, 20 Sep 2023 21:31:14 -0400 Subject: [PATCH 7/8] Complete 6 --- src/main.cpp | 44 ++++++++++++- stream_compaction/cpu.cu | 11 ++++ stream_compaction/cpu.h | 2 + stream_compaction/efficient.cu | 2 +- stream_compaction/radix_sort.cu | 105 +++++++++++++++++++++++++++++++- 5 files changed, 159 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 9cbe151..24f5248 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,9 +11,10 @@ #include #include #include +#include #include "testing_helpers.hpp" -const int SIZE = 1 << 28; // feel free to change the size of array +const int SIZE = 1 << 20; // 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]; @@ -27,7 +28,7 @@ int main(int argc, char* argv[]) { printf("** SCAN TESTS **\n"); printf("****************\n"); - genArray(SIZE - 1, a, 50); // Leave a 0 at the end to test that edge case + genArray(SIZE - 1, a, 1000); // Leave a 0 at the end to test that edge case a[SIZE - 1] = 0; printArray(SIZE, a, true); @@ -102,7 +103,7 @@ int main(int argc, char* argv[]) { // Compaction tests - genArray(SIZE - 1, a, 4); // Leave a 0 at the end to test that edge case + genArray(SIZE - 1, a, 1000); // Leave a 0 at the end to test that edge case a[SIZE - 1] = 0; printArray(SIZE, a, true); @@ -147,6 +148,43 @@ int main(int argc, char* argv[]) { //printArray(count, c, true); printCmpLenResult(count, expectedNPOT, b, c); + printf("\n"); + printf("**********************\n"); + printf("** RADIX SORT TESTS **\n"); + printf("**********************\n"); + + // Radix sort tests + + genArray(SIZE - 1, a, 1000); // Leave a 0 at the end to test that edge case + a[SIZE - 1] = 0; + printArray(SIZE, a, true); + + zeroArray(SIZE, b); + printDesc("cpu sort, power-of-two"); + StreamCompaction::CPU::sort(SIZE, b, a); + printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + printArray(SIZE, b, true); + + zeroArray(SIZE, c); + printDesc("radix sort, power-of-two"); + StreamCompaction::RadixSort::sort(SIZE, c, a); + printElapsedTime(StreamCompaction::RadixSort::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + //printArray(SIZE, c, true); + printCmpResult(SIZE, b, c); + + zeroArray(SIZE, b); + printDesc("cpu sort, non-power-of-two"); + StreamCompaction::CPU::sort(NPOT, b, a); + printElapsedTime(StreamCompaction::CPU::timer().getCpuElapsedTimeForPreviousOperation(), "(std::chrono Measured)"); + printArray(NPOT, b, true); + + zeroArray(SIZE, c); + printDesc("radix sort, non-power-of-two"); + StreamCompaction::RadixSort::sort(NPOT, c, a); + printElapsedTime(StreamCompaction::RadixSort::timer().getGpuElapsedTimeForPreviousOperation(), "(CUDA Measured)"); + //printArray(SIZE, c, true); + printCmpResult(NPOT, b, c); + system("pause"); // stop Win32 console from closing on exit delete[] a; delete[] b; diff --git a/stream_compaction/cpu.cu b/stream_compaction/cpu.cu index 8752a03..e349862 100644 --- a/stream_compaction/cpu.cu +++ b/stream_compaction/cpu.cu @@ -70,5 +70,16 @@ namespace StreamCompaction { timer().endCpuTimer(); return count; } + + int compare(const void *a, const void *b) { + return (*(int*)a - *(int*)b); + } + + void sort(int n, int *odata, const int *idata) { + timer().startCpuTimer(); + memcpy(odata, idata, n * sizeof(int)); + qsort(odata, n, sizeof(int), compare); + timer().endCpuTimer(); + } } } diff --git a/stream_compaction/cpu.h b/stream_compaction/cpu.h index 873c047..ffabe81 100644 --- a/stream_compaction/cpu.h +++ b/stream_compaction/cpu.h @@ -11,5 +11,7 @@ namespace StreamCompaction { int compactWithoutScan(int n, int *odata, const int *idata); int compactWithScan(int n, int *odata, const int *idata); + + void sort(int n, int *odata, const int *idata); } } diff --git a/stream_compaction/efficient.cu b/stream_compaction/efficient.cu index 08faa3c..c68501c 100644 --- a/stream_compaction/efficient.cu +++ b/stream_compaction/efficient.cu @@ -36,7 +36,7 @@ namespace StreamCompaction { } } - __global__ void kernCount(int n, int *count, int *idata, int *indices) { + __global__ void kernCount(int n, int *count, const int *idata, const int *indices) { int index = (blockIdx.x * blockDim.x) + threadIdx.x; if (index == n - 1) { count[0] = indices[index] + (idata[index] ? 1 : 0); diff --git a/stream_compaction/radix_sort.cu b/stream_compaction/radix_sort.cu index 86d1b32..2d1d5d1 100644 --- a/stream_compaction/radix_sort.cu +++ b/stream_compaction/radix_sort.cu @@ -12,10 +12,113 @@ namespace StreamCompaction { return timer; } + __global__ void kernInitEF(int n, int *e, int *f, const int *idata, int bit) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + e[index] = (idata[index] & (1 << bit)) ? 0 : 1; + f[index] = e[index]; + } + } + + __global__ void kernUpSweep(int n, int *data, int step) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n && index % step == 0) { + data[index + step - 1] += data[index + (step >> 1) - 1]; + } + } + + __global__ void kernSetLastZero(int n, int *data) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index == n - 1) { + data[index] = 0; + } + } + + __global__ void kernDownSweep(int n, int *data, int step) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n && index % step == 0) { + int idx1 = index + (step >> 1) - 1, idx2 = index + step - 1; + int t = data[idx1]; + data[idx1] = data[idx2]; + data[idx2] += t; + } + } + + __global__ void kernTotalFalses(int n, int *count, const int *e, const int *f) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index == n - 1) { + count[0] = e[index] + f[index]; + } + } + + __global__ void kernComputeT(int n, int *t, const int *f, const int *count) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + t[index] = index - f[index] + count[0]; + } + } + + __global__ void kernScatterOut(int n, int *odata, const int *idata, const int *e, const int *f, const int *t) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if (index < n) { + int d = e[index] ? f[index] : t[index]; + odata[d] = idata[index]; + } + } + void sort(int n, int *odata, const int *idata) { - timer().startGpuTimer(); + int *dev_odata, *dev_idata, *dev_e, *dev_f, *dev_t, *dev_count; + int rounds = ilog2ceil(n); + int size = 1 << rounds; + cudaMalloc((void**)&dev_odata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_odata failed!"); + cudaMalloc((void**)&dev_idata, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_idata failed!"); + cudaMalloc((void**)&dev_e, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_e failed!"); + cudaMalloc((void**)&dev_f, size * sizeof(int)); + checkCUDAError("cudaMalloc dev_f failed!"); + cudaMalloc((void**)&dev_t, n * sizeof(int)); + checkCUDAError("cudaMalloc dev_t failed!"); + cudaMalloc((void**)&dev_count, sizeof(int)); + checkCUDAError("cudaMalloc dev_count failed!"); + cudaMemcpy(dev_idata, idata, n * sizeof(int), cudaMemcpyHostToDevice); + dim3 blocks((n + blockSize - 1) / blockSize); + timer().startGpuTimer(); + for (int bit = 0; bit < 31; bit++) { + kernInitEF<<>>(n, dev_e, dev_f, dev_idata, bit); + checkCUDAError("kernInitEF failed!"); + int step = 2; + for (int i = 0; i < rounds; i++) { + kernUpSweep<<>>(size, dev_f, step); + checkCUDAError("kernUpSweep failed!"); + step <<= 1; + } + kernSetLastZero<<>>(size, dev_f); + checkCUDAError("kernSetLastZero failed!"); + step = size; + for (int i = 0; i < rounds; i++) { + kernDownSweep<<>>(size, dev_f, step); + checkCUDAError("kernDownSweep failed!"); + step >>= 1; + } + kernTotalFalses<<>>(n, dev_count, dev_e, dev_f); + checkCUDAError("kernTotalFalses failed!"); + kernComputeT<<>>(n, dev_t, dev_f, dev_count); + checkCUDAError("kernComputeT failed!"); + kernScatterOut<<>>(n, dev_odata, dev_idata, dev_e, dev_f, dev_t); + checkCUDAError("kernScatterOut failed!"); + std::swap(dev_odata, dev_idata); + } timer().endGpuTimer(); + cudaMemcpy(odata, dev_idata, n * sizeof(int), cudaMemcpyDeviceToHost); + cudaFree(dev_odata); + cudaFree(dev_idata); + cudaFree(dev_e); + cudaFree(dev_f); + cudaFree(dev_t); + cudaFree(dev_count); } } } From 4f6299228f3086bbbb6a85bcdbddbad6735c9acc Mon Sep 17 00:00:00 2001 From: Zhiyu Lei Date: Wed, 20 Sep 2023 22:23:29 -0400 Subject: [PATCH 8/8] Complete README --- README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e52715..ec19c21 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,11 @@ CUDA Stream Compaction * Tested on: Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (CETS Virtual Lab) ### Project Description +* CPU Scan & Stream Compaction & Quick Sort ([stream_compaction/cpu.cu](stream_compaction/cpu.cu)) +* Naive GPU Scan Algorithm ([stream_compaction/naive.cu](stream_compaction/naive.cu)) +* Work-Efficient GPU Scan & Stream Compaction([stream_compaction/efficient.cu](stream_compaction/efficient.cu)) +* Using Thrust's Implementation ([stream_compaction/thrust.cu](stream_compaction/thrust.cu)) +* Radix Sort ([stream_compaction/radix_sort.cu](stream_compaction/radix_sort.cu)) ### Performance Analysis #### Roughly optimize the block sizes of each of your implementations for minimal run time on your GPU. @@ -20,4 +25,82 @@ block size|naive scan|work-efficient scan|thrust scan 512|1.6586|2.5638|0.1679 #### Compare all of these GPU Scan implementations to the serial CPU version of Scan. Plot a graph of the comparison (with array size on the independent axis). -![](img/README/time-size.png) \ No newline at end of file +![](img/README/time-size.png) +With a smaller array size, CPU scan is faster than GPU scan; but with a larger array size, GPU scan, especially Thrust's implementation, tends to be faster, and work-efficient scan also becomes faster than naive scan. Theoretically, GPU scan algorithms' run time increases logarithmically against the array size, but the plot does not show any sublinear trend. + +#### Write a brief explanation of the phenomena you see here. +Since I implemented both naive and work-efficient scan algorithms using global memory, the performance bottlenecks were mainly memory I/O. Accessing to global memory is more costly than accessing to shared memory. As for Thrust's implementation, the Nsight timeline shows the occupancy is full, so it tends to use the computability as much as possible. + +#### Test Program Output +Array size is $2^{20}$, and array values are in range $[0,1000)$. Radix sort tests are added. +``` +**************** +** SCAN TESTS ** +**************** + [ 559 897 331 240 911 774 261 359 471 923 455 970 436 ... 674 0 ] +==== cpu scan, power-of-two ==== + elapsed time: 1.7442ms (std::chrono Measured) + [ 0 559 1456 1787 2027 2938 3712 3973 4332 4803 5726 6181 7151 ... 521313475 521314149 ] +==== cpu scan, non-power-of-two ==== + elapsed time: 1.7567ms (std::chrono Measured) + [ 0 559 1456 1787 2027 2938 3712 3973 4332 4803 5726 6181 7151 ... 521311914 521312911 ] + passed +==== naive scan, power-of-two ==== + elapsed time: 1.56285ms (CUDA Measured) + passed +==== naive scan, non-power-of-two ==== + elapsed time: 1.55731ms (CUDA Measured) + passed +==== work-efficient scan, power-of-two ==== + elapsed time: 1.99274ms (CUDA Measured) + passed +==== work-efficient scan, non-power-of-two ==== + elapsed time: 1.99523ms (CUDA Measured) + passed +==== thrust scan, power-of-two ==== + elapsed time: 0.187808ms (CUDA Measured) + passed +==== thrust scan, non-power-of-two ==== + elapsed time: 0.166112ms (CUDA Measured) + passed + +***************************** +** STREAM COMPACTION TESTS ** +***************************** + [ 559 897 331 240 911 774 261 359 471 923 455 970 436 ... 674 0 ] +==== cpu compact without scan, power-of-two ==== + elapsed time: 0.3507ms (std::chrono Measured) + [ 559 897 331 240 911 774 261 359 471 923 455 970 436 ... 356 674 ] + passed +==== cpu compact without scan, non-power-of-two ==== + elapsed time: 0.4523ms (std::chrono Measured) + [ 559 897 331 240 911 774 261 359 471 923 455 970 436 ... 997 208 ] + passed +==== cpu compact with scan ==== + elapsed time: 3.6566ms (std::chrono Measured) + [ 559 897 331 240 911 774 261 359 471 923 455 970 436 ... 356 674 ] + passed +==== work-efficient compact, power-of-two ==== + elapsed time: 2.19942ms (CUDA Measured) + passed +==== work-efficient compact, non-power-of-two ==== + elapsed time: 4.09008ms (CUDA Measured) + passed + +********************** +** RADIX SORT TESTS ** +********************** + [ 559 897 331 240 911 774 261 359 471 923 455 970 436 ... 674 0 ] +==== cpu sort, power-of-two ==== + elapsed time: 50.9862ms (std::chrono Measured) + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 999 999 ] +==== radix sort, power-of-two ==== + elapsed time: 74.2602ms (CUDA Measured) + passed +==== cpu sort, non-power-of-two ==== + elapsed time: 53.0439ms (std::chrono Measured) + [ 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 999 999 ] +==== radix sort, non-power-of-two ==== + elapsed time: 71.4663ms (CUDA Measured) + passed +``` \ No newline at end of file