1 Parallel Computing in R

1.1 Limiting conditions for speeding up an R program

  • cpu-bound: Take too much cpu time
  • memory-bound: Take too much memory
  • I/O-bound: Take too much time to read/write from disk
  • network-bound: Take too much time to transfer

1.2 Ways to parallelize

  1. Sockets

    A new version of R is launched on each core.
    • Available on all systems
    • Each process on each core is unique

  2. Forking

    A copy of the current R session is moved to new cores.
    • Not available on Windows
    • Less overhead and easy to implement

1.3 Hidden Parallel

  • The AMD Core Math Library (ACML) is built for AMD chips and contains a full set of BLAS and LAPACK routines. The library is closed-source and is maintained/released by AMD.

  • The Intel Math Kernel is an analogous optimized library for Intel-based chips

  • The Accelerate framework on the Mac contains an optimized BLAS built by Apple.

  • The Automatically Tuned Linear Algebra Software (ATLAS) library is a special “adaptive” software package that is designed to be compiled on the computer where it will be used. As part of the build process, the library extracts detailed CPU information and optimizes the code as it goes along. The ATLAS library is hence a generic package that can be built on a wider array of CPUs.

This causes the matrix calculation can be done in parallel style.

X <- matrix(rnorm(1e6 * 100), 1e6, 100) dim(X) [1] 1000000 100 b <- rnorm(100) y <- drop(X %*% b) + rnorm(1e6)

system.time(b <- solve(crossprod(X), crossprod(X, y))) user system elapsed 0.854 0.002 0.448

1.4 Package: parallel

  1. parallel replaces two historical packages–the multicore and snow packages, and the functions in parallel have overlapping names with those older packages.

  2. Useful Functions

  • mclapply(): Fork Style, not available on Windows. When you specify mc.cores > 1, the code will fail.

    1. Because of the use of the fork mechanism, the mc* functions are generally not available to users of the Windows operating system. The mclapply() function (and related mc* functions) works via the fork mechanism on Unix-style operating systems. Briefly, your R session is the main process and when you call a function like mclapply(), you fork a series of sub-processes that operate independently from the main process (although they share a few low-level features). These sub-processes then execute your function on their subsets of the data, presumably on separate cores of your CPU. Once the computation is complete, each sub-process returns its results and then the sub-process is killed. The parallel package manages the logistics of forking the sub-processes and handling them once they’ve finished.


    1. mclapply() parallelizes calls to lapply(). The first two arguments to mclapply() are exactly the same as they are for lapply(). However, mclapply() has further arguments (that must be named), the most important of which is the mc.cores argument which you can use to specify the number of processors/cores you want to split the computation across. For example, if your machine has 4 cores on it, you might specify mc.cores = 4 to break your parallelize your operation across 4 cores (although this may not be the best idea if you are running other operations in the background besides R).


    1. The beauty of mclapply is that the worker processes are all created as clones of the master right at the point that mclapply is called, so you don’t have to worry about reproducing your environment on each of the cluster workers. Unfortunately, that isn’t possible on Windows.
  • detectCores()

    1. Check if your computer in fact has multiple cores that you can take advantage of.
  • makeCluster()

    1. Creates a set of copies of R running in parallel and communicating over sockets.
    2. You’ll notice that the makeCluster() function has a type argument that allows for different types of clusters beyond using sockets (although the default is a socket cluster).
  • stopCluster()

    1. Clean up an stop the cluster child processes.
    2. Quitting R will also stop all of the child processes
  • clusterEvalQ()

    1. clusterEvalQ evaluates a literal expression on each cluster node. It is a parallel version of evalq, and is a convenience function invoking clusterCall.
cl <- makeCluster(4)
registerDoParallel(cl)
clusterEvalQ(
  cl,
  {runif(1)}
)
stopCluster(cl)
  • parLapply()

    1. To do an lapply() operation over a socket cluster we can use the parLapply() function.
    2. Need the clusterExport() to export the data to the child process. The need to export data is a key difference in behavior between the “multicore” approach and the “socket” approach.
    3. Chunks of computation are statically allocated to nodes using clusterApply().
  • clusterExport()

    1. The second argument to clusterExport() is a character vector, and so you can export an arbitrary number of R objects to the child processes. You should be judicious in choosing what you export simply because each R object will be replicated in each of the child processes, and hence take up memory on your computer.

1.5 Package: doParallel

  1. The doParallel package is a merger of doSNOW and doMC, much as parallel is a merger of snow and multicore.

  2. It tells foreach’s %dopar% to use parallel as backend, which means it is a interface between foreach and parallel.

1.6 Package: foreach

  1. foreach package offers %do% and %dopar% to do the forloop-like jobs. It returns a list of results, whereas a for loop has no value and uses side effects to convey its result. Because of this, foreach loops have a few advantages over for loops when the purpose of the loop is to create a data structure such as a vector, list, or matrix: First, there is less code duplication, and hence, less chance for an error because the initialization of the vector or matrix is unnecessary. Second, a foreach loop may be easily parallelized by changing only a single keyword.

  2. foreach uses iterators package to introduce Python-style data feeding which may save memory during computation.

  3. Much of parallel computing comes to doing three things: splitting the problem into pieces, executing the pieces in parallel, and combining the results back together. Using the foreach package, the iterators help you to split the problem into pieces, the %dopar% function executes the pieces in parallel, and the specified .combine function puts the results back together.

  4. when and %:% make doing nested loops easier.

  5. For nested loops, an important issue is deciding which loop to use the parallelization.

  6. The doMC package is a “parallel backend” for the foreach package. It provides a mechanism needed to execute foreach loops in parallel. The foreach package must be used in conjunction with a package such as doMC in order to execute code in parallel. The user must register a parallel backend to use, otherwise foreach will execute tasks sequentially, even when the %dopar% operator is used.

  7. To get information of the parallel workers, you may use,

  • getDoParName()
  • getDoParWorkers()
    • get the number of cores used by the worker.
  • getDoParVersion()
  • getDoParRegistered()
    • TRUE/FALSE on whether the backend is registered.

R version 3.6.3 (2020-02-29) -- "Holding the Windsock"
Copyright (C) 2020 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

  Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

[Previously saved workspace restored]

> ### Load Package
> library(foreach)
> library(parallel)
> library(doParallel)
Loading required package: iterators
> library(tidyverse)
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
✔ ggplot2 3.3.2     ✔ purrr   0.3.4
✔ tibble  3.0.4     ✔ dplyr   1.0.2
✔ tidyr   1.1.2     ✔ stringr 1.4.0
✔ readr   1.4.0     ✔ forcats 0.5.0
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ purrr::accumulate() masks foreach::accumulate()
✖ dplyr::filter()     masks stats::filter()
✖ dplyr::lag()        masks stats::lag()
✖ purrr::when()       masks foreach::when()
> 
> ### Use 4 cores for parallel
> cl <- makeCluster(4)
> registerDoParallel(cl)
> 
> ### Spawn information 
> getDoParName()
[1] "doParallelSNOW"
> getDoParWorkers()
[1] 4
> 
> ### How many cores does the node have
> detectCores()
[1] 64
> 
> # Our model
> x <- iris[which(iris[,5] != "setosa"), c(1,5)]
> 
> summary(glm(x[,2]~x[,1], family=binomial(logit)))

