• 1 The Art of Benchmarking
    • 1.1 Introduction
    • 1.2 Benchmarking
      • 1.2.1 Comparing read times of CSV and RDS files
      • 1.2.2 Operational differences: “<-” and “=”
      • 1.2.3 Elapsed time
      • 1.2.4 Relative time
    • 1.3 How good is your machine?
  • 2 Fine Tuning: Efficient Base R
  • 3 Diagnosing Problems: Code Profiling
  • 4 Turbo Charged Code: Parallel Programming

1 The Art of Benchmarking

1.1 Introduction

1.1.1 R version

One of the relatively easy optimizations available is to use an up-to-date version of R. In general, R is very conservative, so upgrading doesn’t break existing code. However, a new version will often provide free speed boosts for key functions.

The version command returns a list that contains (among other things) the major and minor version of R currently being used.

# Print the R version details using version
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
version
               _                           
platform       x86_64-pc-linux-gnu         
arch           x86_64                      
os             linux-gnu                   
system         x86_64, linux-gnu           
status                                     
major          4                           
minor          0.0                         
year           2020                        
month          04                          
day            24                          
svn rev        78286                       
language       R                           
version.string R version 4.0.0 (2020-04-24)
nickname       Arbor Day                   
# Assign the variable major to the major component
major <- version$major

# Assign the variable minor to the minor component
minor <- version$minor

Keeping R up to date also makes it easier to get help when you are stuck.

1.2 Benchmarking

1.2.1 Comparing read times of CSV and RDS files

One of the most common tasks we perform is reading in data from CSV files. However, for large CSV files this can be slow. One neat trick is to read in the data and save as an R binary file (rds) using saveRDS(). To read in the rds file, we use readRDS().

Note: Since rds is R’s native format for storing single objects, you have not introduced any third-party dependencies that may change in the future.

To benchmark the two approaches, you can use system.time(). This function returns the time taken to evaluate any R expression. For example, to time how long it takes to calculate the square root of the numbers from one to ten million, you would write the following:

system.time(sqrt(1:1e7))
   user  system elapsed 
  0.154   0.047   0.202 
# How long does it take to read movies from CSV?
system.time(read.csv("movies.csv"))
   user  system elapsed 
  0.284   0.004   0.289 
# How long does it take to read movies from RDS?
system.time(readRDS("movies.rds"))
   user  system elapsed 
  0.051   0.000   0.052 

Reading in RDS files are much quicker than reading in CSV files.

1.2.2 Operational differences: “<-” and “=”

There are a number of ways to assign variables to objects. The two standard ways are to use the = or <- operators. Using the <- operator inside a function call will create a new (or overwrite an existing) object.

This makes ‘<-’ useful inside system.time() since we can store the result.

1.2.3 Elapsed time

Using system.time() is convenient, but it does have its drawbacks when comparing multiple function calls. The microbenchmark package solves this problem with the microbenchmark() function.

# Load the microbenchmark package
library(microbenchmark)

# Compare the two functions
compare <- microbenchmark(read.csv("movies.csv"), 
                          readRDS("movies.rds"), 
                          times = 10)

# Print compare
compare
Unit: milliseconds
ABCDEFGHIJ0123456789
min
<dbl>
lq
<dbl>
mean
<dbl>
median
<dbl>
uq
<dbl>
max
<dbl>
neval
<dbl>
283.70181287.35011299.52388290.25690297.31405376.5994910
50.7212550.8198652.1039851.1619552.3181958.7141710

The microbenchmark() function makes it easier to compare multiple function calls at once by compiling all the relevant information in a data frame. It does this by running each function call multiple times, recording the time it took for the function to run each time, then computing summary statistics for each expression as you can see here.

1.2.4 Relative time

When benchmarking it’s important to consider both absolute and relative times. Using the timings below, on average a single call to read.csv() is 720 - 80 = 640 milliseconds slower than that of readRDS(). Aprox, 9 times slower.

1.3 How good is your machine?

1.3.1 DataCamp hardware

For many problems your time is the expensive part. If having a faster computer makes you more productive, it can be cost effective to buy one. However, before you splash out on new toys for yourself, your boss/partner may want to see some numbers to justify the expense. Measuring the performance of your computer is called benchmarking, and you can do that with the benchmarkme package.

# Load the benchmarkme package
library(benchmarkme)

# Assign the variable ram to the amount of RAM on this machine
ram <- get_ram()
ram
32.6 GB
# Assign the variable cpu to the cpu specs
cpu <- get_cpu()
cpu
$vendor_id
[1] "GenuineIntel"

$model_name
[1] "Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz"

$no_of_cores
[1] 16

1.3.2 Benchmark DataCamp’s machine

The benchmarkme package allows you to run a set of standardized benchmarks and compare your results to other users. One set of benchmarks tests is reading and writing speeds.

The function call

res = benchmark_io(runs = 1, size = 5)

records the length of time it takes to read and write a 5MB file.

# Run the io benchmark
res <- benchmark_io(runs = 1, size = 5)
Preparing read/write io
# IO benchmarks (2 tests) for size 5 MB:
     Writing a csv with 625000 values: 0.57 (sec).
     Reading a csv with 625000 values: 0.228 (sec).
# Plot the results
plot(res)
You are ranked 15 out of 135 machines.

# Run each benchmark 3 times
Warning message:
In readChar(file, size, TRUE) : truncating string with embedded nuls
res <- benchmark_std(runs = 3)
# Programming benchmarks (5 tests):
    3,500,000 Fibonacci numbers calculation (vector calc): 0.603 (sec).
    Grand common divisors of 1,000,000 pairs (recursion): 0.772 (sec).
    Creation of a 3,500 x 3,500 Hilbert matrix (matrix calc): 0.424 (sec).
    Creation of a 3,000 x 3,000 Toeplitz matrix (loops): 1.25 (sec).
    Escoufier's method on a 60 x 60 matrix (mixed): 0.924 (sec).
