I've started on something of an experiment to explore how I can improve Clojure start-up time. I was curious what impact compression level has on start-up latency for JVM applications. A simple clojure application was created (+ 1 2 3) and packaged into an uberjar. Four Zip compression levels were tested 0, 1, 6, and 9. The uberjar was unpacked and then repacked using the linux command line zip tool as follows:
zip -r -0 ../main_c0.jar *
zip -r -1 ../main_c1.jar *
zip -r -6 ../main_c6.jar *
zip -r -9 ../main_c9.jar *
All applications, screen-locks, system sleep, and anti-virus were closed to minimise interference. A test was devised which executes the 4 uberjars 100 times each. I will refer to each uberjars execution set as a run. Every 10 executions a sleep of 30s is used to settle the system as well as writing an 8MB file to flush disk caches. See the test runner shell script below for exact details. The test was left to execute and the results from the first run were discarded as a "warm-up" cycle.
The results are below in ms for the execution time.
| (ms) | C0 | C1 | C6 | C9 |
|---|---|---|---|---|
| 25PCTL | 570 | 616 | 613 | 613 |
| 75PCTL | 600 | 655 | 643 | 643 |
| Max | 650 | 730 | 702 | 700 |
Overall 0 compression appears to shave 50ms from load time. Furthermore for the sample size it appears to be statistically significant.
Compressing with level 0 has an obvious tradeoff for the distribution of artefact over the internet. I'd be curious to see what impact a faster algorithm such as Snappy or LZ4 has on class load performance.
For the next test I'll see what happens when the java byte code is loaded into a byte array and a custom class loader is used. This will be a baseline measurement to demonstrate whether I can write a ClassLoader which is faster than the default JAR loader.
For future tests I would probably reduce the number of runs in the warm-up to 10 or 20 executions per jar rather than 100.
#!/bin/sh
JARS="main_c0.jar main_c1.jar main_c6.jar main_c9.jar"
for JAR in $JARS; do
B=0
OUT="${JAR}-results-r1.txt"
SORTED="${JAR}-sorted-results-r1.txt"
echo "starting test ${OUT}"
while [ $B -lt 10 ]; do
# try to flush the disk cache
dd if=/dev/zero of=/tmp/rando.txt bs=4096 count=2048
rm /tmp/rando.txt
A=0
while [ $A -lt 10 ]; do
java -jar $JAR >> "$OUT"
A=`expr $A + 1`
done
sleep 30
B=`expr $B + 1`
done
cat $OUT | sort -n > $SORTED
done
for JAR in $JARS; do
B=0
OUT="${JAR}-results-r2.txt"
SORTED="${JAR}-sorted-results-r2.txt"
echo "starting test ${OUT}"
while [ $B -lt 10 ]; do
# try to flush the disk cache
dd if=/dev/zero of=/tmp/rando.txt bs=4096 count=2048
rm /tmp/rando.txt
A=0
while [ $A -lt 10 ]; do
java -jar $JAR >> "$OUT"
A=`expr $A + 1`
done
sleep 30
B=`expr $B + 1`
done
cat $OUT | sort -n > $SORTED
done
for JAR in $JARS; do
B=0
OUT="${JAR}-results-r3.txt"
SORTED="${JAR}-sorted-results-r3.txt"
echo "starting test ${OUT}"
while [ $B -lt 10 ]; do
# try to flush the disk cache
dd if=/dev/zero of=/tmp/rando.txt bs=4096 count=2048
rm /tmp/rando.txt
A=0
while [ $A -lt 10 ]; do
java -jar $JAR >> "$OUT"
A=`expr $A + 1`
done
sleep 30
B=`expr $B + 1`
done
cat $OUT | sort -n > $SORTED
done
In [3]:
%matplotlib inline
import scipy.stats as stats
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import math
rounds = 2 # ignore the first round
levels = ['0', '1', '6', '9']
runs = []
for i in range(len(levels)*2):
col = i % len(levels)
run = 2 + math.floor(i/len(levels))
level = levels[col]
filename = 'main_c{}.jar-results-r{}.txt'.format(level, run)
df = pd.read_csv(filename, header=None)
df.columns = [level]
if col == 0:
runs.append(df)
else:
runs[run-2] = pd.concat([runs[run-2], df], axis=1, join='inner')
In [4]:
second = runs[0]
third = runs[1]
result = stats.ttest_ind(second['0'].as_matrix(), second['1'].as_matrix(), equal_var = False)
print("significant:", result.pvalue < 0.01)
result = stats.ttest_ind(third['0'].as_matrix(), third['1'].as_matrix(), equal_var = False)
print("significant:", result.pvalue < 0.01)
In [15]:
plt.figure(figsize=(16,10))
second.boxplot()
plt.show()
second.describe()
Out[15]:
In [25]:
plt.figure(figsize=(16,10))
plt.scatter(second.index, second['0'])
plt.show()
In [17]:
plt.figure(figsize=(16,10))
runs[1].boxplot()
runs[1].describe()
Out[17]: