streamly/bin/bench.sh
2020-12-28 23:56:18 +05:30

418 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
SCRIPT_DIR=$(cd `dirname $0`; pwd)
RUNNING_BENCHMARKS=y
source $SCRIPT_DIR/build-lib.sh
print_help () {
echo "Usage: $0 "
echo " [--benchmarks <"bench1 bench2 ..." | help>]"
echo " [--fields <"field1 field2 ..." | help>]"
echo " [--graphs]"
echo " [--no-measure]"
echo " [--append]"
echo " [--long]"
echo " [--slow]"
echo " [--quick]"
echo " [--raw]"
echo " [--dev-build]"
echo " [--cabal-build-flags <flag>]"
echo " [--rtsopts <opts>]"
echo " [--compare] [--base <commit>] [--candidate <commit>]"
echo " -- <gauge options or benchmarks>"
echo
echo "--benchmarks: benchmarks to run, use 'help' for list of benchmarks"
echo "--fields: measurement fields to report, use 'help' for a list"
echo "--graphs: Generate graphical reports"
echo "--no-measure: Don't run benchmarks, run reports from previous results"
echo "--append: Don't overwrite previous results, append for comparison"
echo "--long: Use much longer stream size for infinite stream benchmarks"
echo "--slow: Slightly more accurate results at the expense of speed"
echo "--quick: Faster results, useful for longer benchmarks"
echo "--raw: Run the benchmarks but don't report them. This is useful when"
echo " you only want to work with the csv files generated."
echo "--cabal-build-flags: Pass any cabal builds flags to be used for build"
echo " e.g. --cabal-build-flags \"--flag fusion-plugin\""
echo
echo "When specific space complexity group is chosen then (and only then) "
echo "RTS memory restrictions are used accordingly. For example, "
echo "bench.sh --benchmarks Data.Parser -- Data.Parser/o-1-space "
echo "restricts Heap/Stack space for O(1) characterstics"
echo
echo "When using --compare, by default comparative chart of HEAD^ vs HEAD"
echo "commit is generated, in the 'charts' directory."
echo "Use --base and --candidate to select the commits to compare."
echo
echo "Any arguments after a '--' are passed directly to gauge"
exit
}
#-----------------------------------------------------------------------------
# Reporting utility functions
#-----------------------------------------------------------------------------
list_comparisons () {
echo "Comparison groups:"
for i in $COMPARISONS
do
echo -n "$i ["
eval "echo -n \$$i"
echo "]"
done
echo
}
# chart is expensive to build and usually not required to be rebuilt,
# use master branch as fallback
cabal_which_report() {
local path=$(cabal_which streamly-benchmarks x $1)
if test -z "$path"
then
cabal_which_builddir dist-newstyle streamly-benchmarks x $1
else
echo $path
fi
}
find_report_prog() {
local prog_name="chart"
hash -r
local prog_path=$(cabal_which_report $prog_name)
if test -x "$prog_path"
then
echo $prog_path
else
return 1
fi
}
build_report_prog() {
local prog_name="chart"
local prog_path=$(cabal_which_report $prog_name)
hash -r
if test ! -x "$prog_path" -a "$BUILD_ONCE" = "0"
then
echo "Building bench-show executables"
BUILD_ONCE=1
$CABAL_EXECUTABLE v2-build --flags dev chart \
|| die "bench-show build failed"
elif test ! -x "$prog_path"
then
return 1
fi
return 0
}
build_report_progs() {
if test "$RAW" = "0"
then
build_report_prog || exit 1
local prog
prog=$(find_report_prog) || \
die "Cannot find bench-show executable"
echo "Using bench-show executable [$prog]"
fi
}
# We run the benchmarks in isolation in a separate process so that different
# benchmarks do not interfere with other. To enable that we need to pass the
# benchmark exe path to gauge as an argument. Unfortunately it cannot find its
# own path currently.
# The path is dependent on the architecture and cabal version.
bench_output_file() {
local bench_name=$1
echo "charts/$bench_name/results.csv"
}
# --min-duration 0 means exactly one iteration per sample. We use a million
# iterations in the benchmarking code explicitly and do not use the iterations
# done by the benchmarking tool.
#
# Benchmarking tool by default discards the first iteration to remove
# aberrations due to initial evaluations etc. We do not discard it because we
# are anyway doing iterations in the benchmarking code and many of them so that
# any constant factor gets amortized and anyway it is a cost that we pay in
# real life.
#
# We can pass --min-samples value from the command line as second argument
# after the benchmark name in case we want to use more than one sample.
# $1: bench name
# $2: bench executable
target_exe_extra_args () {
local bench_name=$1
local bench_prog=$2
local output_file=$(bench_output_file $bench_name)
mkdir -p `dirname $output_file`
local QUICK_OPTS="--quick --min-duration 0"
local SPEED_OPTIONS
if test "$LONG" -eq 0
then
if test "$SLOW" -eq 0
then
if test "$QUICK_MODE" -eq 0
then
# default mode, not super quick, not slow
SPEED_OPTIONS="$QUICK_OPTS --min-samples 10 --time-limit 1"
else
# super quick but less accurate
SPEED_OPTIONS="$QUICK_OPTS --time-limit 0 --include-first-iter"
fi
else
# Slow but more accurate mode
SPEED_OPTIONS="--min-duration 0"
fi
else
# large stream size, always super quick
GAUGE_ARGS="$GAUGE_ARGS $bench_name/o-1-space"
SPEED_OPTIONS="--stream-size 10000000 $QUICK_OPTS --include-first-iter"
fi
echo "$SPEED_OPTIONS \
--csvraw=$output_file \
-v 2 \
--measure-with "$SCRIPT_DIR/bench-exec-one.sh" \
$GAUGE_ARGS"
}
run_benches_comparing() {
local bench_list=$1
if test -z "$CANDIDATE"
then
CANDIDATE=$(git rev-parse HEAD)
fi
if test -z "$BASE"
then
# XXX Should be where the current branch is forked from master
BASE="$CANDIDATE^"
fi
echo "Comparing baseline commit [$BASE] with candidate [$CANDIDATE]"
echo "Checking out base commit [$BASE] for benchmarking"
git checkout "$BASE" || die "Checkout of base commit [$BASE] failed"
$BUILD_BENCH || die "build failed"
run_targets streamly-benchmarks b "$bench_list" target_exe_extra_args
echo "Checking out candidate commit [$CANDIDATE] for benchmarking"
git checkout "$CANDIDATE" || \
die "Checkout of candidate [$CANDIDATE] commit failed"
$BUILD_BENCH || die "build failed"
run_targets streamly-benchmarks b "$bench_list" target_exe_extra_args
# XXX reset back to the original commit
}
backup_output_file() {
local bench_name=$1
local output_file=$(bench_output_file $bench_name)
if test -e $output_file -a "$APPEND" != 1
then
mv -f -v $output_file ${output_file}.prev
fi
}
run_measurements() {
local bench_list=$1
for i in $bench_list
do
backup_output_file $i
done
if test "$COMPARE" = "0"
then
run_targets streamly-benchmarks b "$bench_list" target_exe_extra_args
else
run_benches_comparing "$bench_list"
fi
}
run_reports() {
local prog
prog=$(find_report_prog) || \
die "Cannot find bench-graph executable"
echo
for i in $1
do
echo "Generating reports for ${i}..."
$prog $(test "$GRAPH" = 1 && echo "--graphs") \
--benchmark $i --fields "$FIELDS"
done
}
#-----------------------------------------------------------------------------
# Execution starts here
#-----------------------------------------------------------------------------
cd $SCRIPT_DIR/..
USE_GIT_CABAL=1
set_common_vars
DEFAULT_FIELDS="allocated bytescopied cputime"
ALL_FIELDS="$FIELDS time cycles utime stime minflt majflt nvcsw nivcsw"
FIELDS=$DEFAULT_FIELDS
COMPARE=0
BASE=
CANDIDATE=
APPEND=0
LONG=0
RAW=0
GRAPH=0
MEASURE=1
GAUGE_ARGS=
BUILD_ONCE=0
#-----------------------------------------------------------------------------
# Read command line
#-----------------------------------------------------------------------------
while test -n "$1"
do
case $1 in
-h|--help|help) print_help ;;
# options with arguments
--benchmarks) shift; TARGETS=$1; shift ;;
--targets) shift; TARGETS=$1; shift ;;
--fields) shift; FIELDS=$1; shift ;;
--base) shift; BASE=$1; shift ;;
--candidate) shift; CANDIDATE=$1; shift ;;
--cabal-build-flags) shift; CABAL_BUILD_OPTIONS=$1; shift ;;
--cabal-build-options) shift; CABAL_BUILD_OPTIONS=$1; shift ;;
--rtsopts) shift; RTS_OPTIONS=$1; shift ;;
# flags
--slow) SLOW=1; shift ;;
--quick) QUICK_MODE=1; shift ;;
--compare) COMPARE=1; shift ;;
--raw) RAW=1; shift ;;
--append) APPEND=1; shift ;;
--long) LONG=1; shift ;;
--graphs) GRAPH=1; shift ;;
--no-measure) MEASURE=0; shift ;;
--dev-build) RUNNING_DEVBUILD=1; shift ;;
--) shift; break ;;
-*|--*) echo "Unknown flags: $*"; echo; print_help ;;
*) break ;;
esac
done
GAUGE_ARGS=$*
#-----------------------------------------------------------------------------
# Determine targets
#-----------------------------------------------------------------------------
only_real_benchmarks () {
for i in $TARGETS
do
local SKIP=0
for j in $COMPARISONS
do
if test $i == $j
then
SKIP=1
fi
done
if test "$SKIP" -eq 0
then
echo -n "$i "
fi
done
}
# Requires RUNNING_DEVBUILD var
source $SCRIPT_DIR/targets.sh
if test "$(has_item "$TARGETS" help)" = "help"
then
list_target_groups
list_comparisons
list_targets
exit
fi
if test "$(has_item "$FIELDS" help)" = "help"
then
echo "Supported fields: $ALL_FIELDS"
echo "Default fields: $DEFAULT_FIELDS"
exit
fi
if test "$LONG" -ne 0
then
if test -n "$TARGETS"
then
echo "Cannot specify benchmarks [$TARGETS] with --long"
exit
fi
TARGETS=$infinite_grp
fi
DEFAULT_TARGETS="$(all_grp)"
TARGETS=$(set_targets)
TARGETS_ORIG=$TARGETS
TARGETS=$(only_real_benchmarks)
echo "Using benchmark suites [$TARGETS]"
#-----------------------------------------------------------------------------
# Build reporting utility
#-----------------------------------------------------------------------------
# We need to build the report progs first at the current (latest) commit before
# checking out any other commit for benchmarking.
build_report_progs
#-----------------------------------------------------------------------------
# Build and run targets
#-----------------------------------------------------------------------------
BUILD_BENCH="$CABAL_EXECUTABLE v2-build $CABAL_BUILD_OPTIONS --enable-benchmarks"
if test "$MEASURE" = "1"
then
run_build "$BUILD_BENCH" streamly-benchmarks bench "$TARGETS"
export QUICK_MODE
export RTS_OPTIONS
run_measurements "$TARGETS"
fi
#-----------------------------------------------------------------------------
# Run reports
#-----------------------------------------------------------------------------
COMPARISON_REPORTS=""
for i in $COMPARISONS
do
if test "$(has_item "$TARGETS_ORIG" $i)" = $i
then
COMPARISON_REPORTS="$COMPARISON_REPORTS $i"
mkdir -p "charts/$i"
constituents=$(eval "echo -n \$${i}")
dest_file="charts/$i/results.csv"
: > $dest_file
for j in $constituents
do
cat "charts/$j/results.csv" >> $dest_file
done
fi
done
if test "$RAW" = "0"
then
run_reports "$TARGETS"
run_reports "$COMPARISON_REPORTS"
fi