Artifact:   Corpse Reviver
1 Install
1.1 Virtual  Box
1.2 Manual
2 Optimizing a program
3 Benchmark
4 Empirical results
4.1 Figures
4.2 Supporting claims
7.7.0.10

Artifact: Corpse Reviver

Cameron Moy


Phúc C. Nguyễn


Sam Tobin-Hochstadt


David Van Horn

This artifact accompanies the paper “Corpse Reviver: Sound and Efficient Gradual Typing via Contract Verification.” Artifact evaluation phase I consists of the Install and Optimizing a program sections. Artifact evaluation phase II consists of the Benchmark and Empirical results sections.

1 Install

You can either run the artifact in a VirtualBox virtual machine that includes all the necessary dependencies or manually install the artifact. We recommend the virtual machine.

1.1 VirtualBox

1.2 Manual

2 Optimizing a program

You can write your own gradually typed program and see how well SCV-CR can optimize it. Within the artifact directory there is an examples/ subdirectory with some files. Change to this directory. A file called data.rkt has the following code:

#lang typed/racket/base
 
(provide random-adders)
 
(: random-adders : (-> Natural (Listof (-> Integer Integer))))
(define (random-adders n)
  (for/list ([i (in-range n)])
    (λ ([x : Integer]) (+ (random 10) x))))

Figure 1: The data.rkt file.

This typed module provides a function called random-adders that generates a list of random “adders,” functions that add its argument to a random number. A file called main.rkt has the following code:

#lang racket/base
 
(require "data.rkt")
 
(define iterations 1000)
(define n 5000)
 
(time
 (for ([i (in-range iterations)])
   (for/sum ([f (in-list (random-adders n))])
     (f (random 10)))))

Figure 2: The main.rkt file.

This untyped module generates a list of 5,000 adders from data.rkt and computes a sum with them. It does this for 1,000 iterations and measures how long it takes.

Run the program and use Racket’s profiler to see the cost of contracts due to gradual typing. On our machine, the program takes about 10 seconds with about 67% of that time due to contract checking.

$ raco make data.rkt main.rkt

$ racket main.rkt

cpu time: 9398 real time: 9412 gc time: 874

$ raco contract-profile main.rkt

Running time is 66.66% contracts

8014/12022 ms

 

(-> natural? (listof (-> exact-integer? any)))     8013.5 ms

data.rkt:3:9

    random-adders                                  8013.5 ms

$ raco decompile main.rkt > unopt.out

Run SCV-CR on the modules and see the performance difference. The raco scv-cr command creates optimized bytecode by removing contracts that are proven safe statically. It can be used as replacement for raco make. In this simple example, SCV-CR proves that the contract cannot be violated and removes it automatically.

$ raco scv-cr data.rkt main.rkt

$ racket main.rkt

cpu time: 1256 real time: 1258 gc time: 28

$ raco contract-profile main.rkt

cpu time: 1406 real time: 1410 gc time: 8

Running time is 0% contracts

0/2340 ms

$ raco decompile main.rkt > opt.out

For further confirmation that the contract was removed, you can compare the decompiled outputs in unopt.out and opt.out.

$ diff opt.out unopt.out | grep "random-adders '5000" -A 2