Call:
glm(formula = x[, 2] ~ x[, 1], family = binomial(logit))

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-1.85340  -0.90001  -0.04717   0.96861   2.35458  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept) -12.5708     2.9068  -4.325 1.53e-05 ***
x[, 1]        2.0129     0.4654   4.325 1.53e-05 ***
---
Signif. codes:  0***0.001**0.01*0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 138.63  on 99  degrees of freedom
Residual deviance: 110.55  on 98  degrees of freedom
AIC: 114.55

Number of Fisher Scoring iterations: 4

> 
> # The number of Bootstrap sample
> trials <- 100000
> 
> ## foreach sequential (Not parallel result)
> 
> boot.time.foreach.seq <- system.time({
+   boot.res.foreach.seq <- 
+     foreach(i = icount(trials), .combine=rbind) %do% {
+       set.seed(i)
+       ind <- sample(nrow(iris), replace=TRUE)
+       result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
+       coefficients(result1)
+     }
+ })
> 
> 
> ## foreach parallel
> 
> 
> boot.time.foreach <- system.time({
+   boot.res.foreach <- 
+     foreach(i = icount(trials), .combine=rbind) %dopar% {
+       set.seed(i)
+       ind <- sample(nrow(iris), replace=TRUE)
+       result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
+       coefficients(result1)
+   }
+ })
> 
> 
> 
> ## parallel::mclapply
> boot.time.mclapply <- system.time({
+   boot.res.mclapply <- 
+     mclapply(
+       1:trials,
+       function(i){
+         set.seed(i)
+         ind <- sample(nrow(iris), replace=TRUE)
+         result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
+         coefficients(result1)
+       }, mc.cores = 4
+     )
+ })
> 
> 
> 
> ## parallel::parLapply
> clusterExport(cl, "x")
> 
> boot.time.parLapply <- system.time({
+   boot.res.parLapply <- 
+     parLapply(cl, 1:trials,
+       function(i){
+         set.seed(i)
+         ind <- sample(nrow(iris), replace=TRUE)
+         result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
+         coefficients(result1)
+       }
+     )
+ })
> 
> 
> 
> ## Time consumption
> boot.time.foreach.seq
   user  system elapsed 
389.632   0.841 390.518 
> boot.time.foreach
   user  system elapsed 
 68.835   5.455 165.378 
> boot.time.mclapply
   user  system elapsed 
209.926   2.507 106.313 
> boot.time.parLapply
   user  system elapsed 
  0.150   0.012 101.723 
> 
> ## Intercept 
> mean(boot.res.foreach.seq[, 1])
[1] -13.15341
> sd(boot.res.foreach.seq[, 1])
[1] 3.152096
> 
> mean(boot.res.foreach[, 1])
[1] -13.15341
> sd(boot.res.foreach[, 1])
[1] 3.152096
> 
> mean(map_dbl(boot.res.mclapply, ~.x[1]))
[1] -13.15341
> sd(map_dbl(boot.res.mclapply, ~.x[1]))
[1] 3.152096
> 
> mean(map_dbl(boot.res.parLapply, ~.x[1]))
[1] -13.15341
> sd(map_dbl(boot.res.parLapply, ~.x[1]))
[1] 3.152096
> 
> ## Slope
> mean(boot.res.foreach.seq[, 2])
[1] 2.106673
> sd(boot.res.foreach.seq[, 2])
[1] 0.5044038
> 
> mean(boot.res.foreach[, 2])
[1] 2.106673
> sd(boot.res.foreach[, 2])
[1] 0.5044038
> 
> mean(map_dbl(boot.res.mclapply, ~.x[2]))
[1] 2.106673
> sd(map_dbl(boot.res.mclapply, ~.x[2]))
[1] 0.5044038
> 
> mean(map_dbl(boot.res.parLapply, ~.x[2]))
[1] 2.106673
> sd(map_dbl(boot.res.parLapply, ~.x[2]))
[1] 0.5044038
> 
> proc.time()
   user  system elapsed 
885.105  12.103 773.158 

1.7 Generating Random Numbers

1.8 Summary

  • The “multicore” approach, which makes use of the mclapply() function is perhaps the simplest and can be implemented on just about any multi-core system (which nowadays is any system).

  • The “socket” approach, parLapply() like, is a bit more general and can be implemented on systems where the fork-ing mechanism is not available. The approach used in the “socket” type cluster can also be extended to other parallel cluster management systems.