# Matrix calculation benchmarks (5 tests):
    Creation, transp., deformation of a 5,000 x 5,000 matrix: 0.673 (sec).
    2,500 x 2,500 normal distributed random matrix^1,000: 0.501 (sec).
    Sorting of 7,000,000 random values: 0.793 (sec).
    2,500 x 2,500 cross-product matrix (b = a' * a): 1.27 (sec).
    Linear regr. over a 5,000 x 500 matrix (c = a \ b'): 0.124 (sec).
# Matrix function benchmarks (5 tests):
    Cholesky decomposition of a 3,000 x 3,000 matrix: 0.887 (sec).
    Determinant of a 2,500 x 2,500 random matrix: 0.919 (sec).
    Eigenvalues of a 640 x 640 random matrix: 0.468 (sec).
    FFT over 2,500,000 random values: 0.351 (sec).
    Inverse of a 1,600 x 1,600 random matrix: 0.806 (sec).
plot(res)
You are ranked 258 out of 749 machines.

2 Fine Tuning: Efficient Base R

2.1 Memory allocation

2.1.1 Timings - growing a vector

Growing a vector is one of the deadly sins in R; you should always avoid it.

The growing() function defined below generates n random standard normal numbers, but grows the size of the vector each time an element is added!

Note: Standard normal numbers are numbers drawn from a normal distribution with mean 0 and standard deviation 1.

n <- 30000
# Slow code
growing <- function(n) {
    x <- NULL
    for(i in 1:n)
        x <- c(x, rnorm(1))
    x
}
# Use <- with system.time() to store the result as res_grow
system.time(res_grow <- growing(n))
   user  system elapsed 
  1.444   0.000   1.449 

That was really slow for such a small vector. Next you’ll see ways to speed up that code.

2.1.2 Timings - pre-allocation

In the previous exercise, growing the vector took around 2 seconds. How long does it take when we pre-allocate the vector? The pre_allocate() function is defined below.

# Fast code
pre_allocate <- function(n) {
    x <- numeric(n) # Pre-allocate
    for(i in 1:n) 
        x[i] <- rnorm(1)
    x
}
# Use <- with system.time() to store the result as res_allocate
system.time(res_allocate <- pre_allocate(n))
   user  system elapsed 
  0.051   0.024   0.074 

Pre-allocating the vector is significantly faster than growing the vector!

2.2 Importance of vectorizing your code

2.2.1 Vectorized code: multiplication

The following piece of code is written like traditional C or Fortran code. Instead of using the vectorized version of multiplication, it uses a for loop.

x <- rnorm(10)
x2 <- numeric(length(x))
for(i in 1:10)
    x2[i] <- x[i] * x[i]

Your job is to make this code more “R-like” by vectorizing it.

# Store your answer as x2_imp
x2_imp <- x * x

The new version of the code is quicker to type, easier to understand, and it runs faster!

2.2.2 Vectorized code: calculating a log-sum

A common operation in statistics is to calculate the sum of log probabilities. The following code calculates the log-sum (the sum of the logs).

# x is a vector of probabilities
total <- 0
for(i in seq_along(x)) 
    total <- total + log(x[i])
NaNs producedNaNs producedNaNs producedNaNs producedNaNs produced
# Initial code
n <- 100
total <- 0
x <- runif(n)
for(i in 1:n) 
    total <- total + log(x[i])

# Rewrite in a single line. Store the result in log_sum
log_sum <- sum(log(x))

log() can take in a vector and outputs a vector. sum() takes in a vector and returns the sum of all the values in the vector.

2.3 Data frames and matrices

2.3.1 Data frames and matrices - column selection

All values in a matrix must have the same data type, which has efficiency implications when selecting rows and columns.

# Which is faster, mat[, 1] or df[, 1]? 
microbenchmark(mat[, 1], df[ ,1])
         expr   min    lq    mean median    uq    max neval
mat[, 1] 1.098 1.286 1.49122 1.3695 1.556  6.631   100
df[, 1] 6.064 6.321 7.11863 6.5265 6.791 55.580   100

Because all values in a matrix object must be the same data type, it is much faster to access the first column of a matrix than it is to access that of a data frame.

2.3.2 Row timings

Using microbenchmark(), how long does it take to select the first row from each of these objects?

To make the comparison fair, just use mat[1, ] and df[1, ].

# Which is faster, mat[1, ] or df[, 1]? 
microbenchmark(mat[1, ], df[ ,1])
Unit: microseconds
    expr      min        lq       mean   median        uq      max neval
mat[1, ]    4.122    4.5695   11.69504    9.305   18.2145   36.885   100
 df[1, ] 4397.701 4530.5430 4837.12408 4589.192 4700.0570 7809.431   100

Accessing a row of a data frame is much slower than accessing that of a matrix, more so than when accessing a column from each data type. This is because the values of a column of a data frame must be the same data type, whereas that of a row doesn’t have to be.

3 Diagnosing Problems: Code Profiling

3.1 What is code profiling

3.1.1 Profvis in action

# Load the data set
data(movies, package = "ggplot2movies") 

# Load the profvis package
library(profvis)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
# Profile the following code with the profvis function
profvis({
  # Load and select data
  comedies <- movies[movies$Comedy == 1, ]

  # Plot data of interest
  plot(comedies$year, comedies$rating)

  # Loess regression line
  model <- loess(rating ~ year, data = comedies)
  j <- order(comedies$year)
  
  # Add fitted line to the plot
  lines(comedies$year[j], model$fitted[j], col = "red")
})     ## Remember the closing brackets!

3.2 Profvis: Larger example

3.2.1 Change the data frame to a matrix

One of the parts of the code that profvis highlighted was the line where we generated the possible dice rolls and stored the results in a data frame:

df <- data.frame(d1 = sample(1:6, 3, replace = TRUE),
            d2 = sample(1:6, 3, replace = TRUE))

We can optimize this code by making two improvements:

  • Switching from a data frame to a matrix
  • Generating the 6 dice rolls in a single step

This gives:

m <- matrix(sample(1:6, 6, replace = TRUE), ncol = 2)
# Use microbenchmark to time m() and d()
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
microbenchmark(
 data.frame_solution = d(),
 matrix_solution     = m()
)
Unit: microseconds
ABCDEFGHIJ0123456789
expr
<fctr>
min
<dbl>
lq
<dbl>
mean
<dbl>
median
<dbl>
uq
<dbl>
max
<dbl>
neval
<dbl>
data.frame_solution121.415123.4675129.08917125.2945127.2285291.692100
matrix_solution4.8875.72107.415777.27758.156527.494100

3.2.2 Calculating row sums

The second bottleneck identified was calculating the row sums.

total <- apply(d, 1, sum)

In the previous example we switched the underlying object to a matrix. This makes the above apply operation three times faster. But there’s one further optimization we can use - switch apply() with rowSums()

# Compare the methods
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
microbenchmark(
    app_sol = app(rolls),
    r_sum_sol = r_sum(rolls)
)
Unit: microseconds
ABCDEFGHIJ0123456789
expr
<fctr>
min
<dbl>
lq
<dbl>
mean
<dbl>
median
<dbl>
uq
<dbl>
max
<dbl>
neval
<dbl>
app_sol14.95115.321517.1039915.590516.50359.484100
r_sum_sol3.1683.42204.008293.70453.93522.644100

3.2.3 Use && instead of &

To determine if both dice are the same, the move_square() function uses if statements.

if (is_double[1] & is_double[2] & is_double[3]) {
current <- 11 # Go To Jail - square 11 == Jail
}

The & operator will always evaluate both its arguments. That is, if you type x & y, R will always try to work out what x and y are. There are some cases where this is inefficient. For example, if x is FALSE, then x & y will always be FALSE, regardless of the value of y. Thus, you can save a little processing time by not calculating it. The && operator takes advantage of this trick, and doesn’t bother to calculate y if it doesn’t make a difference to the overall result.

In this code, if is_double[1] is FALSE we don’t need to evaluate is_double[2] or is_double[3], so we can get a speedup by swapping & for &&.

One thing to note is that && only works on single logical values, i.e., logical vectors of length 1 (like you would pass into an if condition), but & also works on vectors of length greater than 1.

is_double = c(FALSE, TRUE, TRUE)
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
is_double
[1] FALSE  TRUE  TRUE
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
# Define the previous solution
move <- function(is_double) {
    if (is_double[1] & is_double[2] & is_double[3]) {
        current <- 11 # Go To Jail
    }
}

# Define the improved solution
improved_move <- function(is_double) {
    if (is_double[1] && is_double[2] && is_double[3]) {
        current <- 11 # Go To Jail
    }
}
# microbenchmark both solutions
# Very occassionally the improved solution is actually a little slower
# This is just random chance
microbenchmark(move, improved_move, times = 1e5)
Unit: nanoseconds
ABCDEFGHIJ0123456789
expr
<fctr>
min
<dbl>
lq
<dbl>
mean
<dbl>
median
<dbl>
uq
<dbl>
max
<dbl>
neval
<dbl>
move222526.03437252653191e+05
improved_move212324.05335242458901e+05

4 Turbo Charged Code: Parallel Programming

4.1 CPUs - why do we have more than one

4.1.1 How many cores does this machine have?

The parallel package has a function detectCores() that determines the number of cores in a machine.

# Load the parallel package
library(parallel)

# Store the number of cores in the object no_of_cores
no_of_cores <- detectCores()

# Print no_of_cores
no_of_cores
[1] 16

4.2 What sort of problems benefit from parallel computing?

4.2.1 Can this loop run in parallel?

The following piece of code implements a simple dice game. The game is as follows:

  • Initialize: total <- 0.
  • Roll a single die and add it to total.
  • If total is even, reset total to zero.
  • If total is greater than 10, the game finishes.
total <- no_of_rolls <- 0 # Initialise
while(total < 10) {
  total <- total + sample(1:6, 1)

  if(total %% 2 == 0) total <- 0  # If even. Reset to 0

  no_of_rolls <- no_of_rolls + 1
}
no_of_rolls
[1] 3

Do you think this algorithm can be (easily) run in parallel? No: it’s a sequential algorithm. The ith value depends on the previous value. press

If we wrap the game in a function:

play <- function() {
  total <- no_of_rolls <- 0
  while(total < 10) {
    total <- total + sample(1:6, 1)

    # If even. Reset to 0
    if(total %% 2 == 0) total <- 0 
    no_of_rolls <- no_of_rolls + 1
  }
  no_of_rolls
}

And construct a loop to play the game

results <- numeric(100)
for(i in seq_along(results)) 
    results[i] <- play()
results
  [1] 16  5  8  8 69  8  5 10  8  6 17 21 16 14  3  5  5  6  9  2 10  2  8
 [24] 17  3 29  4 21 10  7 10 15  6 28 19 17  9  8 55  7  8 16 29  7  9  6
 [47]  4 25 12 29 40 54 21  7 14  4 17  3 10 13 16  7  5 30 12 10 15 36 10
 [70] 20 11  7 11 11 12 61 13 27 68 28  9 19  4  3  3  4  5 32 15 20  8  2
 [93] 17 17  6 18 10  3 14  5
results <- numeric(100)
for(i in seq_along(-results)) 
    results[i] <- play()
results
  [1]  4 12 16 21  5 59 63 10  3 11 10  9 43  3 21 51  4 13 15 67  3 49 18
 [24] 14 22  8 17 29 13  5 10 21 22  3 11 20 10 11 14 14 16 13 11  4 21  8
 [47] 18 26  4 16 37 39 31 15 11 14  4 17  6 28  7  7  6  6 36  2  9  8 18
 [70]  2 23  6  4 34 11  7 12 19 20 18 20  3  4  6 10 27 34 31  4  5 13  5
 [93] 18 14  5  3 16 18  7  2

Yes, this is embarrassingly parallel. We can simulate the games in any order. press Independent Monte Carlo simulations are ideal for parallel computation.

4.3 The parallel package parApply

Sometimes parApply() is faster; it just depends on the problem.

4.3.1 Moving to parApply

To run code in parallel using the parallel package, the basic workflow has three steps.

  1. Create a cluster using makeCluster().
  2. Do some work.
  3. Stop the cluster using stopCluster().

The simplest way to make a cluster is to pass a number to makeCluster(). This creates a cluster of the default type, running the code on that many cores.

dd <- readRDS("dd.rds")

The object dd is a data frame with 10 columns and 100 rows. The following code uses apply() to calculate the column medians:

apply(dd, 2, median)

To run this in parallel, you swap apply() for parApply(). The arguments to this function are the same, except that it takes a cluster argument before the usual apply() arguments.

# Determine the number of available cores
detectCores()
[1] 16
# Create a cluster via makeCluster
cl <- makeCluster(2)

# Parallelize this code
parApply(cl, dd, 2, median)
 [1]  0.01375112 -0.07879187  0.02337513 -0.02880553 -0.05578824
 [6] -0.07316231 -0.14109027 -0.25877556 -0.01040861  0.05407892
# Stop the cluster
stopCluster(cl)

4.4 The parallel package - parSapply

4.4.1 Using parSapply()

We previously played the following game:

  • Initialize: total = 0.
  • Roll a single die and add it to total.
  • If total is even, reset total to zero.
  • If total is greater than 10. The game finishes.

The game could be simulated using the play() function:

play <- function() {
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
  total <- no_of_rolls <- 0
  while(total < 10) {
    total <- total + sample(1:6, 1)

    # If even. Reset to 0
    if(total %% 2 == 0) total <- 0 
    no_of_rolls <- no_of_rolls + 1
  }
  no_of_rolls
}

To simulate the game 100 times, we could use a for loop or sapply():

res <- sapply(1:100, function(i) play())

This is perfect for running in parallel!

To make functions available on a cluster, you use the clusterExport() function. This takes a cluster and a string naming the function.

clusterExport(cl, "some_function")
library("parallel")
# Create a cluster via makeCluster (2 cores)
cl <- makeCluster(2)

# Export the play() function to the cluster
clusterExport(cl, "play")

# Re-write sapply as parSapply
res <- parSapply(cl, 1:100, function(i) play())

# Stop the cluster
stopCluster(cl)

clusterExport() makes it easy to run your functions inside a cluster.

4.4.2 Timings parSapply()

Running the dice game is embarrassingly parallel. These types of simulations usually (but not always) produce a good speed-up. As before, we can use microbenchmark() or system.time(). For simplicity, we’ll use system.time() in this exercise.

# Set the number of games to play
no_of_games <- 1e5

## Time serial version
system.time(serial <- sapply(1:no_of_games, function(i) play()))
   user  system elapsed 
  7.008   0.014   7.048 
## Set up cluster
cl <- makeCluster(2)
clusterExport(cl, "play")

## Time parallel version
system.time(par <- parSapply(cl, 1:no_of_games, function(i) play()))
   user  system elapsed 
  0.039   0.009   7.066 
## Stop cluster
stopCluster(cl)
LS0tCnRpdGxlOiAiV3JpdGluZyBFZmZpY2llbnQgUiBDb2RlIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfY29sbGFwc2VkOiBmYWxzZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICAKdG9jX2RlcHRoOiAzCi0tLQojIFRoZSBBcnQgb2YgQmVuY2htYXJraW5nCgoKIyMgSW50cm9kdWN0aW9uCgojIyMgUiB2ZXJzaW9uCgpPbmUgb2YgdGhlIHJlbGF0aXZlbHkgZWFzeSBvcHRpbWl6YXRpb25zIGF2YWlsYWJsZSBpcyB0byB1c2UgYW4gdXAtdG8tZGF0ZSB2ZXJzaW9uIG9mIFIuIEluIGdlbmVyYWwsIFIgaXMgdmVyeSBjb25zZXJ2YXRpdmUsIHNvIHVwZ3JhZGluZyBkb2Vzbid0IGJyZWFrIGV4aXN0aW5nIGNvZGUuIEhvd2V2ZXIsIGEgbmV3IHZlcnNpb24gd2lsbCBvZnRlbiBwcm92aWRlIGZyZWUgc3BlZWQgYm9vc3RzIGZvciBrZXkgZnVuY3Rpb25zLgoKVGhlIHZlcnNpb24gY29tbWFuZCByZXR1cm5zIGEgbGlzdCB0aGF0IGNvbnRhaW5zIChhbW9uZyBvdGhlciB0aGluZ3MpIHRoZSBtYWpvciBhbmQgbWlub3IgdmVyc2lvbiBvZiBSIGN1cnJlbnRseSBiZWluZyB1c2VkLgpgYGB7cn0KIyBQcmludCB0aGUgUiB2ZXJzaW9uIGRldGFpbHMgdXNpbmcgdmVyc2lvbgp2ZXJzaW9uCgojIEFzc2lnbiB0aGUgdmFyaWFibGUgbWFqb3IgdG8gdGhlIG1ham9yIGNvbXBvbmVudAptYWpvciA8LSB2ZXJzaW9uJG1ham9yCgojIEFzc2lnbiB0aGUgdmFyaWFibGUgbWlub3IgdG8gdGhlIG1pbm9yIGNvbXBvbmVudAptaW5vciA8LSB2ZXJzaW9uJG1pbm9yCmBgYApLZWVwaW5nIFIgdXAgdG8gZGF0ZSBhbHNvIG1ha2VzIGl0IGVhc2llciB0byBnZXQgaGVscCB3aGVuIHlvdSBhcmUgc3R1Y2suCgojIyBCZW5jaG1hcmtpbmcKCiMjIyBDb21wYXJpbmcgcmVhZCB0aW1lcyBvZiBDU1YgYW5kIFJEUyBmaWxlcwoKT25lIG9mIHRoZSBtb3N0IGNvbW1vbiB0YXNrcyB3ZSBwZXJmb3JtIGlzIHJlYWRpbmcgaW4gZGF0YSBmcm9tIENTViBmaWxlcy4gSG93ZXZlciwgZm9yIGxhcmdlIENTViBmaWxlcyB0aGlzIGNhbiBiZSBzbG93LiBPbmUgbmVhdCB0cmljayBpcyB0byByZWFkIGluIHRoZSBkYXRhIGFuZCBzYXZlIGFzIGFuIFIgYmluYXJ5IGZpbGUgKHJkcykgdXNpbmcgc2F2ZVJEUygpLiBUbyByZWFkIGluIHRoZSByZHMgZmlsZSwgd2UgdXNlIHJlYWRSRFMoKS4KCk5vdGU6IFNpbmNlIHJkcyBpcyBSJ3MgbmF0aXZlIGZvcm1hdCBmb3Igc3RvcmluZyBzaW5nbGUgb2JqZWN0cywgeW91IGhhdmUgbm90IGludHJvZHVjZWQgYW55IHRoaXJkLXBhcnR5IGRlcGVuZGVuY2llcyB0aGF0IG1heSBjaGFuZ2UgaW4gdGhlIGZ1dHVyZS4KClRvIGJlbmNobWFyayB0aGUgdHdvIGFwcHJvYWNoZXMsIHlvdSBjYW4gdXNlIHN5c3RlbS50aW1lKCkuIFRoaXMgZnVuY3Rpb24gcmV0dXJucyB0aGUgdGltZSB0YWtlbiB0byBldmFsdWF0ZSBhbnkgUiBleHByZXNzaW9uLiBGb3IgZXhhbXBsZSwgdG8gdGltZSBob3cgbG9uZyBpdCB0YWtlcyB0byBjYWxjdWxhdGUgdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXJzIGZyb20gb25lIHRvIHRlbiBtaWxsaW9uLCB5b3Ugd291bGQgd3JpdGUgdGhlIGZvbGxvd2luZzoKYGBge3J9CnN5c3RlbS50aW1lKHNxcnQoMToxZTcpKQpgYGAKCgpgYGB7cn0KIyBIb3cgbG9uZyBkb2VzIGl0IHRha2UgdG8gcmVhZCBtb3ZpZXMgZnJvbSBDU1Y/CnN5c3RlbS50aW1lKHJlYWQuY3N2KCJtb3ZpZXMuY3N2IikpCgojIEhvdyBsb25nIGRvZXMgaXQgdGFrZSB0byByZWFkIG1vdmllcyBmcm9tIFJEUz8Kc3lzdGVtLnRpbWUocmVhZFJEUygibW92aWVzLnJkcyIpKQpgYGAKUmVhZGluZyBpbiBSRFMgZmlsZXMgYXJlIG11Y2ggcXVpY2tlciB0aGFuIHJlYWRpbmcgaW4gQ1NWIGZpbGVzLgoKIyMjIE9wZXJhdGlvbmFsIGRpZmZlcmVuY2VzOiAiPC0iIGFuZCAiPSIKClRoZXJlIGFyZSBhIG51bWJlciBvZiB3YXlzIHRvIGFzc2lnbiB2YXJpYWJsZXMgdG8gb2JqZWN0cy4gVGhlIHR3byBzdGFuZGFyZCB3YXlzIGFyZSB0byB1c2UgdGhlID0gb3IgPC0gb3BlcmF0b3JzLiBVc2luZyB0aGUgPC0gb3BlcmF0b3IgaW5zaWRlIGEgZnVuY3Rpb24gY2FsbCB3aWxsIGNyZWF0ZSBhIG5ldyAob3Igb3ZlcndyaXRlIGFuIGV4aXN0aW5nKSBvYmplY3QuCgpUaGlzIG1ha2VzICc8LScgdXNlZnVsIGluc2lkZSBzeXN0ZW0udGltZSgpIHNpbmNlIHdlIGNhbiBzdG9yZSB0aGUgcmVzdWx0LgoKIyMjIEVsYXBzZWQgdGltZQoKVXNpbmcgc3lzdGVtLnRpbWUoKSBpcyBjb252ZW5pZW50LCBidXQgaXQgZG9lcyBoYXZlIGl0cyBkcmF3YmFja3Mgd2hlbiBjb21wYXJpbmcgbXVsdGlwbGUgZnVuY3Rpb24gY2FsbHMuIFRoZSBtaWNyb2JlbmNobWFyayBwYWNrYWdlIHNvbHZlcyB0aGlzIHByb2JsZW0gd2l0aCB0aGUgbWljcm9iZW5jaG1hcmsoKSBmdW5jdGlvbi4KYGBge3J9CiMgTG9hZCB0aGUgbWljcm9iZW5jaG1hcmsgcGFja2FnZQpsaWJyYXJ5KG1pY3JvYmVuY2htYXJrKQoKIyBDb21wYXJlIHRoZSB0d28gZnVuY3Rpb25zCmNvbXBhcmUgPC0gbWljcm9iZW5jaG1hcmsocmVhZC5jc3YoIm1vdmllcy5jc3YiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcmVhZFJEUygibW92aWVzLnJkcyIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lcyA9IDEwKQoKIyBQcmludCBjb21wYXJlCmNvbXBhcmUKYGBgClRoZSBtaWNyb2JlbmNobWFyaygpIGZ1bmN0aW9uIG1ha2VzIGl0IGVhc2llciB0byBjb21wYXJlIG11bHRpcGxlIGZ1bmN0aW9uIGNhbGxzIGF0IG9uY2UgYnkgY29tcGlsaW5nIGFsbCB0aGUgcmVsZXZhbnQgaW5mb3JtYXRpb24gaW4gYSBkYXRhIGZyYW1lLiBJdCBkb2VzIHRoaXMgYnkgcnVubmluZyBlYWNoIGZ1bmN0aW9uIGNhbGwgbXVsdGlwbGUgdGltZXMsIHJlY29yZGluZyB0aGUgdGltZSBpdCB0b29rIGZvciB0aGUgZnVuY3Rpb24gdG8gcnVuIGVhY2ggdGltZSwgdGhlbiBjb21wdXRpbmcgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBlYWNoIGV4cHJlc3Npb24gYXMgeW91IGNhbiBzZWUgaGVyZS4KCiMjIyBSZWxhdGl2ZSB0aW1lCgpXaGVuIGJlbmNobWFya2luZyBpdCdzIGltcG9ydGFudCB0byBjb25zaWRlciBib3RoIGFic29sdXRlIGFuZCByZWxhdGl2ZSB0aW1lcy4gVXNpbmcgdGhlIHRpbWluZ3MgYmVsb3csIG9uIGF2ZXJhZ2UgYSBzaW5nbGUgY2FsbCB0byByZWFkLmNzdigpIGlzIDcyMCAtIDgwID0gNjQwIG1pbGxpc2Vjb25kcyBzbG93ZXIgdGhhbiB0aGF0IG9mIHJlYWRSRFMoKS4gQXByb3gsIDkgdGltZXMgc2xvd2VyLgoKIyMgSG93IGdvb2QgaXMgeW91ciBtYWNoaW5lPwoKIyMjIERhdGFDYW1wIGhhcmR3YXJlCgpGb3IgbWFueSBwcm9ibGVtcyB5b3VyIHRpbWUgaXMgdGhlIGV4cGVuc2l2ZSBwYXJ0LiBJZiBoYXZpbmcgYSBmYXN0ZXIgY29tcHV0ZXIgbWFrZXMgeW91IG1vcmUgcHJvZHVjdGl2ZSwgaXQgY2FuIGJlIGNvc3QgZWZmZWN0aXZlIHRvIGJ1eSBvbmUuIEhvd2V2ZXIsIGJlZm9yZSB5b3Ugc3BsYXNoIG91dCBvbiBuZXcgdG95cyBmb3IgeW91cnNlbGYsIHlvdXIgYm9zcy9wYXJ0bmVyIG1heSB3YW50IHRvIHNlZSBzb21lIG51bWJlcnMgdG8ganVzdGlmeSB0aGUgZXhwZW5zZS4gTWVhc3VyaW5nIHRoZSBwZXJmb3JtYW5jZSBvZiB5b3VyIGNvbXB1dGVyIGlzIGNhbGxlZCBiZW5jaG1hcmtpbmcsIGFuZCB5b3UgY2FuIGRvIHRoYXQgd2l0aCB0aGUgYmVuY2htYXJrbWUgcGFja2FnZS4KYGBge3J9CiMgTG9hZCB0aGUgYmVuY2htYXJrbWUgcGFja2FnZQpsaWJyYXJ5KGJlbmNobWFya21lKQoKIyBBc3NpZ24gdGhlIHZhcmlhYmxlIHJhbSB0byB0aGUgYW1vdW50IG9mIFJBTSBvbiB0aGlzIG1hY2hpbmUKcmFtIDwtIGdldF9yYW0oKQpyYW0KCiMgQXNzaWduIHRoZSB2YXJpYWJsZSBjcHUgdG8gdGhlIGNwdSBzcGVjcwpjcHUgPC0gZ2V0X2NwdSgpCmNwdQpgYGAKIyMjIEJlbmNobWFyayBEYXRhQ2FtcCdzIG1hY2hpbmUKCgpUaGUgYmVuY2htYXJrbWUgcGFja2FnZSBhbGxvd3MgeW91IHRvIHJ1biBhIHNldCBvZiBzdGFuZGFyZGl6ZWQgYmVuY2htYXJrcyBhbmQgY29tcGFyZSB5b3VyIHJlc3VsdHMgdG8gb3RoZXIgdXNlcnMuIE9uZSBzZXQgb2YgYmVuY2htYXJrcyB0ZXN0cyBpcyByZWFkaW5nIGFuZCB3cml0aW5nIHNwZWVkcy4KClRoZSBmdW5jdGlvbiBjYWxsCgogICAgcmVzID0gYmVuY2htYXJrX2lvKHJ1bnMgPSAxLCBzaXplID0gNSkKCnJlY29yZHMgdGhlIGxlbmd0aCBvZiB0aW1lIGl0IHRha2VzIHRvIHJlYWQgYW5kIHdyaXRlIGEgNU1CIGZpbGUuCgpgYGB7cn0KIyBSdW4gdGhlIGlvIGJlbmNobWFyawpyZXMgPC0gYmVuY2htYXJrX2lvKHJ1bnMgPSAxLCBzaXplID0gNSkKCiMgUGxvdCB0aGUgcmVzdWx0cwpwbG90KHJlcykKYGBgCmBgYHtyfQojIFJ1biBlYWNoIGJlbmNobWFyayAzIHRpbWVzCnJlcyA8LSBiZW5jaG1hcmtfc3RkKHJ1bnMgPSAzKQpwbG90KHJlcykKYGBgCiMgRmluZSBUdW5pbmc6IEVmZmljaWVudCBCYXNlIFIKCiMjIE1lbW9yeSBhbGxvY2F0aW9uCgojIyMgVGltaW5ncyAtIGdyb3dpbmcgYSB2ZWN0b3IKCkdyb3dpbmcgYSB2ZWN0b3IgaXMgb25lIG9mIHRoZSBkZWFkbHkgc2lucyBpbiBSOyB5b3Ugc2hvdWxkIGFsd2F5cyBhdm9pZCBpdC4KClRoZSBncm93aW5nKCkgZnVuY3Rpb24gZGVmaW5lZCBiZWxvdyBnZW5lcmF0ZXMgbiByYW5kb20gc3RhbmRhcmQgbm9ybWFsIG51bWJlcnMsIGJ1dCBncm93cyB0aGUgc2l6ZSBvZiB0aGUgdmVjdG9yIGVhY2ggdGltZSBhbiBlbGVtZW50IGlzIGFkZGVkIQoKTm90ZTogU3RhbmRhcmQgbm9ybWFsIG51bWJlcnMgYXJlIG51bWJlcnMgZHJhd24gZnJvbSBhIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBtZWFuIDAgYW5kIHN0YW5kYXJkIGRldmlhdGlvbiAxLgpgYGB7cn0KbiA8LSAzMDAwMAojIFNsb3cgY29kZQpncm93aW5nIDwtIGZ1bmN0aW9uKG4pIHsKICAgIHggPC0gTlVMTAogICAgZm9yKGkgaW4gMTpuKQogICAgICAgIHggPC0gYyh4LCBybm9ybSgxKSkKICAgIHgKfQpgYGAKYGBge3J9CiMgVXNlIDwtIHdpdGggc3lzdGVtLnRpbWUoKSB0byBzdG9yZSB0aGUgcmVzdWx0IGFzIHJlc19ncm93CnN5c3RlbS50aW1lKHJlc19ncm93IDwtIGdyb3dpbmcobikpCmBgYApUaGF0IHdhcyByZWFsbHkgc2xvdyBmb3Igc3VjaCBhIHNtYWxsIHZlY3Rvci4gTmV4dCB5b3UnbGwgc2VlIHdheXMgdG8gc3BlZWQgdXAgdGhhdCBjb2RlLgoKIyMjIFRpbWluZ3MgLSBwcmUtYWxsb2NhdGlvbgoKSW4gdGhlIHByZXZpb3VzIGV4ZXJjaXNlLCBncm93aW5nIHRoZSB2ZWN0b3IgdG9vayBhcm91bmQgMiBzZWNvbmRzLiBIb3cgbG9uZyBkb2VzIGl0IHRha2Ugd2hlbiB3ZSBwcmUtYWxsb2NhdGUgdGhlIHZlY3Rvcj8gVGhlIHByZV9hbGxvY2F0ZSgpIGZ1bmN0aW9uIGlzIGRlZmluZWQgYmVsb3cuCmBgYHtyfQojIEZhc3QgY29kZQpwcmVfYWxsb2NhdGUgPC0gZnVuY3Rpb24obikgewogICAgeCA8LSBudW1lcmljKG4pICMgUHJlLWFsbG9jYXRlCiAgICBmb3IoaSBpbiAxOm4pIAogICAgICAgIHhbaV0gPC0gcm5vcm0oMSkKICAgIHgKfQpgYGAKYGBge3J9CiMgVXNlIDwtIHdpdGggc3lzdGVtLnRpbWUoKSB0byBzdG9yZSB0aGUgcmVzdWx0IGFzIHJlc19hbGxvY2F0ZQpzeXN0ZW0udGltZShyZXNfYWxsb2NhdGUgPC0gcHJlX2FsbG9jYXRlKG4pKQpgYGAKUHJlLWFsbG9jYXRpbmcgdGhlIHZlY3RvciBpcyBzaWduaWZpY2FudGx5IGZhc3RlciB0aGFuIGdyb3dpbmcgdGhlIHZlY3RvciEKCiMjIEltcG9ydGFuY2Ugb2YgdmVjdG9yaXppbmcgeW91ciBjb2RlCgojIyMgVmVjdG9yaXplZCBjb2RlOiBtdWx0aXBsaWNhdGlvbgoKVGhlIGZvbGxvd2luZyBwaWVjZSBvZiBjb2RlIGlzIHdyaXR0ZW4gbGlrZSB0cmFkaXRpb25hbCBDIG9yIEZvcnRyYW4gY29kZS4gSW5zdGVhZCBvZiB1c2luZyB0aGUgdmVjdG9yaXplZCB2ZXJzaW9uIG9mIG11bHRpcGxpY2F0aW9uLCBpdCB1c2VzIGEgZm9yIGxvb3AuCmBgYHtyfQp4IDwtIHJub3JtKDEwKQp4MiA8LSBudW1lcmljKGxlbmd0aCh4KSkKZm9yKGkgaW4gMToxMCkKICAgIHgyW2ldIDwtIHhbaV0gKiB4W2ldCmBgYApZb3VyIGpvYiBpcyB0byBtYWtlIHRoaXMgY29kZSBtb3JlICJSLWxpa2UiIGJ5IHZlY3Rvcml6aW5nIGl0LgpgYGB7cn0KIyBTdG9yZSB5b3VyIGFuc3dlciBhcyB4Ml9pbXAKeDJfaW1wIDwtIHggKiB4CmBgYApUaGUgbmV3IHZlcnNpb24gb2YgdGhlIGNvZGUgaXMgcXVpY2tlciB0byB0eXBlLCBlYXNpZXIgdG8gdW5kZXJzdGFuZCwgYW5kIGl0IHJ1bnMgZmFzdGVyIQoKIyMjIFZlY3Rvcml6ZWQgY29kZTogY2FsY3VsYXRpbmcgYSBsb2ctc3VtCgpBIGNvbW1vbiBvcGVyYXRpb24gaW4gc3RhdGlzdGljcyBpcyB0byBjYWxjdWxhdGUgdGhlIHN1bSBvZiBsb2cgcHJvYmFiaWxpdGllcy4gVGhlIGZvbGxvd2luZyBjb2RlIGNhbGN1bGF0ZXMgdGhlIGxvZy1zdW0gKHRoZSBzdW0gb2YgdGhlIGxvZ3MpLgpgYGB7cn0KIyB4IGlzIGEgdmVjdG9yIG9mIHByb2JhYmlsaXRpZXMKdG90YWwgPC0gMApmb3IoaSBpbiBzZXFfYWxvbmcoeCkpIAogICAgdG90YWwgPC0gdG90YWwgKyBsb2coeFtpXSkKYGBgCmBgYHtyfQojIEluaXRpYWwgY29kZQpuIDwtIDEwMAp0b3RhbCA8LSAwCnggPC0gcnVuaWYobikKZm9yKGkgaW4gMTpuKSAKICAgIHRvdGFsIDwtIHRvdGFsICsgbG9nKHhbaV0pCgojIFJld3JpdGUgaW4gYSBzaW5nbGUgbGluZS4gU3RvcmUgdGhlIHJlc3VsdCBpbiBsb2dfc3VtCmxvZ19zdW0gPC0gc3VtKGxvZyh4KSkKYGBgCmxvZygpIGNhbiB0YWtlIGluIGEgdmVjdG9yIGFuZCBvdXRwdXRzIGEgdmVjdG9yLiBzdW0oKSB0YWtlcyBpbiBhIHZlY3RvciBhbmQgcmV0dXJucyB0aGUgc3VtIG9mIGFsbCB0aGUgdmFsdWVzIGluIHRoZSB2ZWN0b3IuCgojIyBEYXRhIGZyYW1lcyBhbmQgbWF0cmljZXMKCiMjIyBEYXRhIGZyYW1lcyBhbmQgbWF0cmljZXMgLSBjb2x1bW4gc2VsZWN0aW9uCgpBbGwgdmFsdWVzIGluIGEgbWF0cml4IG11c3QgaGF2ZSB0aGUgc2FtZSBkYXRhIHR5cGUsIHdoaWNoIGhhcyBlZmZpY2llbmN5IGltcGxpY2F0aW9ucyB3aGVuIHNlbGVjdGluZyByb3dzIGFuZCBjb2x1bW5zLgoKICAgICMgV2hpY2ggaXMgZmFzdGVyLCBtYXRbLCAxXSBvciBkZlssIDFdPyAKICAgIG1pY3JvYmVuY2htYXJrKG1hdFssIDFdLCBkZlsgLDFdKQogICAgICAgICAgICAgZXhwciAgIG1pbiAgICBscSAgICBtZWFuIG1lZGlhbiAgICB1cSAgICBtYXggbmV2YWwKICAgIG1hdFssIDFdIDEuMDk4IDEuMjg2IDEuNDkxMjIgMS4zNjk1IDEuNTU2ICA2LjYzMSAgIDEwMAogICAgZGZbLCAxXSA2LjA2NCA2LjMyMSA3LjExODYzIDYuNTI2NSA2Ljc5MSA1NS41ODAgICAxMDAKCkJlY2F1c2UgYWxsIHZhbHVlcyBpbiBhIG1hdHJpeCBvYmplY3QgbXVzdCBiZSB0aGUgc2FtZSBkYXRhIHR5cGUsIGl0IGlzIG11Y2ggZmFzdGVyIHRvIGFjY2VzcyB0aGUgZmlyc3QgY29sdW1uIG9mIGEgbWF0cml4IHRoYW4gaXQgaXMgdG8gYWNjZXNzIHRoYXQgb2YgYSBkYXRhIGZyYW1lLgoKIyMjIFJvdyB0aW1pbmdzCgpVc2luZyBtaWNyb2JlbmNobWFyaygpLCBob3cgbG9uZyBkb2VzIGl0IHRha2UgdG8gc2VsZWN0IHRoZSBmaXJzdCByb3cgZnJvbSBlYWNoIG9mIHRoZXNlIG9iamVjdHM/CgpUbyBtYWtlIHRoZSBjb21wYXJpc29uIGZhaXIsIGp1c3QgdXNlIG1hdFsxLCBdIGFuZCBkZlsxLCBdLgoKICAgICMgV2hpY2ggaXMgZmFzdGVyLCBtYXRbMSwgXSBvciBkZlssIDFdPyAKICAgIG1pY3JvYmVuY2htYXJrKG1hdFsxLCBdLCBkZlsgLDFdKQogICAgVW5pdDogbWljcm9zZWNvbmRzCiAgICAgICAgZXhwciAgICAgIG1pbiAgICAgICAgbHEgICAgICAgbWVhbiAgIG1lZGlhbiAgICAgICAgdXEgICAgICBtYXggbmV2YWwKICAgIG1hdFsxLCBdICAgIDQuMTIyICAgIDQuNTY5NSAgIDExLjY5NTA0ICAgIDkuMzA1ICAgMTguMjE0NSAgIDM2Ljg4NSAgIDEwMAogICAgIGRmWzEsIF0gNDM5Ny43MDEgNDUzMC41NDMwIDQ4MzcuMTI0MDggNDU4OS4xOTIgNDcwMC4wNTcwIDc4MDkuNDMxICAgMTAwCgpBY2Nlc3NpbmcgYSByb3cgb2YgYSBkYXRhIGZyYW1lIGlzIG11Y2ggc2xvd2VyIHRoYW4gYWNjZXNzaW5nIHRoYXQgb2YgYSBtYXRyaXgsIG1vcmUgc28gdGhhbiB3aGVuIGFjY2Vzc2luZyBhIGNvbHVtbiBmcm9tIGVhY2ggZGF0YSB0eXBlLiBUaGlzIGlzIGJlY2F1c2UgdGhlIHZhbHVlcyBvZiBhIGNvbHVtbiBvZiBhIGRhdGEgZnJhbWUgbXVzdCBiZSB0aGUgc2FtZSBkYXRhIHR5cGUsIHdoZXJlYXMgdGhhdCBvZiBhIHJvdyBkb2Vzbid0IGhhdmUgdG8gYmUuICAKCiMgRGlhZ25vc2luZyBQcm9ibGVtczogQ29kZSBQcm9maWxpbmcKCiMjIFdoYXQgaXMgY29kZSBwcm9maWxpbmcKCiMjIyBQcm9mdmlzIGluIGFjdGlvbgoKYGBge3J9CiMgTG9hZCB0aGUgZGF0YSBzZXQKZGF0YShtb3ZpZXMsIHBhY2thZ2UgPSAiZ2dwbG90Mm1vdmllcyIpIAoKIyBMb2FkIHRoZSBwcm9mdmlzIHBhY2thZ2UKbGlicmFyeShwcm9mdmlzKQoKIyBQcm9maWxlIHRoZSBmb2xsb3dpbmcgY29kZSB3aXRoIHRoZSBwcm9mdmlzIGZ1bmN0aW9uCnByb2Z2aXMoewogICMgTG9hZCBhbmQgc2VsZWN0IGRhdGEKICBjb21lZGllcyA8LSBtb3ZpZXNbbW92aWVzJENvbWVkeSA9PSAxLCBdCgogICMgUGxvdCBkYXRhIG9mIGludGVyZXN0CiAgcGxvdChjb21lZGllcyR5ZWFyLCBjb21lZGllcyRyYXRpbmcpCgogICMgTG9lc3MgcmVncmVzc2lvbiBsaW5lCiAgbW9kZWwgPC0gbG9lc3MocmF0aW5nIH4geWVhciwgZGF0YSA9IGNvbWVkaWVzKQogIGogPC0gb3JkZXIoY29tZWRpZXMkeWVhcikKICAKICAjIEFkZCBmaXR0ZWQgbGluZSB0byB0aGUgcGxvdAogIGxpbmVzKGNvbWVkaWVzJHllYXJbal0sIG1vZGVsJGZpdHRlZFtqXSwgY29sID0gInJlZCIpCn0pICAgICAjIyBSZW1lbWJlciB0aGUgY2xvc2luZyBicmFja2V0cyEKYGBgCiMjIFByb2Z2aXM6IExhcmdlciBleGFtcGxlCgojIyMgQ2hhbmdlIHRoZSBkYXRhIGZyYW1lIHRvIGEgbWF0cml4CgpPbmUgb2YgdGhlIHBhcnRzIG9mIHRoZSBjb2RlIHRoYXQgcHJvZnZpcyBoaWdobGlnaHRlZCB3YXMgdGhlIGxpbmUgd2hlcmUgd2UgZ2VuZXJhdGVkIHRoZSBwb3NzaWJsZSBkaWNlIHJvbGxzIGFuZCBzdG9yZWQgdGhlIHJlc3VsdHMgaW4gYSBkYXRhIGZyYW1lOgoKICAgIGRmIDwtIGRhdGEuZnJhbWUoZDEgPSBzYW1wbGUoMTo2LCAzLCByZXBsYWNlID0gVFJVRSksCiAgICAgICAgICAgICAgICBkMiA9IHNhbXBsZSgxOjYsIDMsIHJlcGxhY2UgPSBUUlVFKSkKCldlIGNhbiBvcHRpbWl6ZSB0aGlzIGNvZGUgYnkgbWFraW5nIHR3byBpbXByb3ZlbWVudHM6CgotIFN3aXRjaGluZyBmcm9tIGEgZGF0YSBmcmFtZSB0byBhIG1hdHJpeAotIEdlbmVyYXRpbmcgdGhlIDYgZGljZSByb2xscyBpbiBhIHNpbmdsZSBzdGVwCgpUaGlzIGdpdmVzOgoKICAgIG0gPC0gbWF0cml4KHNhbXBsZSgxOjYsIDYsIHJlcGxhY2UgPSBUUlVFKSwgbmNvbCA9IDIpCgpgYGB7cn0KIyBMb2FkIHRoZSBtaWNyb2JlbmNobWFyayBwYWNrYWdlCmxpYnJhcnkobWljcm9iZW5jaG1hcmspCgojIFRoZSBwcmV2aW91cyBkYXRhIGZyYW1lIHNvbHV0aW9uIGlzIGRlZmluZWQKIyBkKCkgU2ltdWxhdGVzIDYgZGljZXMgcm9sbHMKZCA8LSBmdW5jdGlvbigpIHsKICBkYXRhLmZyYW1lKAogICAgZDEgPSBzYW1wbGUoMTo2LCAzLCByZXBsYWNlID0gVFJVRSksCiAgICBkMiA9IHNhbXBsZSgxOjYsIDMsIHJlcGxhY2UgPSBUUlVFKQogICkKfQoKIyBDb21wbGV0ZSB0aGUgbWF0cml4IHNvbHV0aW9uCm0gPC0gZnVuY3Rpb24oKSB7CiAgbWF0cml4KHNhbXBsZSgxOjYsIDYsIHJlcGxhY2UgPSBUUlVFKSwgbmNvbCA9IDIpCn0KCiMgVXNlIG1pY3JvYmVuY2htYXJrIHRvIHRpbWUgbSgpIGFuZCBkKCkKbWljcm9iZW5jaG1hcmsoCiBkYXRhLmZyYW1lX3NvbHV0aW9uID0gZCgpLAogbWF0cml4X3NvbHV0aW9uICAgICA9IG0oKQopCmBgYAojIyMgQ2FsY3VsYXRpbmcgcm93IHN1bXMKClRoZSBzZWNvbmQgYm90dGxlbmVjayBpZGVudGlmaWVkIHdhcyBjYWxjdWxhdGluZyB0aGUgcm93IHN1bXMuCgogICAgdG90YWwgPC0gYXBwbHkoZCwgMSwgc3VtKQoKSW4gdGhlIHByZXZpb3VzIGV4YW1wbGUgd2Ugc3dpdGNoZWQgdGhlIHVuZGVybHlpbmcgb2JqZWN0IHRvIGEgbWF0cml4LiBUaGlzIG1ha2VzIHRoZSBhYm92ZSBhcHBseSBvcGVyYXRpb24gdGhyZWUgdGltZXMgZmFzdGVyLiBCdXQgdGhlcmUncyBvbmUgZnVydGhlciBvcHRpbWl6YXRpb24gd2UgY2FuIHVzZSAtIHN3aXRjaCBhcHBseSgpIHdpdGggcm93U3VtcygpCgpgYGB7cn0KIyBFeGFtcGxlIGRhdGEKcm9sbHMgPC0gbWF0cml4KGMoMywgNiwgNSwgMSwgNSwgNCksIG5jb2wgPSAyKQpyb2xscwojIERlZmluZSB0aGUgcHJldmlvdXMgc29sdXRpb24gCmFwcCA8LSBmdW5jdGlvbih4KSB7CiAgICBhcHBseSh4LCAxLCBzdW0pCn0KCiMgRGVmaW5lIHRoZSBuZXcgc29sdXRpb24Kcl9zdW0gPC0gZnVuY3Rpb24oeCkgewogICAgcm93U3Vtcyh4KQp9CgojIENvbXBhcmUgdGhlIG1ldGhvZHMKbWljcm9iZW5jaG1hcmsoCiAgICBhcHBfc29sID0gYXBwKHJvbGxzKSwKICAgIHJfc3VtX3NvbCA9IHJfc3VtKHJvbGxzKQopCmBgYAojIyMgVXNlICYmIGluc3RlYWQgb2YgJgoKVG8gZGV0ZXJtaW5lIGlmIGJvdGggZGljZSBhcmUgdGhlIHNhbWUsIHRoZSBtb3ZlX3NxdWFyZSgpIGZ1bmN0aW9uIHVzZXMgaWYgc3RhdGVtZW50cy4KCiAgICBpZiAoaXNfZG91YmxlWzFdICYgaXNfZG91YmxlWzJdICYgaXNfZG91YmxlWzNdKSB7CiAgICBjdXJyZW50IDwtIDExICMgR28gVG8gSmFpbCAtIHNxdWFyZSAxMSA9PSBKYWlsCiAgICB9CgpUaGUgJiBvcGVyYXRvciB3aWxsIGFsd2F5cyBldmFsdWF0ZSBib3RoIGl0cyBhcmd1bWVudHMuIFRoYXQgaXMsIGlmIHlvdSB0eXBlIHggJiB5LCBSIHdpbGwgYWx3YXlzIHRyeSB0byB3b3JrIG91dCB3aGF0IHggYW5kIHkgYXJlLiBUaGVyZSBhcmUgc29tZSBjYXNlcyB3aGVyZSB0aGlzIGlzIGluZWZmaWNpZW50LiBGb3IgZXhhbXBsZSwgaWYgeCBpcyBGQUxTRSwgdGhlbiB4ICYgeSB3aWxsIGFsd2F5cyBiZSBGQUxTRSwgcmVnYXJkbGVzcyBvZiB0aGUgdmFsdWUgb2YgeS4gVGh1cywgeW91IGNhbiBzYXZlIGEgbGl0dGxlIHByb2Nlc3NpbmcgdGltZSBieSBub3QgY2FsY3VsYXRpbmcgaXQuIFRoZSAmJiBvcGVyYXRvciB0YWtlcyBhZHZhbnRhZ2Ugb2YgdGhpcyB0cmljaywgYW5kIGRvZXNuJ3QgYm90aGVyIHRvIGNhbGN1bGF0ZSB5IGlmIGl0IGRvZXNuJ3QgbWFrZSBhIGRpZmZlcmVuY2UgdG8gdGhlIG92ZXJhbGwgcmVzdWx0LgoKSW4gdGhpcyBjb2RlLCBpZiBpc19kb3VibGVbMV0gaXMgRkFMU0Ugd2UgZG9uJ3QgbmVlZCB0byBldmFsdWF0ZSBpc19kb3VibGVbMl0gb3IgaXNfZG91YmxlWzNdLCBzbyB3ZSBjYW4gZ2V0IGEgc3BlZWR1cCBieSBzd2FwcGluZyAmIGZvciAmJi4KCk9uZSB0aGluZyB0byBub3RlIGlzIHRoYXQgJiYgb25seSB3b3JrcyBvbiBzaW5nbGUgbG9naWNhbCB2YWx1ZXMsIGkuZS4sIGxvZ2ljYWwgdmVjdG9ycyBvZiBsZW5ndGggMSAobGlrZSB5b3Ugd291bGQgcGFzcyBpbnRvIGFuIGlmIGNvbmRpdGlvbiksIGJ1dCAmIGFsc28gd29ya3Mgb24gdmVjdG9ycyBvZiBsZW5ndGggZ3JlYXRlciB0aGFuIDEuCgpgYGB7cn0KaXNfZG91YmxlID0gYyhGQUxTRSwgVFJVRSwgVFJVRSkKYGBgCgoKYGBge3J9CmlzX2RvdWJsZQoKIyBEZWZpbmUgdGhlIHByZXZpb3VzIHNvbHV0aW9uCm1vdmUgPC0gZnVuY3Rpb24oaXNfZG91YmxlKSB7CiAgICBpZiAoaXNfZG91YmxlWzFdICYgaXNfZG91YmxlWzJdICYgaXNfZG91YmxlWzNdKSB7CiAgICAgICAgY3VycmVudCA8LSAxMSAjIEdvIFRvIEphaWwKICAgIH0KfQoKIyBEZWZpbmUgdGhlIGltcHJvdmVkIHNvbHV0aW9uCmltcHJvdmVkX21vdmUgPC0gZnVuY3Rpb24oaXNfZG91YmxlKSB7CiAgICBpZiAoaXNfZG91YmxlWzFdICYmIGlzX2RvdWJsZVsyXSAmJiBpc19kb3VibGVbM10pIHsKICAgICAgICBjdXJyZW50IDwtIDExICMgR28gVG8gSmFpbAogICAgfQp9CmBgYAoKCmBgYHtyfQojIG1pY3JvYmVuY2htYXJrIGJvdGggc29sdXRpb25zCiMgVmVyeSBvY2Nhc3Npb25hbGx5IHRoZSBpbXByb3ZlZCBzb2x1dGlvbiBpcyBhY3R1YWxseSBhIGxpdHRsZSBzbG93ZXIKIyBUaGlzIGlzIGp1c3QgcmFuZG9tIGNoYW5jZQptaWNyb2JlbmNobWFyayhtb3ZlLCBpbXByb3ZlZF9tb3ZlLCB0aW1lcyA9IDFlNSkKYGBgCiMgVHVyYm8gQ2hhcmdlZCBDb2RlOiBQYXJhbGxlbCBQcm9ncmFtbWluZwoKIyMgQ1BVcyAtIHdoeSBkbyB3ZSBoYXZlIG1vcmUgdGhhbiBvbmUKCiMjIyBIb3cgbWFueSBjb3JlcyBkb2VzIHRoaXMgbWFjaGluZSBoYXZlPwoKVGhlIHBhcmFsbGVsIHBhY2thZ2UgaGFzIGEgZnVuY3Rpb24gZGV0ZWN0Q29yZXMoKSB0aGF0IGRldGVybWluZXMgdGhlIG51bWJlciBvZiBjb3JlcyBpbiBhIG1hY2hpbmUuCgpgYGB7cn0KIyBMb2FkIHRoZSBwYXJhbGxlbCBwYWNrYWdlCmxpYnJhcnkocGFyYWxsZWwpCgojIFN0b3JlIHRoZSBudW1iZXIgb2YgY29yZXMgaW4gdGhlIG9iamVjdCBub19vZl9jb3Jlcwpub19vZl9jb3JlcyA8LSBkZXRlY3RDb3JlcygpCgojIFByaW50IG5vX29mX2NvcmVzCm5vX29mX2NvcmVzCmBgYAojIyBXaGF0IHNvcnQgb2YgcHJvYmxlbXMgYmVuZWZpdCBmcm9tIHBhcmFsbGVsIGNvbXB1dGluZz8KCiMjIyBDYW4gdGhpcyBsb29wIHJ1biBpbiBwYXJhbGxlbD8KCgpUaGUgZm9sbG93aW5nIHBpZWNlIG9mIGNvZGUgaW1wbGVtZW50cyBhIHNpbXBsZSBkaWNlIGdhbWUuIFRoZSBnYW1lIGlzIGFzIGZvbGxvd3M6CgotIEluaXRpYWxpemU6IHRvdGFsIDwtIDAuCi0gUm9sbCBhIHNpbmdsZSBkaWUgYW5kIGFkZCBpdCB0byB0b3RhbC4KLSBJZiB0b3RhbCBpcyBldmVuLCByZXNldCB0b3RhbCB0byB6ZXJvLgotIElmIHRvdGFsIGlzIGdyZWF0ZXIgdGhhbiAxMCwgdGhlIGdhbWUgZmluaXNoZXMuCgpgYGB7cn0KdG90YWwgPC0gbm9fb2Zfcm9sbHMgPC0gMCAjIEluaXRpYWxpc2UKd2hpbGUodG90YWwgPCAxMCkgewogIHRvdGFsIDwtIHRvdGFsICsgc2FtcGxlKDE6NiwgMSkKCiAgaWYodG90YWwgJSUgMiA9PSAwKSB0b3RhbCA8LSAwICAjIElmIGV2ZW4uIFJlc2V0IHRvIDAKCiAgbm9fb2Zfcm9sbHMgPC0gbm9fb2Zfcm9sbHMgKyAxCn0Kbm9fb2Zfcm9sbHMKYGBgCkRvIHlvdSB0aGluayB0aGlzIGFsZ29yaXRobSBjYW4gYmUgKGVhc2lseSkgcnVuIGluIHBhcmFsbGVsPwpObzogaXQncyBhIHNlcXVlbnRpYWwgYWxnb3JpdGhtLiBUaGUgaXRoIHZhbHVlIGRlcGVuZHMgb24gdGhlIHByZXZpb3VzIHZhbHVlLgpwcmVzcwoKSWYgd2Ugd3JhcCB0aGUgZ2FtZSBpbiBhIGZ1bmN0aW9uOgpgYGB7cn0KcGxheSA8LSBmdW5jdGlvbigpIHsKICB0b3RhbCA8LSBub19vZl9yb2xscyA8LSAwCiAgd2hpbGUodG90YWwgPCAxMCkgewogICAgdG90YWwgPC0gdG90YWwgKyBzYW1wbGUoMTo2LCAxKQoKICAgICMgSWYgZXZlbi4gUmVzZXQgdG8gMAogICAgaWYodG90YWwgJSUgMiA9PSAwKSB0b3RhbCA8LSAwIAogICAgbm9fb2Zfcm9sbHMgPC0gbm9fb2Zfcm9sbHMgKyAxCiAgfQogIG5vX29mX3JvbGxzCn0KYGBgCgoKQW5kIGNvbnN0cnVjdCBhIGxvb3AgdG8gcGxheSB0aGUgZ2FtZQpgYGB7cn0KcmVzdWx0cyA8LSBudW1lcmljKDEwMCkKZm9yKGkgaW4gc2VxX2Fsb25nKHJlc3VsdHMpKSAKICAgIHJlc3VsdHNbaV0gPC0gcGxheSgpCnJlc3VsdHMKYGBgCmBgYHtyfQojcmV2ZXJzZQpyZXN1bHRzIDwtIG51bWVyaWMoMTAwKQpmb3IoaSBpbiBzZXFfYWxvbmcoLXJlc3VsdHMpKSAKICAgIHJlc3VsdHNbaV0gPC0gcGxheSgpCnJlc3VsdHMKYGBgClllcywgdGhpcyBpcyBlbWJhcnJhc3NpbmdseSBwYXJhbGxlbC4gV2UgY2FuIHNpbXVsYXRlIHRoZSBnYW1lcyBpbiBhbnkgb3JkZXIuCnByZXNzCkluZGVwZW5kZW50IE1vbnRlIENhcmxvIHNpbXVsYXRpb25zIGFyZSBpZGVhbCBmb3IgcGFyYWxsZWwgY29tcHV0YXRpb24uCgojIyBUaGUgcGFyYWxsZWwgcGFja2FnZSBwYXJBcHBseQoKU29tZXRpbWVzIHBhckFwcGx5KCkgaXMgZmFzdGVyOyBpdCBqdXN0IGRlcGVuZHMgb24gdGhlIHByb2JsZW0uCgojIyMgTW92aW5nIHRvIHBhckFwcGx5CgpUbyBydW4gY29kZSBpbiBwYXJhbGxlbCB1c2luZyB0aGUgcGFyYWxsZWwgcGFja2FnZSwgdGhlIGJhc2ljIHdvcmtmbG93IGhhcyB0aHJlZSBzdGVwcy4KCjEuIENyZWF0ZSBhIGNsdXN0ZXIgdXNpbmcgbWFrZUNsdXN0ZXIoKS4KMi4gRG8gc29tZSB3b3JrLgozLiBTdG9wIHRoZSBjbHVzdGVyIHVzaW5nIHN0b3BDbHVzdGVyKCkuCgpUaGUgc2ltcGxlc3Qgd2F5IHRvIG1ha2UgYSBjbHVzdGVyIGlzIHRvIHBhc3MgYSBudW1iZXIgdG8gbWFrZUNsdXN0ZXIoKS4gVGhpcyBjcmVhdGVzIGEgY2x1c3RlciBvZiB0aGUgZGVmYXVsdCB0eXBlLCBydW5uaW5nIHRoZSBjb2RlIG9uIHRoYXQgbWFueSBjb3Jlcy4KCmBgYHtyfQpkZCA8LSByZWFkUkRTKCJkZC5yZHMiKQpgYGAKVGhlIG9iamVjdCBkZCBpcyBhIGRhdGEgZnJhbWUgd2l0aCAxMCBjb2x1bW5zIGFuZCAxMDAgcm93cy4gVGhlIGZvbGxvd2luZyBjb2RlIHVzZXMgYXBwbHkoKSB0byBjYWxjdWxhdGUgdGhlIGNvbHVtbiBtZWRpYW5zOgoKICAgIGFwcGx5KGRkLCAyLCBtZWRpYW4pCiAgICAKVG8gcnVuIHRoaXMgaW4gcGFyYWxsZWwsIHlvdSBzd2FwIGFwcGx5KCkgZm9yIHBhckFwcGx5KCkuIFRoZSBhcmd1bWVudHMgdG8gdGhpcyBmdW5jdGlvbiBhcmUgdGhlIHNhbWUsIGV4Y2VwdCB0aGF0IGl0IHRha2VzIGEgY2x1c3RlciBhcmd1bWVudCBiZWZvcmUgdGhlIHVzdWFsIGFwcGx5KCkgYXJndW1lbnRzLgpgYGB7cn0KIyBEZXRlcm1pbmUgdGhlIG51bWJlciBvZiBhdmFpbGFibGUgY29yZXMKZGV0ZWN0Q29yZXMoKQoKIyBDcmVhdGUgYSBjbHVzdGVyIHZpYSBtYWtlQ2x1c3RlcgpjbCA8LSBtYWtlQ2x1c3RlcigyKQoKIyBQYXJhbGxlbGl6ZSB0aGlzIGNvZGUKcGFyQXBwbHkoY2wsIGRkLCAyLCBtZWRpYW4pCgojIFN0b3AgdGhlIGNsdXN0ZXIKc3RvcENsdXN0ZXIoY2wpCmBgYAojIyBUaGUgcGFyYWxsZWwgcGFja2FnZSAtIHBhclNhcHBseQoKIyMjIFVzaW5nIHBhclNhcHBseSgpCgpXZSBwcmV2aW91c2x5IHBsYXllZCB0aGUgZm9sbG93aW5nIGdhbWU6CgotIEluaXRpYWxpemU6IHRvdGFsID0gMC4KLSBSb2xsIGEgc2luZ2xlIGRpZSBhbmQgYWRkIGl0IHRvIHRvdGFsLgotIElmIHRvdGFsIGlzIGV2ZW4sIHJlc2V0IHRvdGFsIHRvIHplcm8uCi0gSWYgdG90YWwgaXMgZ3JlYXRlciB0aGFuIDEwLiBUaGUgZ2FtZSBmaW5pc2hlcy4KClRoZSBnYW1lIGNvdWxkIGJlIHNpbXVsYXRlZCB1c2luZyB0aGUgcGxheSgpIGZ1bmN0aW9uOgpgYGB7cn0KcGxheSA8LSBmdW5jdGlvbigpIHsKICB0b3RhbCA8LSBub19vZl9yb2xscyA8LSAwCiAgd2hpbGUodG90YWwgPCAxMCkgewogICAgdG90YWwgPC0gdG90YWwgKyBzYW1wbGUoMTo2LCAxKQoKICAgICMgSWYgZXZlbi4gUmVzZXQgdG8gMAogICAgaWYodG90YWwgJSUgMiA9PSAwKSB0b3RhbCA8LSAwIAogICAgbm9fb2Zfcm9sbHMgPC0gbm9fb2Zfcm9sbHMgKyAxCiAgfQogIG5vX29mX3JvbGxzCn0KYGBgClRvIHNpbXVsYXRlIHRoZSBnYW1lIDEwMCB0aW1lcywgd2UgY291bGQgdXNlIGEgZm9yIGxvb3Agb3Igc2FwcGx5KCk6CmBgYHtyfQpyZXMgPC0gc2FwcGx5KDE6MTAwLCBmdW5jdGlvbihpKSBwbGF5KCkpCmBgYApUaGlzIGlzIHBlcmZlY3QgZm9yIHJ1bm5pbmcgaW4gcGFyYWxsZWwhCgpUbyBtYWtlIGZ1bmN0aW9ucyBhdmFpbGFibGUgb24gYSBjbHVzdGVyLCB5b3UgdXNlIHRoZSBjbHVzdGVyRXhwb3J0KCkgZnVuY3Rpb24uIFRoaXMgdGFrZXMgYSBjbHVzdGVyIGFuZCBhIHN0cmluZyBuYW1pbmcgdGhlIGZ1bmN0aW9uLgoKICAgIGNsdXN0ZXJFeHBvcnQoY2wsICJzb21lX2Z1bmN0aW9uIikKCmBgYHtyfQpsaWJyYXJ5KCJwYXJhbGxlbCIpCiMgQ3JlYXRlIGEgY2x1c3RlciB2aWEgbWFrZUNsdXN0ZXIgKDIgY29yZXMpCmNsIDwtIG1ha2VDbHVzdGVyKDIpCgojIEV4cG9ydCB0aGUgcGxheSgpIGZ1bmN0aW9uIHRvIHRoZSBjbHVzdGVyCmNsdXN0ZXJFeHBvcnQoY2wsICJwbGF5IikKCiMgUmUtd3JpdGUgc2FwcGx5IGFzIHBhclNhcHBseQpyZXMgPC0gcGFyU2FwcGx5KGNsLCAxOjEwMCwgZnVuY3Rpb24oaSkgcGxheSgpKQoKIyBTdG9wIHRoZSBjbHVzdGVyCnN0b3BDbHVzdGVyKGNsKQpgYGAKY2x1c3RlckV4cG9ydCgpIG1ha2VzIGl0IGVhc3kgdG8gcnVuIHlvdXIgZnVuY3Rpb25zIGluc2lkZSBhIGNsdXN0ZXIuCgojIyMgVGltaW5ncyBwYXJTYXBwbHkoKQoKUnVubmluZyB0aGUgZGljZSBnYW1lIGlzIGVtYmFycmFzc2luZ2x5IHBhcmFsbGVsLiBUaGVzZSB0eXBlcyBvZiBzaW11bGF0aW9ucyB1c3VhbGx5IChidXQgbm90IGFsd2F5cykgcHJvZHVjZSBhIGdvb2Qgc3BlZWQtdXAuIEFzIGJlZm9yZSwgd2UgY2FuIHVzZSBtaWNyb2JlbmNobWFyaygpIG9yIHN5c3RlbS50aW1lKCkuIEZvciBzaW1wbGljaXR5LCB3ZSdsbCB1c2Ugc3lzdGVtLnRpbWUoKSBpbiB0aGlzIGV4ZXJjaXNlLgpgYGB7cn0KIyBTZXQgdGhlIG51bWJlciBvZiBnYW1lcyB0byBwbGF5Cm5vX29mX2dhbWVzIDwtIDFlNQoKIyMgVGltZSBzZXJpYWwgdmVyc2lvbgpzeXN0ZW0udGltZShzZXJpYWwgPC0gc2FwcGx5KDE6bm9fb2ZfZ2FtZXMsIGZ1bmN0aW9uKGkpIHBsYXkoKSkpCgojIyBTZXQgdXAgY2x1c3RlcgpjbCA8LSBtYWtlQ2x1c3RlcigyKQpjbHVzdGVyRXhwb3J0KGNsLCAicGxheSIpCgojIyBUaW1lIHBhcmFsbGVsIHZlcnNpb24Kc3lzdGVtLnRpbWUocGFyIDwtIHBhclNhcHBseShjbCwgMTpub19vZl9nYW1lcywgZnVuY3Rpb24oaSkgcGxheSgpKSkKCiMjIFN0b3AgY2x1c3RlcgpzdG9wQ2x1c3RlcihjbCkKYGBgCgo=