<          (let ((local67 (random-adders '5000)))

---

>          (let ((local73 (lifted/12.1 '5000)))

On top is the optimized version that directly calls random-adders. The bottom, by contrast, calls lifted/12.1 instead. If you chase down this definition, it is the contracted version of random-adders.

If SCV is unable to prove that a contract won’t be violated, then that contract is not removed. A file called main-imprecise.rkt has the following code:

#lang racket/base
 
(require "data.rkt")
 
(define iterations 1000)
(define n 5000)
 
(time
 (for ([i (in-range iterations)])
   (for/sum ([f (in-list (random-adders n))])
     (f (if (string<=? "a" "b")
            (random 10)
            "")))))

Figure 3: The main-imprecise.rkt file.

The only difference between main.rkt and main-imprecise.rkt is that the imprecise version contains a conditional that will violate the type of random-adders if the string a is not lexicographically smaller than the string b. Since it is lexicographically smaller, this branch is never executed and the contract check is unnecessary. However, SCV cannot prove this is so, and thus the contract is not removed.

This is reflected in the performance of the “optimized” program; the profiler also confirms that the contract was not optimized away.

$ raco scv-cr data.rkt main-imprecise.rkt

$ racket main-imprecise.rkt

cpu time: 8041 real time: 8081 gc time: 550

$ raco contract-profile main-imprecise.rkt

cpu time: 9037 real time: 9172 gc time: 522

Running time is 72.02% contracts

7034/9767 ms

 

(-> natural? (listof (-> exact-integer? any)))      7034 ms

data.rkt:3:9

    random-adders                                   7034 ms

See the SCV-CR documentation for the full list of options to raco scv-cr.

3 Benchmark

Execute the following commands to run the entire benchmark suite:

$ cd corpse-reviver/corpse-reviver-artifact

$ raco scv-cr-benchmark -c 5 -b -i 5 -S 2 -R 2 -o data/baseline

$ raco scv-cr-benchmark -r -o data/opt

These commands will create a series of JSON-formatted measurement files—one for each benchmark sample. Baseline measurements, without applying SCV-CR, are placed in data/baseline. Measurements after applying SCV-CR are placed in data/opt.

We have found that on a decent laptop, the benchmarks with these parameters terminate in about 14 hours. If you want to run this script overnight, combine the last two commands with &&.

The suggested flags will sample from larger benchmarks instead of exhaustively measuring them all. Different parameter choices will yield different benchmarking times. Changing -R from 2 to 1 will halve benchmark completion time. We don’t recommend going any lower than this setting. If you want to run the benchmarks for longer, increasing any one of -S, -R, or -c, will result in a more accurate result. See the benchmarking documentation for more information.

After this is done, you can generate this page with your benchmark results.

$ raco setup corpse-reviver-artifact

$ raco docs T:corpse-reviver-artifact

When raco docs opens a browser, select the first result. The page you’re currently reading should appear, but the figures and claims in Empirical results will be generated from your data instead of ours (available in the author_data directory).

4 Empirical results

Compare the figures and claims generated from your benchmarking results to those generated from ours. Depending on your choice of parameters, the results may differ more or less. However, they should generally support the thesis of the paper—that across a wide range of benchmarks, contract verification can effectively reduce the performance overhead of sound gradual typing.

4.1 Figures

Figure 4: Overhead of gradual typing over the whole benchmark suite. The purple () curve is Typed Racket and the orange () curve is SCV-CR. The log-scaled x-axis indicates slowdown factor compared against the fully-untyped configuration, while the y-axis indicates the percent of configurations incurring that slowdown. Each benchmark is allocated an equal proportion of the y-axis. Higher is better.

Figure 5: Performance lattices for sieve (left) and zombie (right). Each point in the lattice is a configuration of the benchmark, where a white box is an untyped module and a black box is a typed module. The numbers below indicate the slowdown factor for Typed Racket 7.8 on the left and SCV-CR on the right.

Figure 6: Overhead of gradual typing for each benchmark individually. The purple curve is Typed Racket and the orange curve is SCV-CR. Each point (x, y) indicates an x-factor slowdown over the fully-untyped configuration for y% of configurations. The dashed lines between 1 and 2 occur at increments of 0.2 and between 2 and 10 at increments of 2.

Figure 7: Exact time it takes each configuration to execute. The purple curve is Typed Racket and the orange curve is SCV-CR. The x-axis is binned by the number of typed modules in a configuration. The y-axis is time to execute in seconds.

Racket Overhead

SCV-CR Overhead

SCV-CR Analyze

SCV-CR Compile

Benchmark

Max

Mean

Max

Mean

Mean ± 𝜎 (s)

Mean ± 𝜎 (s)

fsm

2.8×

1.7×

1.1×

0.7×

25 ± 3

16 ± 4

gregor

2.1×

1.7×

1.3×

1.1×

879 ± 858

52 ± 11

kcfa

4.3×

2.5×

1.1×

35 ± 3

34 ± 4

lnm

1.1×

0.8×

0.7×

125 ± 16

44 ± 6

morsecode

1.8×

1.2×

0.9×

18 ± 9

10 ± 3

sieve

18.6×

6.4×

5 ± 2

4 ± 2

snake

16.4×

8.2×

1.6×

94 ± 43

22 ± 4

suffixtree

32.1×

14.1×

0.8×

28 ± 12

21 ± 4

synth

73.6×

32.6×

1.4×

1.1×

131 ± 51

34 ± 6

tetris

11.1×

1.3×

190 ± 128

25 ± 6

zombie

59.8×

27.8×

1.3×

1.1×

55 ± 19

15 ± 4

zordoz

3.9×

1.8×

0.9×

361 ± 8

66 ± 8

Figure 8: Maximum and mean overhead for Racket 7.8 and SCV-CR for each benchmark. Red indicates a slowdown ≥3× while green indicates a slowdown ≤1.3×.

4.2 Supporting claims