LS0tDQp0aXRsZTogIlBhcmFsbGVsIENvbXB1dGluZyINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgdGhlbWU6IHVuaXRlZA0KICAgIGhpZ2hsaWdodDogdGFuZ28NCi0tLQ0KDQojIFBhcmFsbGVsIENvbXB1dGluZyBpbiBSDQoNCiMjIExpbWl0aW5nIGNvbmRpdGlvbnMgZm9yIHNwZWVkaW5nIHVwIGFuIFIgcHJvZ3JhbQ0KDQotIGNwdS1ib3VuZDogVGFrZSB0b28gbXVjaCBjcHUgdGltZQ0KLSBtZW1vcnktYm91bmQ6IFRha2UgdG9vIG11Y2ggbWVtb3J5DQotIEkvTy1ib3VuZDogVGFrZSB0b28gbXVjaCB0aW1lIHRvIHJlYWQvd3JpdGUgZnJvbSBkaXNrDQotIG5ldHdvcmstYm91bmQ6IFRha2UgdG9vIG11Y2ggdGltZSB0byB0cmFuc2Zlcg0KDQojIyBXYXlzIHRvIHBhcmFsbGVsaXplDQoNCjEuIFNvY2tldHMNCjxici8+PGJyLz4NCkEgbmV3IHZlcnNpb24gb2YgUiBpcyBsYXVuY2hlZCBvbiBlYWNoIGNvcmUuDQogICAgLSBBdmFpbGFibGUgb24gYWxsIHN5c3RlbXMNCiAgICAtIEVhY2ggcHJvY2VzcyBvbiBlYWNoIGNvcmUgaXMgdW5pcXVlDQo8YnIvPjxici8+DQoyLiBGb3JraW5nDQo8YnIvPjxici8+DQpBIGNvcHkgb2YgdGhlIGN1cnJlbnQgUiBzZXNzaW9uIGlzIG1vdmVkIHRvIG5ldyBjb3Jlcy4NCiAgICAtIE5vdCBhdmFpbGFibGUgb24gV2luZG93cw0KICAgIC0gTGVzcyBvdmVyaGVhZCBhbmQgZWFzeSB0byBpbXBsZW1lbnQNCiAgICANCiAgICANCiAgICANCiMjIEhpZGRlbiBQYXJhbGxlbA0KDQotIFRoZSBBTUQgQ29yZSBNYXRoIExpYnJhcnkgKEFDTUwpIGlzIGJ1aWx0IGZvciBBTUQgY2hpcHMgYW5kIGNvbnRhaW5zIGEgZnVsbCBzZXQgb2YgQkxBUyBhbmQgTEFQQUNLIHJvdXRpbmVzLiBUaGUgbGlicmFyeSBpcyBjbG9zZWQtc291cmNlIGFuZCBpcyBtYWludGFpbmVkL3JlbGVhc2VkIGJ5IEFNRC4NCg0KLSBUaGUgSW50ZWwgTWF0aCBLZXJuZWwgaXMgYW4gYW5hbG9nb3VzIG9wdGltaXplZCBsaWJyYXJ5IGZvciBJbnRlbC1iYXNlZCBjaGlwcw0KDQotIFRoZSBBY2NlbGVyYXRlIGZyYW1ld29yayBvbiB0aGUgTWFjIGNvbnRhaW5zIGFuIG9wdGltaXplZCBCTEFTIGJ1aWx0IGJ5IEFwcGxlLg0KDQotIFRoZSBBdXRvbWF0aWNhbGx5IFR1bmVkIExpbmVhciBBbGdlYnJhIFNvZnR3YXJlIChBVExBUykgbGlicmFyeSBpcyBhIHNwZWNpYWwg4oCcYWRhcHRpdmXigJ0gc29mdHdhcmUgcGFja2FnZSB0aGF0IGlzIGRlc2lnbmVkIHRvIGJlIGNvbXBpbGVkIG9uIHRoZSBjb21wdXRlciB3aGVyZSBpdCB3aWxsIGJlIHVzZWQuIEFzIHBhcnQgb2YgdGhlIGJ1aWxkIHByb2Nlc3MsIHRoZSBsaWJyYXJ5IGV4dHJhY3RzIGRldGFpbGVkIENQVSBpbmZvcm1hdGlvbiBhbmQgb3B0aW1pemVzIHRoZSBjb2RlIGFzIGl0IGdvZXMgYWxvbmcuIFRoZSBBVExBUyBsaWJyYXJ5IGlzIGhlbmNlIGEgZ2VuZXJpYyBwYWNrYWdlIHRoYXQgY2FuIGJlIGJ1aWx0IG9uIGEgd2lkZXIgYXJyYXkgb2YgQ1BVcy4NCg0KVGhpcyBjYXVzZXMgdGhlIG1hdHJpeCBjYWxjdWxhdGlvbiBjYW4gYmUgZG9uZSBpbiBwYXJhbGxlbCBzdHlsZS4gDQoNCj4gWCA8LSBtYXRyaXgocm5vcm0oMWU2ICogMTAwKSwgMWU2LCAxMDApDQo+IGRpbShYKQ0KPlsxXSAxMDAwMDAwICAgICAxMDANCj4gYiA8LSBybm9ybSgxMDApDQo+IHkgPC0gZHJvcChYICUqJSBiKSArIHJub3JtKDFlNikNCg0KPiBzeXN0ZW0udGltZShiIDwtIHNvbHZlKGNyb3NzcHJvZChYKSwgY3Jvc3Nwcm9kKFgsIHkpKSkNCj4gICB1c2VyICBzeXN0ZW0gZWxhcHNlZCANCj4gIDAuODU0ICAgMC4wMDIgICAwLjQ0OCANCiAgICANCiAgICANCg0KIyMgUGFja2FnZTogKipwYXJhbGxlbCoqDQoNCjEuICoqcGFyYWxsZWwqKiByZXBsYWNlcyB0d28gaGlzdG9yaWNhbCBwYWNrYWdlc+KAk3RoZSAqKm11bHRpY29yZSoqIGFuZCAqKnNub3cqKiBwYWNrYWdlcywgYW5kIHRoZSBmdW5jdGlvbnMgaW4gcGFyYWxsZWwgaGF2ZSBvdmVybGFwcGluZyBuYW1lcyB3aXRoIHRob3NlIG9sZGVyIHBhY2thZ2VzLg0KDQoNCg0KMi4gVXNlZnVsIEZ1bmN0aW9ucw0KICAtIGBtY2xhcHBseSgpYDogRm9yayBTdHlsZSwgbm90IGF2YWlsYWJsZSBvbiBXaW5kb3dzLiBXaGVuIHlvdSBzcGVjaWZ5IG1jLmNvcmVzID4gMSwgdGhlIGNvZGUgd2lsbCBmYWlsLg0KICAgIDEuICoqQmVjYXVzZSBvZiB0aGUgdXNlIG9mIHRoZSBmb3JrIG1lY2hhbmlzbSwgdGhlIG1jKiBmdW5jdGlvbnMgYXJlIGdlbmVyYWxseSBub3QgYXZhaWxhYmxlIHRvIHVzZXJzIG9mIHRoZSBXaW5kb3dzIG9wZXJhdGluZyBzeXN0ZW0qKi4gVGhlIGBtY2xhcHBseSgpYCBmdW5jdGlvbiAoYW5kIHJlbGF0ZWQgYG1jKmAgZnVuY3Rpb25zKSB3b3JrcyB2aWEgdGhlIGZvcmsgbWVjaGFuaXNtIG9uIFVuaXgtc3R5bGUgb3BlcmF0aW5nIHN5c3RlbXMuIEJyaWVmbHksIHlvdXIgUiBzZXNzaW9uIGlzIHRoZSBtYWluIHByb2Nlc3MgYW5kIHdoZW4geW91IGNhbGwgYSBmdW5jdGlvbiBsaWtlIGBtY2xhcHBseSgpYCwgeW91IGZvcmsgYSBzZXJpZXMgb2Ygc3ViLXByb2Nlc3NlcyB0aGF0IG9wZXJhdGUgaW5kZXBlbmRlbnRseSBmcm9tIHRoZSBtYWluIHByb2Nlc3MgKGFsdGhvdWdoIHRoZXkgc2hhcmUgYSBmZXcgbG93LWxldmVsIGZlYXR1cmVzKS4gVGhlc2Ugc3ViLXByb2Nlc3NlcyB0aGVuIGV4ZWN1dGUgeW91ciBmdW5jdGlvbiBvbiB0aGVpciBzdWJzZXRzIG9mIHRoZSBkYXRhLCBwcmVzdW1hYmx5IG9uIHNlcGFyYXRlIGNvcmVzIG9mIHlvdXIgQ1BVLiBPbmNlIHRoZSBjb21wdXRhdGlvbiBpcyBjb21wbGV0ZSwgZWFjaCBzdWItcHJvY2VzcyByZXR1cm5zIGl0cyByZXN1bHRzIGFuZCB0aGVuIHRoZSBzdWItcHJvY2VzcyBpcyBraWxsZWQuIFRoZSBgcGFyYWxsZWxgIHBhY2thZ2UgbWFuYWdlcyB0aGUgbG9naXN0aWNzIG9mIGZvcmtpbmcgdGhlIHN1Yi1wcm9jZXNzZXMgYW5kIGhhbmRsaW5nIHRoZW0gb25jZSB0aGV54oCZdmUgZmluaXNoZWQuDQoNCiAgICA8YnIvPg0KICAgIA0KICAgIDIuIGBtY2xhcHBseSgpYCBwYXJhbGxlbGl6ZXMgY2FsbHMgdG8gYGxhcHBseSgpYC4gVGhlIGZpcnN0IHR3byBhcmd1bWVudHMgdG8gYG1jbGFwcGx5KClgIGFyZSBleGFjdGx5IHRoZSBzYW1lIGFzIHRoZXkgYXJlIGZvciBgbGFwcGx5KClgLiBIb3dldmVyLCBgbWNsYXBwbHkoKWAgaGFzIGZ1cnRoZXIgYXJndW1lbnRzICh0aGF0IG11c3QgYmUgbmFtZWQpLCB0aGUgbW9zdCBpbXBvcnRhbnQgb2Ygd2hpY2ggaXMgdGhlIGBtYy5jb3Jlc2AgYXJndW1lbnQgd2hpY2ggeW91IGNhbiB1c2UgdG8gc3BlY2lmeSB0aGUgbnVtYmVyIG9mIHByb2Nlc3NvcnMvY29yZXMgeW91IHdhbnQgdG8gc3BsaXQgdGhlIGNvbXB1dGF0aW9uIGFjcm9zcy4gRm9yIGV4YW1wbGUsIGlmIHlvdXIgbWFjaGluZSBoYXMgNCBjb3JlcyBvbiBpdCwgeW91IG1pZ2h0IHNwZWNpZnkgYG1jLmNvcmVzID0gNGAgdG8gYnJlYWsgeW91ciBwYXJhbGxlbGl6ZSB5b3VyIG9wZXJhdGlvbiBhY3Jvc3MgNCBjb3JlcyAoYWx0aG91Z2ggdGhpcyBtYXkgbm90IGJlIHRoZSBiZXN0IGlkZWEgaWYgeW91IGFyZSBydW5uaW5nIG90aGVyIG9wZXJhdGlvbnMgaW4gdGhlIGJhY2tncm91bmQgYmVzaWRlcyBSKS4NCiAgICANCiAgICA8YnIvPg0KICAgIA0KICAgIDMuIFRoZSBiZWF1dHkgb2YgYG1jbGFwcGx5YCBpcyB0aGF0IHRoZSB3b3JrZXIgcHJvY2Vzc2VzIGFyZSBhbGwgY3JlYXRlZCBhcyBjbG9uZXMgb2YgdGhlIG1hc3RlciByaWdodCBhdCB0aGUgcG9pbnQgdGhhdCBgbWNsYXBwbHlgIGlzIGNhbGxlZCwgc28geW91IGRvbid0IGhhdmUgdG8gd29ycnkgYWJvdXQgcmVwcm9kdWNpbmcgeW91ciBlbnZpcm9ubWVudCBvbiBlYWNoIG9mIHRoZSBjbHVzdGVyIHdvcmtlcnMuIFVuZm9ydHVuYXRlbHksIHRoYXQgaXNuJ3QgcG9zc2libGUgb24gV2luZG93cy4NCiAgICANCiAgLSBgZGV0ZWN0Q29yZXMoKWANCiAgICAxLiAgQ2hlY2sgaWYgeW91ciBjb21wdXRlciBpbiBmYWN0IGhhcyBtdWx0aXBsZSBjb3JlcyB0aGF0IHlvdSBjYW4gdGFrZSBhZHZhbnRhZ2Ugb2YuDQogIC0gYG1ha2VDbHVzdGVyKClgDQogICAgMS4gQ3JlYXRlcyBhIHNldCBvZiBjb3BpZXMgb2YgUiBydW5uaW5nIGluIHBhcmFsbGVsIGFuZCBjb21tdW5pY2F0aW5nIG92ZXIgc29ja2V0cy4NCiAgICAyLiBZb3XigJlsbCBub3RpY2UgdGhhdCB0aGUgYG1ha2VDbHVzdGVyKClgIGZ1bmN0aW9uIGhhcyBhIHR5cGUgYXJndW1lbnQgdGhhdCBhbGxvd3MgZm9yIGRpZmZlcmVudCB0eXBlcyBvZiBjbHVzdGVycyBiZXlvbmQgdXNpbmcgc29ja2V0cyAoYWx0aG91Z2ggdGhlIGRlZmF1bHQgaXMgYSBzb2NrZXQgY2x1c3RlcikuDQogICAgDQogIC0gYHN0b3BDbHVzdGVyKClgDQogICAgMS4gQ2xlYW4gdXAgYW4gc3RvcCB0aGUgY2x1c3RlciBjaGlsZCBwcm9jZXNzZXMuDQogICAgMi4gUXVpdHRpbmcgUiB3aWxsIGFsc28gc3RvcCBhbGwgb2YgdGhlIGNoaWxkIHByb2Nlc3Nlcw0KICAtIGBjbHVzdGVyRXZhbFEoKWANCiAgICAxLiBgY2x1c3RlckV2YWxRYCBldmFsdWF0ZXMgYSBsaXRlcmFsIGV4cHJlc3Npb24gb24gZWFjaCBjbHVzdGVyIG5vZGUuIEl0IGlzIGEgcGFyYWxsZWwgdmVyc2lvbiBvZiBgZXZhbHFgLCBhbmQgaXMgYSBjb252ZW5pZW5jZSBmdW5jdGlvbiBpbnZva2luZyBjbHVzdGVyQ2FsbC4NCmBgYHtyfQ0KY2wgPC0gbWFrZUNsdXN0ZXIoNCkNCnJlZ2lzdGVyRG9QYXJhbGxlbChjbCkNCmNsdXN0ZXJFdmFsUSgNCiAgY2wsDQogIHtydW5pZigxKX0NCikNCnN0b3BDbHVzdGVyKGNsKQ0KYGBgDQogICAgDQogIC0gYHBhckxhcHBseSgpYA0KICANCiAgICAxLiBUbyBkbyBhbiBgbGFwcGx5KClgIG9wZXJhdGlvbiBvdmVyIGEgKipzb2NrZXQqKiBjbHVzdGVyIHdlIGNhbiB1c2UgdGhlIGBwYXJMYXBwbHkoKWAgZnVuY3Rpb24uIA0KICAgIDIuIE5lZWQgdGhlIGBjbHVzdGVyRXhwb3J0KClgIHRvIGV4cG9ydCB0aGUgZGF0YSB0byB0aGUgY2hpbGQgcHJvY2Vzcy4gVGhlIG5lZWQgdG8gZXhwb3J0IGRhdGEgaXMgYSBrZXkgZGlmZmVyZW5jZSBpbiBiZWhhdmlvciBiZXR3ZWVuIHRoZSDigJxtdWx0aWNvcmXigJ0gYXBwcm9hY2ggYW5kIHRoZSDigJxzb2NrZXTigJ0gYXBwcm9hY2guDQogICAgMy4gQ2h1bmtzIG9mIGNvbXB1dGF0aW9uIGFyZSBzdGF0aWNhbGx5IGFsbG9jYXRlZCB0byBub2RlcyB1c2luZyBgY2x1c3RlckFwcGx5KClgLiANCiAgICANCiAgLSBgY2x1c3RlckV4cG9ydCgpYA0KICAgIDEuIFRoZSBzZWNvbmQgYXJndW1lbnQgdG8gYGNsdXN0ZXJFeHBvcnQoKWAgaXMgYSBjaGFyYWN0ZXIgdmVjdG9yLCBhbmQgc28geW91IGNhbiBleHBvcnQgYW4gYXJiaXRyYXJ5IG51bWJlciBvZiBSIG9iamVjdHMgdG8gdGhlIGNoaWxkIHByb2Nlc3Nlcy4gWW91IHNob3VsZCBiZSBqdWRpY2lvdXMgaW4gY2hvb3Npbmcgd2hhdCB5b3UgZXhwb3J0IHNpbXBseSBiZWNhdXNlIGVhY2ggUiBvYmplY3Qgd2lsbCBiZSByZXBsaWNhdGVkIGluIGVhY2ggb2YgdGhlIGNoaWxkIHByb2Nlc3NlcywgYW5kIGhlbmNlIHRha2UgdXAgbWVtb3J5IG9uIHlvdXIgY29tcHV0ZXIuDQogIA0KICANCiMjIFBhY2thZ2U6ICoqZG9QYXJhbGxlbCoqDQoNCjEuIFRoZSBgZG9QYXJhbGxlbGAgcGFja2FnZSBpcyBhIG1lcmdlciBvZiBgZG9TTk9XYCBhbmQgYGRvTUNgLCBtdWNoIGFzIGBwYXJhbGxlbGAgaXMgYSBtZXJnZXIgb2YgYHNub3dgIGFuZCBgbXVsdGljb3JlLmANCg0KMi4gSXQgdGVsbHMgYGZvcmVhY2hgJ3MgYCVkb3BhciVgIHRvIHVzZSBgcGFyYWxsZWxgIGFzIGJhY2tlbmQsIHdoaWNoIG1lYW5zIGl0IGlzIGEgaW50ZXJmYWNlIGJldHdlZW4gYGZvcmVhY2hgIGFuZCBgcGFyYWxsZWxgLg0KDQoNCiMjIFBhY2thZ2U6ICoqZm9yZWFjaCoqDQoNCjEuIGBmb3JlYWNoYCBwYWNrYWdlIG9mZmVycyAlZG8lIGFuZCAlZG9wYXIlIHRvIGRvIHRoZSBmb3Jsb29wLWxpa2Ugam9icy4gSXQgcmV0dXJucyBhIGxpc3Qgb2YgcmVzdWx0cywgd2hlcmVhcyBhIGZvciBsb29wIGhhcyBubyB2YWx1ZSBhbmQgdXNlcyBzaWRlIGVmZmVjdHMgdG8gY29udmV5IGl0cyByZXN1bHQuIEJlY2F1c2Ugb2YgdGhpcywgZm9yZWFjaCBsb29wcyBoYXZlIGEgZmV3IGFkdmFudGFnZXMgb3ZlciBmb3IgbG9vcHMgd2hlbiB0aGUgcHVycG9zZSBvZiB0aGUgbG9vcCBpcyB0byBjcmVhdGUgYSBkYXRhIHN0cnVjdHVyZSBzdWNoIGFzIGEgdmVjdG9yLCBsaXN0LCBvciBtYXRyaXg6IEZpcnN0LCB0aGVyZSBpcyBsZXNzIGNvZGUgZHVwbGljYXRpb24sIGFuZCBoZW5jZSwgbGVzcyBjaGFuY2UgZm9yIGFuIGVycm9yIGJlY2F1c2UgdGhlIGluaXRpYWxpemF0aW9uIG9mIHRoZSB2ZWN0b3Igb3IgbWF0cml4IGlzIHVubmVjZXNzYXJ5LiBTZWNvbmQsIGEgZm9yZWFjaCBsb29wIG1heSBiZSBlYXNpbHkgcGFyYWxsZWxpemVkIGJ5IGNoYW5naW5nIG9ubHkgYSBzaW5nbGUga2V5d29yZC4NCg0KMi4gYGZvcmVhY2hgIHVzZXMgYGl0ZXJhdG9yc2AgcGFja2FnZSB0byBpbnRyb2R1Y2UgUHl0aG9uLXN0eWxlIGRhdGEgZmVlZGluZyB3aGljaCBtYXkgc2F2ZSBtZW1vcnkgZHVyaW5nIGNvbXB1dGF0aW9uLg0KDQozLiBNdWNoIG9mIHBhcmFsbGVsIGNvbXB1dGluZyBjb21lcyB0byBkb2luZyB0aHJlZSB0aGluZ3M6IHNwbGl0dGluZyB0aGUgcHJvYmxlbSBpbnRvIHBpZWNlcywgZXhlY3V0aW5nIHRoZSBwaWVjZXMgaW4gcGFyYWxsZWwsIGFuZCBjb21iaW5pbmcgdGhlIHJlc3VsdHMgYmFjayB0b2dldGhlci4gVXNpbmcgdGhlIGBmb3JlYWNoYCBwYWNrYWdlLCB0aGUgYGl0ZXJhdG9yc2AgaGVscCB5b3UgdG8gc3BsaXQgdGhlIHByb2JsZW0gaW50byBwaWVjZXMsIHRoZSBgJWRvcGFyJWAgZnVuY3Rpb24gZXhlY3V0ZXMgdGhlIHBpZWNlcyBpbiBwYXJhbGxlbCwgYW5kIHRoZSBzcGVjaWZpZWQgYC5jb21iaW5lYCBmdW5jdGlvbiBwdXRzIHRoZSByZXN1bHRzIGJhY2sgdG9nZXRoZXIuDQoNCjQuIGB3aGVuYCBhbmQgYCU6JWAgbWFrZSBkb2luZyBuZXN0ZWQgbG9vcHMgZWFzaWVyLg0KDQo1LiBGb3IgbmVzdGVkIGxvb3BzLCBhbiBpbXBvcnRhbnQgaXNzdWUgaXMgZGVjaWRpbmcgd2hpY2ggbG9vcCB0byB1c2UgdGhlIHBhcmFsbGVsaXphdGlvbi4NCg0KNi4gVGhlIGRvTUMgcGFja2FnZSBpcyBhIOKAnHBhcmFsbGVsIGJhY2tlbmTigJ0gZm9yIHRoZSBmb3JlYWNoIHBhY2thZ2UuIEl0IHByb3ZpZGVzIGEgbWVjaGFuaXNtIG5lZWRlZA0KdG8gZXhlY3V0ZSBmb3JlYWNoIGxvb3BzIGluIHBhcmFsbGVsLiBUaGUgZm9yZWFjaCBwYWNrYWdlIG11c3QgYmUgdXNlZCBpbiBjb25qdW5jdGlvbiB3aXRoIGENCnBhY2thZ2Ugc3VjaCBhcyBkb01DIGluIG9yZGVyIHRvIGV4ZWN1dGUgY29kZSBpbiBwYXJhbGxlbC4gVGhlIHVzZXIgbXVzdCByZWdpc3RlciBhIHBhcmFsbGVsIGJhY2tlbmQNCnRvIHVzZSwgb3RoZXJ3aXNlIGZvcmVhY2ggd2lsbCBleGVjdXRlIHRhc2tzIHNlcXVlbnRpYWxseSwgZXZlbiB3aGVuIHRoZSAlZG9wYXIlIG9wZXJhdG9yIGlzIHVzZWQuDQoNCjcuIFRvIGdldCBpbmZvcm1hdGlvbiBvZiB0aGUgcGFyYWxsZWwgd29ya2VycywgeW91IG1heSB1c2UsDQoNCiAgLSBgZ2V0RG9QYXJOYW1lKClgDQogIC0gYGdldERvUGFyV29ya2VycygpYA0KICAgIC0gZ2V0IHRoZSBudW1iZXIgb2YgY29yZXMgdXNlZCBieSB0aGUgd29ya2VyLg0KICAtIGBnZXREb1BhclZlcnNpb24oKWANCiAgLSBgZ2V0RG9QYXJSZWdpc3RlcmVkKClgDQogICAgLSBgVFJVRWAvYEZBTFNFYCBvbiB3aGV0aGVyIHRoZSBiYWNrZW5kIGlzIHJlZ2lzdGVyZWQuDQogICAgDQogICAgDQogICAgDQogICAgDQogICAgDQogICAgDQogICAgDQpgYGB7cn0NCg0KUiB2ZXJzaW9uIDMuNi4zICgyMDIwLTAyLTI5KSAtLSAiSG9sZGluZyB0aGUgV2luZHNvY2siDQpDb3B5cmlnaHQgKEMpIDIwMjAgVGhlIFIgRm91bmRhdGlvbiBmb3IgU3RhdGlzdGljYWwgQ29tcHV0aW5nDQpQbGF0Zm9ybTogeDg2XzY0LXBjLWxpbnV4LWdudSAoNjQtYml0KQ0KDQpSIGlzIGZyZWUgc29mdHdhcmUgYW5kIGNvbWVzIHdpdGggQUJTT0xVVEVMWSBOTyBXQVJSQU5UWS4NCllvdSBhcmUgd2VsY29tZSB0byByZWRpc3RyaWJ1dGUgaXQgdW5kZXIgY2VydGFpbiBjb25kaXRpb25zLg0KVHlwZSAnbGljZW5zZSgpJyBvciAnbGljZW5jZSgpJyBmb3IgZGlzdHJpYnV0aW9uIGRldGFpbHMuDQoNCiAgTmF0dXJhbCBsYW5ndWFnZSBzdXBwb3J0IGJ1dCBydW5uaW5nIGluIGFuIEVuZ2xpc2ggbG9jYWxlDQoNClIgaXMgYSBjb2xsYWJvcmF0aXZlIHByb2plY3Qgd2l0aCBtYW55IGNvbnRyaWJ1dG9ycy4NClR5cGUgJ2NvbnRyaWJ1dG9ycygpJyBmb3IgbW9yZSBpbmZvcm1hdGlvbiBhbmQNCidjaXRhdGlvbigpJyBvbiBob3cgdG8gY2l0ZSBSIG9yIFIgcGFja2FnZXMgaW4gcHVibGljYXRpb25zLg0KDQpUeXBlICdkZW1vKCknIGZvciBzb21lIGRlbW9zLCAnaGVscCgpJyBmb3Igb24tbGluZSBoZWxwLCBvcg0KJ2hlbHAuc3RhcnQoKScgZm9yIGFuIEhUTUwgYnJvd3NlciBpbnRlcmZhY2UgdG8gaGVscC4NClR5cGUgJ3EoKScgdG8gcXVpdCBSLg0KDQpbUHJldmlvdXNseSBzYXZlZCB3b3Jrc3BhY2UgcmVzdG9yZWRdDQoNCj4gIyMjIExvYWQgUGFja2FnZQ0KPiBsaWJyYXJ5KGZvcmVhY2gpDQo+IGxpYnJhcnkocGFyYWxsZWwpDQo+IGxpYnJhcnkoZG9QYXJhbGxlbCkNCkxvYWRpbmcgcmVxdWlyZWQgcGFja2FnZTogaXRlcmF0b3JzDQo+IGxpYnJhcnkodGlkeXZlcnNlKQ0K4pSA4pSAIEF0dGFjaGluZyBwYWNrYWdlcyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAgdGlkeXZlcnNlIDEuMy4wIOKUgOKUgA0K4pyUIGdncGxvdDIgMy4zLjIgICAgIOKclCBwdXJyciAgIDAuMy40DQrinJQgdGliYmxlICAzLjAuNCAgICAg4pyUIGRwbHlyICAgMS4wLjINCuKclCB0aWR5ciAgIDEuMS4yICAgICDinJQgc3RyaW5nciAxLjQuMA0K4pyUIHJlYWRyICAgMS40LjAgICAgIOKclCBmb3JjYXRzIDAuNS4wDQrilIDilIAgQ29uZmxpY3RzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgCB0aWR5dmVyc2VfY29uZmxpY3RzKCkg4pSA4pSADQrinJYgcHVycnI6OmFjY3VtdWxhdGUoKSBtYXNrcyBmb3JlYWNoOjphY2N1bXVsYXRlKCkNCuKcliBkcGx5cjo6ZmlsdGVyKCkgICAgIG1hc2tzIHN0YXRzOjpmaWx0ZXIoKQ0K4pyWIGRwbHlyOjpsYWcoKSAgICAgICAgbWFza3Mgc3RhdHM6OmxhZygpDQrinJYgcHVycnI6OndoZW4oKSAgICAgICBtYXNrcyBmb3JlYWNoOjp3aGVuKCkNCj4gDQo+ICMjIyBVc2UgNCBjb3JlcyBmb3IgcGFyYWxsZWwNCj4gY2wgPC0gbWFrZUNsdXN0ZXIoNCkNCj4gcmVnaXN0ZXJEb1BhcmFsbGVsKGNsKQ0KPiANCj4gIyMjIFNwYXduIGluZm9ybWF0aW9uIA0KPiBnZXREb1Bhck5hbWUoKQ0KWzFdICJkb1BhcmFsbGVsU05PVyINCj4gZ2V0RG9QYXJXb3JrZXJzKCkNClsxXSA0DQo+IA0KPiAjIyMgSG93IG1hbnkgY29yZXMgZG9lcyB0aGUgbm9kZSBoYXZlDQo+IGRldGVjdENvcmVzKCkNClsxXSA2NA0KPiANCj4gIyBPdXIgbW9kZWwNCj4geCA8LSBpcmlzW3doaWNoKGlyaXNbLDVdICE9ICJzZXRvc2EiKSwgYygxLDUpXQ0KPiANCj4gc3VtbWFyeShnbG0oeFssMl1+eFssMV0sIGZhbWlseT1iaW5vbWlhbChsb2dpdCkpKQ0KDQpDYWxsOg0KZ2xtKGZvcm11bGEgPSB4WywgMl0gfiB4WywgMV0sIGZhbWlseSA9IGJpbm9taWFsKGxvZ2l0KSkNCg0KRGV2aWFuY2UgUmVzaWR1YWxzOiANCiAgICAgTWluICAgICAgICAxUSAgICBNZWRpYW4gICAgICAgIDNRICAgICAgIE1heCAgDQotMS44NTM0MCAgLTAuOTAwMDEgIC0wLjA0NzE3ICAgMC45Njg2MSAgIDIuMzU0NTggIA0KDQpDb2VmZmljaWVudHM6DQogICAgICAgICAgICBFc3RpbWF0ZSBTdGQuIEVycm9yIHogdmFsdWUgUHIoPnx6fCkgICAgDQooSW50ZXJjZXB0KSAtMTIuNTcwOCAgICAgMi45MDY4ICAtNC4zMjUgMS41M2UtMDUgKioqDQp4WywgMV0gICAgICAgIDIuMDEyOSAgICAgMC40NjU0ICAgNC4zMjUgMS41M2UtMDUgKioqDQotLS0NClNpZ25pZi4gY29kZXM6ICAwIOKAmCoqKuKAmSAwLjAwMSDigJgqKuKAmSAwLjAxIOKAmCrigJkgMC4wNSDigJgu4oCZIDAuMSDigJgg4oCZIDENCg0KKERpc3BlcnNpb24gcGFyYW1ldGVyIGZvciBiaW5vbWlhbCBmYW1pbHkgdGFrZW4gdG8gYmUgMSkNCg0KICAgIE51bGwgZGV2aWFuY2U6IDEzOC42MyAgb24gOTkgIGRlZ3JlZXMgb2YgZnJlZWRvbQ0KUmVzaWR1YWwgZGV2aWFuY2U6IDExMC41NSAgb24gOTggIGRlZ3JlZXMgb2YgZnJlZWRvbQ0KQUlDOiAxMTQuNTUNCg0KTnVtYmVyIG9mIEZpc2hlciBTY29yaW5nIGl0ZXJhdGlvbnM6IDQNCg0KPiANCj4gIyBUaGUgbnVtYmVyIG9mIEJvb3RzdHJhcCBzYW1wbGUNCj4gdHJpYWxzIDwtIDEwMDAwMA0KPiANCj4gIyMgZm9yZWFjaCBzZXF1ZW50aWFsIChOb3QgcGFyYWxsZWwgcmVzdWx0KQ0KPiANCj4gYm9vdC50aW1lLmZvcmVhY2guc2VxIDwtIHN5c3RlbS50aW1lKHsNCisgICBib290LnJlcy5mb3JlYWNoLnNlcSA8LSANCisgICAgIGZvcmVhY2goaSA9IGljb3VudCh0cmlhbHMpLCAuY29tYmluZT1yYmluZCkgJWRvJSB7DQorICAgICAgIHNldC5zZWVkKGkpDQorICAgICAgIGluZCA8LSBzYW1wbGUobnJvdyhpcmlzKSwgcmVwbGFjZT1UUlVFKQ0KKyAgICAgICByZXN1bHQxIDwtIGdsbSh4W2luZCwyXX54W2luZCwxXSwgZmFtaWx5PWJpbm9taWFsKGxvZ2l0KSkNCisgICAgICAgY29lZmZpY2llbnRzKHJlc3VsdDEpDQorICAgICB9DQorIH0pDQo+IA0KPiANCj4gIyMgZm9yZWFjaCBwYXJhbGxlbA0KPiANCj4gDQo+IGJvb3QudGltZS5mb3JlYWNoIDwtIHN5c3RlbS50aW1lKHsNCisgICBib290LnJlcy5mb3JlYWNoIDwtIA0KKyAgICAgZm9yZWFjaChpID0gaWNvdW50KHRyaWFscyksIC5jb21iaW5lPXJiaW5kKSAlZG9wYXIlIHsNCisgICAgICAgc2V0LnNlZWQoaSkNCisgICAgICAgaW5kIDwtIHNhbXBsZShucm93KGlyaXMpLCByZXBsYWNlPVRSVUUpDQorICAgICAgIHJlc3VsdDEgPC0gZ2xtKHhbaW5kLDJdfnhbaW5kLDFdLCBmYW1pbHk9Ymlub21pYWwobG9naXQpKQ0KKyAgICAgICBjb2VmZmljaWVudHMocmVzdWx0MSkNCisgICB9DQorIH0pDQo+IA0KPiANCj4gDQo+ICMjIHBhcmFsbGVsOjptY2xhcHBseQ0KPiBib290LnRpbWUubWNsYXBwbHkgPC0gc3lzdGVtLnRpbWUoew0KKyAgIGJvb3QucmVzLm1jbGFwcGx5IDwtIA0KKyAgICAgbWNsYXBwbHkoDQorICAgICAgIDE6dHJpYWxzLA0KKyAgICAgICBmdW5jdGlvbihpKXsNCisgICAgICAgICBzZXQuc2VlZChpKQ0KKyAgICAgICAgIGluZCA8LSBzYW1wbGUobnJvdyhpcmlzKSwgcmVwbGFjZT1UUlVFKQ0KKyAgICAgICAgIHJlc3VsdDEgPC0gZ2xtKHhbaW5kLDJdfnhbaW5kLDFdLCBmYW1pbHk9Ymlub21pYWwobG9naXQpKQ0KKyAgICAgICAgIGNvZWZmaWNpZW50cyhyZXN1bHQxKQ0KKyAgICAgICB9LCBtYy5jb3JlcyA9IDQNCisgICAgICkNCisgfSkNCj4gDQo+IA0KPiANCj4gIyMgcGFyYWxsZWw6OnBhckxhcHBseQ0KPiBjbHVzdGVyRXhwb3J0KGNsLCAieCIpDQo+IA0KPiBib290LnRpbWUucGFyTGFwcGx5IDwtIHN5c3RlbS50aW1lKHsNCisgICBib290LnJlcy5wYXJMYXBwbHkgPC0gDQorICAgICBwYXJMYXBwbHkoY2wsIDE6dHJpYWxzLA0KKyAgICAgICBmdW5jdGlvbihpKXsNCisgICAgICAgICBzZXQuc2VlZChpKQ0KKyAgICAgICAgIGluZCA8LSBzYW1wbGUobnJvdyhpcmlzKSwgcmVwbGFjZT1UUlVFKQ0KKyAgICAgICAgIHJlc3VsdDEgPC0gZ2xtKHhbaW5kLDJdfnhbaW5kLDFdLCBmYW1pbHk9Ymlub21pYWwobG9naXQpKQ0KKyAgICAgICAgIGNvZWZmaWNpZW50cyhyZXN1bHQxKQ0KKyAgICAgICB9DQorICAgICApDQorIH0pDQo+IA0KPiANCj4gDQo+ICMjIFRpbWUgY29uc3VtcHRpb24NCj4gYm9vdC50aW1lLmZvcmVhY2guc2VxDQogICB1c2VyICBzeXN0ZW0gZWxhcHNlZCANCjM4OS42MzIgICAwLjg0MSAzOTAuNTE4IA0KPiBib290LnRpbWUuZm9yZWFjaA0KICAgdXNlciAgc3lzdGVtIGVsYXBzZWQgDQogNjguODM1ICAgNS40NTUgMTY1LjM3OCANCj4gYm9vdC50aW1lLm1jbGFwcGx5DQogICB1c2VyICBzeXN0ZW0gZWxhcHNlZCANCjIwOS45MjYgICAyLjUwNyAxMDYuMzEzIA0KPiBib290LnRpbWUucGFyTGFwcGx5DQogICB1c2VyICBzeXN0ZW0gZWxhcHNlZCANCiAgMC4xNTAgICAwLjAxMiAxMDEuNzIzIA0KPiANCj4gIyMgSW50ZXJjZXB0IA0KPiBtZWFuKGJvb3QucmVzLmZvcmVhY2guc2VxWywgMV0pDQpbMV0gLTEzLjE1MzQxDQo+IHNkKGJvb3QucmVzLmZvcmVhY2guc2VxWywgMV0pDQpbMV0gMy4xNTIwOTYNCj4gDQo+IG1lYW4oYm9vdC5yZXMuZm9yZWFjaFssIDFdKQ0KWzFdIC0xMy4xNTM0MQ0KPiBzZChib290LnJlcy5mb3JlYWNoWywgMV0pDQpbMV0gMy4xNTIwOTYNCj4gDQo+IG1lYW4obWFwX2RibChib290LnJlcy5tY2xhcHBseSwgfi54WzFdKSkNClsxXSAtMTMuMTUzNDENCj4gc2QobWFwX2RibChib290LnJlcy5tY2xhcHBseSwgfi54WzFdKSkNClsxXSAzLjE1MjA5Ng0KPiANCj4gbWVhbihtYXBfZGJsKGJvb3QucmVzLnBhckxhcHBseSwgfi54WzFdKSkNClsxXSAtMTMuMTUzNDENCj4gc2QobWFwX2RibChib290LnJlcy5wYXJMYXBwbHksIH4ueFsxXSkpDQpbMV0gMy4xNTIwOTYNCj4gDQo+ICMjIFNsb3BlDQo+IG1lYW4oYm9vdC5yZXMuZm9yZWFjaC5zZXFbLCAyXSkNClsxXSAyLjEwNjY3Mw0KPiBzZChib290LnJlcy5mb3JlYWNoLnNlcVssIDJdKQ0KWzFdIDAuNTA0NDAzOA0KPiANCj4gbWVhbihib290LnJlcy5mb3JlYWNoWywgMl0pDQpbMV0gMi4xMDY2NzMNCj4gc2QoYm9vdC5yZXMuZm9yZWFjaFssIDJdKQ0KWzFdIDAuNTA0NDAzOA0KPiANCj4gbWVhbihtYXBfZGJsKGJvb3QucmVzLm1jbGFwcGx5LCB+LnhbMl0pKQ0KWzFdIDIuMTA2NjczDQo+IHNkKG1hcF9kYmwoYm9vdC5yZXMubWNsYXBwbHksIH4ueFsyXSkpDQpbMV0gMC41MDQ0MDM4DQo+IA0KPiBtZWFuKG1hcF9kYmwoYm9vdC5yZXMucGFyTGFwcGx5LCB+LnhbMl0pKQ0KWzFdIDIuMTA2NjczDQo+IHNkKG1hcF9kYmwoYm9vdC5yZXMucGFyTGFwcGx5LCB+LnhbMl0pKQ0KWzFdIDAuNTA0NDAzOA0KPiANCj4gcHJvYy50aW1lKCkNCiAgIHVzZXIgIHN5c3RlbSBlbGFwc2VkIA0KODg1LjEwNSAgMTIuMTAzIDc3My4xNTggDQoNCg0KYGBgDQoNCiAgICANCiAgICANCiAgICANCiAgICANCiAgICANCiAgICANCg0KIyMgR2VuZXJhdGluZyBSYW5kb20gTnVtYmVycw0KDQoNCg0KDQoNCiMjIFN1bW1hcnkNCg0KLSAgVGhlIOKAnG11bHRpY29yZeKAnSBhcHByb2FjaCwgd2hpY2ggbWFrZXMgdXNlIG9mIHRoZSBtY2xhcHBseSgpIGZ1bmN0aW9uIGlzIHBlcmhhcHMgdGhlIHNpbXBsZXN0IGFuZCBjYW4gYmUgaW1wbGVtZW50ZWQgb24ganVzdCBhYm91dCBhbnkgbXVsdGktY29yZSBzeXN0ZW0gKHdoaWNoIG5vd2FkYXlzIGlzIGFueSBzeXN0ZW0pLg0KDQotICBUaGUg4oCcc29ja2V04oCdIGFwcHJvYWNoLCBgcGFyTGFwcGx5KClgIGxpa2UsIGlzIGEgYml0IG1vcmUgZ2VuZXJhbCBhbmQgY2FuIGJlIGltcGxlbWVudGVkIG9uIHN5c3RlbXMgd2hlcmUgdGhlIGZvcmstaW5nIG1lY2hhbmlzbSBpcyBub3QgYXZhaWxhYmxlLiBUaGUgYXBwcm9hY2ggdXNlZCBpbiB0aGUg4oCcc29ja2V04oCdIHR5cGUgY2x1c3RlciBjYW4gYWxzbyBiZSBleHRlbmRlZCB0byBvdGhlciBwYXJhbGxlbCBjbHVzdGVyIG1hbmFnZW1lbnQgc3lzdGVtcy4NCg0KDQojIyBSZWZlcmVuY2UNCg0KLSBbUiBQcm9ncmFtbWluZyBmb3IgRGF0YSBTY2llbmNlLCBDaGFwdGVyIDIyXShodHRwczovL2Jvb2tkb3duLm9yZy9yZHBlbmcvcnByb2dkYXRhc2NpZW5jZS9wYXJhbGxlbC1jb21wdXRhdGlvbi5odG1sKQ0KDQotIFtzdGFja292ZXJmbG93LCB1bmRlcnN0YW5kaW5nLXRoZS1kaWZmZXJlbmNlcy1iZXR3ZWVuLW1jbGFwcGx5LWFuZC1wYXJsYXBwbHktaW4tcl0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMTcxOTYyNjEvdW5kZXJzdGFuZGluZy10aGUtZGlmZmVyZW5jZXMtYmV0d2Vlbi1tY2xhcHBseS1hbmQtcGFybGFwcGx5LWluLXIpDQoNCi0gW0dldHRpbmcgU3RhcnRlZCB3aXRoIGRvTUMgYW5kIGZvcmVhY2hdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9kb01DL3ZpZ25ldHRlcy9nZXR0aW5nc3RhcnRlZE1DLnBkZikNCg==