Create a bench-runner wrapper that uses bench-config

Consolidate the specific customizations to bench-config.sh.

Integrate bench-exec-one.sh into the main bench.sh script. After the
gauge/tasty unification changes, we do not have a requirement for a
separate executable now.

Now we can push the generic part of the script to the benchmark/bench-report
package. And that package can be reused by streaming-benchmarks as well.
We can make that as a submodule.

Ultimately we should be converting these shell scripts to Haskell.
This commit is contained in:
Harendra Kumar 2021-07-04 02:36:53 +05:30
parent 2ab247b559
commit 1a17e2db4b
7 changed files with 796 additions and 781 deletions

View File

@ -1,5 +1,129 @@
#!/usr/bin/env bash
# Customization options
bench_config () {
BENCHMARK_DIR=benchmark
BENCHMARK_PACKAGE_NAME=streamly-benchmarks
BENCHMARK_PACKAGE_VERSION=0.0.0
USE_GAUGE=0
DEFAULT_FIELDS="allocated cputime bytescopied"
}
#------------------------------------------------------------------------------
# test and benchmark groups
#------------------------------------------------------------------------------
# IMPORTANT NOTE: the names "_grp" and "_cmp" suffixes are special, do
# not rename them to something else.
bench_targets () {
base_stream_grp="\
`bench_only Data.Stream.StreamD` \
`bench_only Data.Stream.StreamK` \
`bench_only Data.Stream.StreamDK`"
prelude_serial_grp="\
Prelude.Serial \
Prelude.WSerial \
Prelude.ZipSerial"
prelude_concurrent_grp="\
Prelude.Async \
Prelude.WAsync \
Prelude.Ahead \
Prelude.Parallel \
Prelude.ZipAsync"
prelude_other_grp="\
`test_only Prelude` \
$(test_only $(dev_build Prelude.Rate)) \
`bench_only Prelude.Rate` \
`test_only Prelude.Fold` \
`test_only Prelude.Concurrent` \
$(bench_only $(dev_build Prelude.Concurrent)) \
`bench_only Prelude.Adaptive`"
array_grp="\
Data.Array \
Data.Array.Foreign \
Data.Array.Prim \
Data.SmallArray \
Data.Array.Prim.Pinned"
base_parser_grp="\
Data.Parser.ParserD \
`bench_only Data.Parser.ParserK`"
parser_grp="\
Data.Fold \
Data.Parser"
list_grp="\
`test_only Data.List.Base` \
`test_only Data.List`"
#------------------------------------------------------------------------------
# Streaming vs non-streaming
#------------------------------------------------------------------------------
# The "o-1-space" groups of these benchmarks are run with long stream
# sizes when --long option is used.
infinite_grp="\
$prelude_serial_grp \
$prelude_concurrent_grp \
`bench_only Prelude.Rate`"
#------------------------------------------------------------------------------
# Benchmark comparison groups
#------------------------------------------------------------------------------
# *_cmp denotes a comparison benchmark, the benchmarks provided in *_cmp
# variables are compared with each other
base_stream_cmp="Data.Stream.StreamD Data.Stream.StreamK"
serial_wserial_cmp="Prelude.Serial Prelude.WSerial"
serial_async_cmp="Prelude.Serial Prelude.Async"
concurrent_cmp="Prelude.Async Prelude.WAsync Prelude.Ahead Prelude.Parallel"
array_cmp="Memory.Array Data.Array.Prim Data.Array Data.Array.Prim.Pinned"
pinned_array_cmp="Memory.Array Data.Array.Prim.Pinned"
base_parser_cmp=$base_parser_grp
COMPARISONS="\
base_stream_cmp \
serial_wserial_cmp \
serial_async_cmp \
concurrent_cmp \
array_cmp \
pinned_array_cmp \
base_parser_cmp"
#------------------------------------------------------------------------------
# All test/benchmark modules must be in at least one of these
#------------------------------------------------------------------------------
# All groups
GROUP_TARGETS="\
base_stream_grp \
prelude_serial_grp \
prelude_concurrent_grp \
prelude_other_grp \
array_grp \
base_parser_grp \
parser_grp \
list_grp \
infinite_grp"
# Not in any groups
INDIVIDUAL_TARGETS="\
Data.Unfold \
Unicode.Stream \
FileSystem.Handle \
`test_only FileSystem.Event` \
`test_only Network.Socket` \
`test_only Network.Inet.TCP` \
`test_only version-bounds`"
}
#------------------------------------------------------------------------------
# RTS options based on the benchmark executable name and benchmark name
#------------------------------------------------------------------------------

View File

@ -1,150 +0,0 @@
#!/usr/bin/env bash
# Environment passed:
# BENCH_EXEC_PATH: the benchmark executable
# RTS_OPTIONS: additional RTS options
# QUICK_MODE: whether we are in quick mode
# USE_GAUGE: whether to use gauge or tasty-bench
# LONG: whether to use a large stream size
set -e
set -o pipefail
SCRIPT_DIR=$(cd `dirname $0`; pwd)
source $SCRIPT_DIR/bench-exec-options.sh
# $1: message
die () {
>&2 echo -e "Error: $1"
exit 1
}
warn () {
>&2 echo -e "Warning: $1"
}
test -n "$BENCH_EXEC_PATH" || die "BENCH_EXEC_PATH env var must be set"
test -n "$QUICK_MODE" || warn "QUICK_MODE env var not set (to 0 or 1)"
#------------------------------------------------------------------------------
# Speed options
#------------------------------------------------------------------------------
if test "$USE_GAUGE" -eq 0
then
SUPER_QUICK_OPTIONS="--stdev 1000000"
QUICKER_OPTIONS="--stdev 100"
else
# Do not keep time limit as 0 otherwise GC stats may remain 0 in some cases.
SUPER_QUICK_OPTIONS="--quick --min-duration 0 --time-limit 0.01 --include-first-iter"
QUICKER_OPTIONS="--min-samples 3 --time-limit 1"
fi
# tasty-bench does not like an option set twice
set_super_quick_mode () {
echo -n super_quick
}
# For certain long benchmarks if the user has not requested super quick
# mode we anyway use a slightly quicker mode.
use_quicker_mode () {
if test "$QUICK_MODE" -eq 0
then
echo quicker
fi
}
bench_output_file() {
local bench_name=$1
echo "charts/$bench_name/results.csv"
}
#------------------------------------------------------------------------------
# Determine options from benchmark name
#------------------------------------------------------------------------------
BENCH_NAME_ORIG="$1"
shift
if test "$USE_GAUGE" -eq 0
then
# XXX this is a hack to make the "/" separated names used in the functions
# determining options based on benchmark name. For tasty-bench the benchmark
# names are separated by "." instead of "/".
BENCH_NAME0=$(echo $BENCH_NAME_ORIG | sed -e s/^All\.//)
BENCH_NAME1=$(echo $BENCH_NAME0 | cut -f1 -d '/')
BENCH_NAME2=$(echo $BENCH_NAME0 | cut -f2- -d '/' | sed -e 's/\./\//g')
BENCH_NAME="$BENCH_NAME1/$BENCH_NAME2"
else
BENCH_NAME=$BENCH_NAME_ORIG
fi
RTS_OPTIONS=\
"+RTS -T \
$(bench_rts_options $(basename $BENCH_EXEC_PATH) $BENCH_NAME) \
$RTS_OPTIONS \
-RTS"
QUICK_MODE_TYPE="\
$(if test "$QUICK_MODE" -ne 0; then set_super_quick_mode; fi) \
$(bench_speed_options $(basename $BENCH_EXEC_PATH) $BENCH_NAME)"
for i in $QUICK_MODE_TYPE
do
case "$i" in
super_quick) QUICK_BENCH_OPTIONS="$SUPER_QUICK_OPTIONS"; break ;;
quicker) QUICK_BENCH_OPTIONS="$QUICKER_OPTIONS"; break ;;
esac
done
if test "$LONG" -ne 0
then
STREAM_SIZE=10000000
STREAM_LEN=$(env LC_ALL=en_US.UTF-8 printf "--stream-size %'.f\n" $STREAM_SIZE)
STREAM_SIZE_OPT="--stream-size $STREAM_SIZE"
fi
echo "$BENCH_NAME_ORIG: \
$RTS_OPTIONS \
$STREAM_LEN \
$QUICK_BENCH_OPTIONS" \
"$@"
#------------------------------------------------------------------------------
# Run benchmark with options and collect results
#------------------------------------------------------------------------------
output_file=$(bench_output_file $(basename $BENCH_EXEC_PATH))
mkdir -p `dirname $output_file`
rm -f ${output_file}.tmp
if test $USE_GAUGE -eq 0
then
# Escape "\" and double quotes in benchmark names
BENCH_NAME_ESC=$(echo "$BENCH_NAME_ORIG" | sed -e 's/\\/\\\\/g' | sed -e 's/"/\\"/g')
$BENCH_EXEC_PATH \
-j 1 \
$RTS_OPTIONS \
$STREAM_SIZE_OPT \
$QUICK_BENCH_OPTIONS \
"$@" \
--csv=${output_file}.tmp \
-p '$0 == "'"$BENCH_NAME_ESC"'"'
# Convert cpuTime field from picoseconds to seconds
awk --version 2>&1 | grep -q "GNU Awk" \
|| die "Need GNU awk. [$(which awk)] is not GNU awk."
tail -n +2 ${output_file}.tmp | \
awk 'BEGIN {FPAT = "([^,]+)|(\"[^\"]+\")";OFS=","} {$2=$2/1000000000000;print}' \
>> $output_file
else
$BENCH_EXEC_PATH \
$RTS_OPTIONS \
$STREAM_SIZE_OPT \
$QUICK_BENCH_OPTIONS \
"$@" \
--csvraw=${output_file}.tmp \
-m exact "$BENCH_NAME"
tail -n +2 ${output_file}.tmp \
>> $output_file
fi

665
bin/bench-runner.sh Executable file
View File

@ -0,0 +1,665 @@
#!/usr/bin/env bash
# Note that this script is used in the "streamly" package as well as
# in "streaming-benchmarks" package. Any changes to the script should be
# generic enough so that it works in both the cases.
#------------------------------------------------------------------------------
set -o pipefail
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 " [--prefix <benchmark name prefix to match>"
echo " [--fields <"field1 field2 ..." | help>]"
echo " [--sort-by-name]"
echo " [--compare]"
echo " [--graphs]"
echo " [--no-measure]"
echo " [--append]"
echo " [--long]"
echo " [--slow]"
echo " [--quick]"
echo " [--raw]"
echo " [--dev-build]"
echo " [--use-nix]"
echo " [--with-compiler <compiler exe name>]"
echo " [--cabal-build-options <options>]"
echo " [--rtsopts <opts>]"
echo " [--commit-compare] [--base <commit>] [--candidate <commit>]"
#echo " -- <gauge options or benchmarks>"
echo
echo "--benchmarks: benchmarks to run, use 'help' for list of benchmarks"
echo "--compare: compare the specified benchmarks with each other"
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-options: Pass any cabal build options to be used for build"
echo " e.g. --cabal-build-options \"--flag dev\""
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 --commit-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
cabal_which_report() {
local path="./bin/$1"
if test -e "$path"
then
echo $path
fi
}
find_report_prog() {
local prog_name="bench-report"
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="bench-report"
local prog_path=$(cabal_which_report $prog_name)
hash -r
if test ! -x "$prog_path" -a "$BUILD_ONCE" = "0"
then
echo "Building bench-report executables"
BUILD_ONCE=1
pushd $BENCHMARK_DIR/bench-report
local cmd
cmd="$CABAL_EXECUTABLE install --installdir $SCRIPT_DIR bench-report"
if test "$USE_NIX" -eq 0
then
$cmd || die "bench-report build failed"
else
nix-shell --run "$cmd" || die "bench-report build failed"
fi
popd
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-report executable"
echo "Using bench-report 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"
}
#------------------------------------------------------------------------------
# Speed options
#------------------------------------------------------------------------------
# tasty-bench does not like an option set twice
set_super_quick_mode () {
echo -n super_quick
}
# For certain long benchmarks if the user has not requested super quick
# mode we anyway use a slightly quicker mode.
use_quicker_mode () {
if test "$QUICK_MODE" -eq 0
then
echo quicker
fi
}
#------------------------------------------------------------------------------
# Determine options from benchmark name
#------------------------------------------------------------------------------
# Global environment passed:
# BENCH_EXEC_PATH: the benchmark executable
# RTS_OPTIONS: additional RTS options
# QUICK_MODE: whether we are in quick mode
# USE_GAUGE: whether to use gauge or tasty-bench
# LONG: whether to use a large stream size
# $1: bench name
bench_exec_one() {
local BENCH_NAME_ORIG
BENCH_NAME_ORIG="$1"
shift
local SUPER_QUICK_OPTIONS
local QUICKER_OPTIONS
if test "$USE_GAUGE" -eq 0
then
SUPER_QUICK_OPTIONS="--stdev 1000000"
QUICKER_OPTIONS="--stdev 100"
else
# Do not keep time limit as 0 otherwise GC stats may remain 0 in some cases.
SUPER_QUICK_OPTIONS="--quick --min-duration 0 --time-limit 0.01 --include-first-iter"
QUICKER_OPTIONS="--min-samples 3 --time-limit 1"
fi
local BENCH_NAME0
local BENCH_NAME1
local BENCH_NAME2
local BENCH_NAME
if test "$USE_GAUGE" -eq 0
then
# XXX this is a hack to make the "/" separated names used in the functions
# determining options based on benchmark name. For tasty-bench the benchmark
# names are separated by "." instead of "/".
BENCH_NAME0=$(echo $BENCH_NAME_ORIG | sed -e s/^All\.//)
BENCH_NAME1=$(echo $BENCH_NAME0 | cut -f1 -d '/')
BENCH_NAME2=$(echo $BENCH_NAME0 | cut -f2- -d '/' | sed -e 's/\./\//g')
BENCH_NAME="$BENCH_NAME1/$BENCH_NAME2"
else
BENCH_NAME=$BENCH_NAME_ORIG
fi
local RTS_OPTIONS1
RTS_OPTIONS1="\
+RTS -T \
$(bench_rts_options $(basename $BENCH_EXEC_PATH) $BENCH_NAME) \
$RTS_OPTIONS \
-RTS"
local QUICK_MODE_TYPE
QUICK_MODE_TYPE="\
$(if test "$QUICK_MODE" -ne 0; then set_super_quick_mode; fi) \
$(bench_speed_options $(basename $BENCH_EXEC_PATH) $BENCH_NAME)"
local QUICK_BENCH_OPTIONS
for i in $QUICK_MODE_TYPE
do
case "$i" in
super_quick) QUICK_BENCH_OPTIONS="$SUPER_QUICK_OPTIONS"; break ;;
quicker) QUICK_BENCH_OPTIONS="$QUICKER_OPTIONS"; break ;;
esac
done
local STREAM_SIZE
local STREAM_LEN
local STREAM_SIZE_OPT
if test "$LONG" -ne 0
then
STREAM_SIZE=10000000
STREAM_LEN=$(env LC_ALL=en_US.UTF-8 printf "--stream-size %'.f\n" $STREAM_SIZE)
STREAM_SIZE_OPT="--stream-size $STREAM_SIZE"
fi
echo "$BENCH_NAME_ORIG: \
$RTS_OPTIONS1 \
$STREAM_LEN \
$QUICK_BENCH_OPTIONS" \
"$@"
#------------------------------------------------------------------------------
# Run benchmark with options and collect results
#------------------------------------------------------------------------------
local output_file
output_file=$(bench_output_file $(basename $BENCH_EXEC_PATH))
mkdir -p `dirname $output_file`
rm -f ${output_file}.tmp
local BENCH_NAME_ESC
if test $USE_GAUGE -eq 0
then
# Escape "\" and double quotes in benchmark names
BENCH_NAME_ESC=$(echo "$BENCH_NAME_ORIG" | sed -e 's/\\/\\\\/g' | sed -e 's/"/\\"/g')
$BENCH_EXEC_PATH \
-j 1 \
$RTS_OPTIONS1 \
$STREAM_SIZE_OPT \
$QUICK_BENCH_OPTIONS \
"$@" \
--csv=${output_file}.tmp \
-p '$0 == "'"$BENCH_NAME_ESC"'"'
# Convert cpuTime field from picoseconds to seconds
awk --version 2>&1 | grep -q "GNU Awk" \
|| die "Need GNU awk. [$(which awk)] is not GNU awk."
tail -n +2 ${output_file}.tmp | \
awk 'BEGIN {FPAT = "([^,]+)|(\"[^\"]+\")";OFS=","} {$2=$2/1000000000000;print}' \
>> $output_file
else
$BENCH_EXEC_PATH \
$RTS_OPTIONS1 \
$STREAM_SIZE_OPT \
$QUICK_BENCH_OPTIONS \
"$@" \
--csvraw=${output_file}.tmp \
-m exact "$BENCH_NAME"
tail -n +2 ${output_file}.tmp \
>> $output_file
fi
}
invoke_gauge () {
local target_prog="$1"
local target_name="$2"
local output_file="$3"
local MATCH=""
if test "$LONG" -ne 0
then
MATCH="$target_name/o-1-space"
else
MATCH="$BENCH_PREFIX"
fi
echo "name,iters,time,cycles,cpuTime,utime,stime,maxrss,minflt,majflt,nvcsw,nivcsw,allocated,numGcs,bytesCopied,mutatorWallSeconds,mutatorCpuSeconds,gcWallSeconds,gcCpuSeconds" >> $output_file
# keep only benchmark names with shortest prefix e.g. "a/b/c" and "a/b", we
# should only keep "a/b" otherwise benchmarks will run multiple times. why?
$target_prog -l \
| grep "^$MATCH" \
| while read -r name; \
do bench_exec_one "$name" "${GAUGE_ARGS[@]}" || exit 1; done \
|| die "Benchmark execution failed."
}
invoke_tasty_bench () {
local target_prog="$1"
local target_name="$2"
local output_file="$3"
local MATCH=""
if test "$LONG" -ne 0
then
MATCH="-p /$target_name\/o-1-space/"
else
if test -n "$BENCH_PREFIX"
then
# escape "/"
local escaped_name=$(echo "$BENCH_PREFIX" | sed -e 's/\//\\\//g')
MATCH="-p /$escaped_name/"
fi
fi
echo "Name,cpuTime,2*Stdev (ps),Allocated,bytesCopied" >> $output_file
$target_prog -l $MATCH \
| grep "^All" \
| while read -r name; \
do bench_exec_one "$name" "${GAUGE_ARGS[@]}" || exit 1; done \
|| die "Benchmark execution failed."
}
run_bench_target () {
local package_name=$1
local component=$2
local target_name=$3
local target_prog
target_prog=$(cabal_target_prog $package_name $component $target_name) || \
die "Cannot find executable for target $target_name"
echo "Running executable $target_name ..."
# Needed by bench_exec_one
BENCH_EXEC_PATH=$target_prog
local output_file=$(bench_output_file $target_name)
mkdir -p `dirname $output_file`
if test "$USE_GAUGE" -eq 0
then invoke_tasty_bench "$target_prog" "$target_name" "$output_file"
else invoke_gauge "$target_prog" "$target_name" "$output_file"
fi
}
# $1: package name
# $2: component
# $3: targets
run_bench_targets() {
for i in $3
do
run_bench_target $1 $2 $i
done
}
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_bench_targets $BENCHMARK_PACKAGE_NAME 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_bench_targets $BENCHMARK_PACKAGE_NAME 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 "$COMMIT_COMPARE" = "0"
then
run_bench_targets $BENCHMARK_PACKAGE_NAME 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 \
--benchmark $i \
$(test "$GRAPH" = 1 && echo "--graphs") \
$(test "$SORT_BY_NAME" = 1 && echo "--sort-by-name") \
--fields "$FIELDS"
done
}
#-----------------------------------------------------------------------------
# Execution starts here
#-----------------------------------------------------------------------------
cd $SCRIPT_DIR/..
USE_GIT_CABAL=1 # This is used by set_common_vars
set_common_vars
USE_NIX=0
COMPARE=0
COMMIT_COMPARE=0
BASE=
CANDIDATE=
APPEND=0
LONG=0
RAW=0
SORT_BY_NAME=0
GRAPH=0
MEASURE=1
GAUGE_ARGS=
BUILD_ONCE=0
CABAL_BUILD_OPTIONS="--flag fusion-plugin "
#-----------------------------------------------------------------------------
# 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 ;;
--prefix) shift; BENCH_PREFIX="$1"; shift ;;
--fields) shift; FIELDS=$1; shift ;;
--base) shift; BASE=$1; shift ;;
--candidate) shift; CANDIDATE=$1; shift ;;
--with-compiler) shift; CABAL_WITH_COMPILER=$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 ;;
--config) shift; BENCH_CONFIG_FILE=$1; shift ;;
# flags
--slow) SLOW=1; shift ;;
--quick) QUICK_MODE=1; shift ;;
--compare) COMPARE=1; shift ;;
--commit-compare) COMMIT_COMPARE=1; shift ;;
--raw) RAW=1; shift ;;
--append) APPEND=1; shift ;;
--long) LONG=1; shift ;;
--sort-by-name) SORT_BY_NAME=1; shift ;;
--graphs) GRAPH=1; shift ;;
--no-measure) MEASURE=0; shift ;;
--dev-build) RUNNING_DEVBUILD=1; shift ;;
--use-nix) USE_NIX=1; shift ;;
--use-gauge) USE_GAUGE=1; shift ;;
--) shift; break ;;
*) echo "Unknown flags: $*"; echo; print_help ;;
esac
done
GAUGE_ARGS=("$@")
if test -z "$BENCH_CONFIG_FILE"
then
die "Please use --config to specify config file"
fi
source "$BENCH_CONFIG_FILE" || \
die "Failed to source config file $BENCH_CONFIG_FILE"
# Defined in $BENCH_CONFIG_FILE
bench_config
if test -z "$FIELDS"
then
FIELDS=$DEFAULT_FIELDS
fi
set_derived_vars
#-----------------------------------------------------------------------------
# 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
}
# defined in $BENCH_CONFIG_FILE
bench_targets
if test "$(has_item "$TARGETS" help)" = "help"
then
list_target_groups
list_comparisons
list_targets
exit
fi
COMMON_FIELDS="allocated bytescopied cputime maxrss"
if test "$USE_GAUGE" -eq 1
then
ALL_FIELDS="$COMMON_FIELDS time cycles utime stime minflt majflt nvcsw nivcsw"
else
ALL_FIELDS="$COMMON_FIELDS"
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
#-----------------------------------------------------------------------------
if test "$USE_GAUGE" -eq 1
then
BUILD_FLAGS="--flag use-gauge"
fi
BUILD_BENCH="$CABAL_EXECUTABLE v2-build $BUILD_FLAGS $CABAL_BUILD_OPTIONS --enable-benchmarks"
if test "$MEASURE" = "1"
then
run_build "$BUILD_BENCH" $BENCHMARK_PACKAGE_NAME bench "$TARGETS"
run_measurements "$TARGETS"
fi
#-----------------------------------------------------------------------------
# Run reports
#-----------------------------------------------------------------------------
# $1: var name
build_comparison_results () {
local name
local constituents
name=$1
constituents=$(eval "echo -n \$${name}")
mkdir -p "charts/$name"
dest_file="charts/$name/results.csv"
: > $dest_file
for j in $constituents
do
cat "charts/$j/results.csv" >> $dest_file
done
}
if test "$COMPARE" -eq 1
then
DYN_CMP_GRP="$(echo "$TARGETS" | sed -e 's/ /_/g')_cmp"
eval "$DYN_CMP_GRP=\"$TARGETS\""
COMPARISON_REPORTS=$DYN_CMP_GRP
build_comparison_results $DYN_CMP_GRP
else
COMPARISON_REPORTS=""
fi
for i in $COMPARISONS
do
if test "$(has_item "$TARGETS_ORIG" $i)" = $i
then
COMPARISON_REPORTS="$COMPARISON_REPORTS $i"
build_comparison_results $i
fi
done
if test "$RAW" = "0"
then
run_reports "$TARGETS"
run_reports "$COMPARISON_REPORTS"
if test -n "$DYN_CMP_GRP"
then
rm -rf "charts/$DYN_CMP_GRP"
fi
fi

View File

@ -1,519 +1,3 @@
#!/usr/bin/env bash
# Note that this script is used in the "streamly" package as well as
# in "streaming-benchmarks" package. Any changes to the script should be
# generic enough so that it works in both the cases.
# Customization options
BENCHMARK_DIR=benchmark
BENCHMARK_PACKAGE_NAME=streamly-benchmarks
BENCHMARK_PACKAGE_VERSION=0.0.0
USE_GAUGE=0
DEFAULT_FIELDS="allocated cputime bytescopied"
#------------------------------------------------------------------------------
set -o pipefail
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 " [--prefix <benchmark name prefix to match>"
echo " [--fields <"field1 field2 ..." | help>]"
echo " [--sort-by-name]"
echo " [--compare]"
echo " [--graphs]"
echo " [--no-measure]"
echo " [--append]"
echo " [--long]"
echo " [--slow]"
echo " [--quick]"
echo " [--raw]"
echo " [--dev-build]"
echo " [--use-nix]"
echo " [--with-compiler <compiler exe name>]"
echo " [--cabal-build-options <options>]"
echo " [--rtsopts <opts>]"
echo " [--commit-compare] [--base <commit>] [--candidate <commit>]"
echo " -- <gauge options or benchmarks>"
echo
echo "--benchmarks: benchmarks to run, use 'help' for list of benchmarks"
echo "--compare: compare the specified benchmarks with each other"
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-options: Pass any cabal build options to be used for build"
echo " e.g. --cabal-build-options \"--flag dev\""
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 --commit-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
cabal_which_report() {
local path="./bin/$1"
if test -e "$path"
then
echo $path
fi
}
find_report_prog() {
local prog_name="bench-report"
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="bench-report"
local prog_path=$(cabal_which_report $prog_name)
hash -r
if test ! -x "$prog_path" -a "$BUILD_ONCE" = "0"
then
echo "Building bench-report executables"
BUILD_ONCE=1
pushd $BENCHMARK_DIR/bench-report
local cmd
cmd="$CABAL_EXECUTABLE install --installdir $SCRIPT_DIR bench-report"
if test "$USE_NIX" -eq 0
then
$cmd || die "bench-report build failed"
else
nix-shell --run "$cmd" || die "bench-report build failed"
fi
popd
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-report executable"
echo "Using bench-report 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"
}
invoke_gauge () {
local target_prog="$1"
local target_name="$2"
local output_file="$3"
local MATCH=""
if test "$LONG" -ne 0
then
MATCH="$target_name/o-1-space"
else
MATCH="$BENCH_PREFIX"
fi
echo "name,iters,time,cycles,cpuTime,utime,stime,maxrss,minflt,majflt,nvcsw,nivcsw,allocated,numGcs,bytesCopied,mutatorWallSeconds,mutatorCpuSeconds,gcWallSeconds,gcCpuSeconds" >> $output_file
# keep only benchmark names with shortest prefix e.g. "a/b/c" and "a/b", we
# should only keep "a/b" otherwise benchmarks will run multiple times. why?
$target_prog -l \
| grep "^$MATCH" \
| while read -r name; \
do bin/bench-exec-one.sh "$name" "${GAUGE_ARGS[@]}" || exit 1; done \
|| die "Benchmark execution failed."
}
invoke_tasty_bench () {
local target_prog="$1"
local target_name="$2"
local output_file="$3"
local MATCH=""
if test "$LONG" -ne 0
then
MATCH="-p /$target_name\/o-1-space/"
else
if test -n "$BENCH_PREFIX"
then
# escape "/"
local escaped_name=$(echo "$BENCH_PREFIX" | sed -e 's/\//\\\//g')
MATCH="-p /$escaped_name/"
fi
fi
echo "Name,cpuTime,2*Stdev (ps),Allocated,bytesCopied" >> $output_file
$target_prog -l $MATCH \
| grep "^All" \
| while read -r name; \
do bin/bench-exec-one.sh "$name" "${GAUGE_ARGS[@]}" || exit 1; done \
|| die "Benchmark execution failed."
}
run_bench_target () {
local package_name=$1
local component=$2
local target_name=$3
local target_prog
target_prog=$(cabal_target_prog $package_name $component $target_name) || \
die "Cannot find executable for target $target_name"
echo "Running executable $target_name ..."
# Needed by bench-exec-one.sh
export BENCH_EXEC_PATH=$target_prog
export RTS_OPTIONS
export QUICK_MODE
export USE_GAUGE
export LONG
local output_file=$(bench_output_file $target_name)
mkdir -p `dirname $output_file`
if test "$USE_GAUGE" -eq 0
then invoke_tasty_bench "$target_prog" "$target_name" "$output_file"
else invoke_gauge "$target_prog" "$target_name" "$output_file"
fi
}
# $1: package name
# $2: component
# $3: targets
run_bench_targets() {
for i in $3
do
run_bench_target $1 $2 $i
done
}
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_bench_targets $BENCHMARK_PACKAGE_NAME 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_bench_targets $BENCHMARK_PACKAGE_NAME 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 "$COMMIT_COMPARE" = "0"
then
run_bench_targets $BENCHMARK_PACKAGE_NAME 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 \
--benchmark $i \
$(test "$GRAPH" = 1 && echo "--graphs") \
$(test "$SORT_BY_NAME" = 1 && echo "--sort-by-name") \
--fields "$FIELDS"
done
}
#-----------------------------------------------------------------------------
# Execution starts here
#-----------------------------------------------------------------------------
cd $SCRIPT_DIR/..
USE_GIT_CABAL=1
USE_NIX=0
set_common_vars
COMMON_FIELDS="allocated bytescopied cputime maxrss"
if test "$USE_GAUGE" -eq 1
then
ALL_FIELDS="$COMMON_FIELDS time cycles utime stime minflt majflt nvcsw nivcsw"
else
ALL_FIELDS="$COMMON_FIELDS"
fi
FIELDS=$DEFAULT_FIELDS
COMPARE=0
COMMIT_COMPARE=0
BASE=
CANDIDATE=
APPEND=0
LONG=0
RAW=0
SORT_BY_NAME=0
GRAPH=0
MEASURE=1
GAUGE_ARGS=
BUILD_ONCE=0
CABAL_BUILD_OPTIONS="--flag fusion-plugin "
#-----------------------------------------------------------------------------
# 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 ;;
--prefix) shift; BENCH_PREFIX="$1"; shift ;;
--fields) shift; FIELDS=$1; shift ;;
--base) shift; BASE=$1; shift ;;
--candidate) shift; CANDIDATE=$1; shift ;;
--with-compiler) shift; CABAL_WITH_COMPILER=$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 ;;
--commit-compare) COMMIT_COMPARE=1; shift ;;
--raw) RAW=1; shift ;;
--append) APPEND=1; shift ;;
--long) LONG=1; shift ;;
--sort-by-name) SORT_BY_NAME=1; shift ;;
--graphs) GRAPH=1; shift ;;
--no-measure) MEASURE=0; shift ;;
--dev-build) RUNNING_DEVBUILD=1; shift ;;
--use-nix) USE_NIX=1; shift ;;
--use-gauge) USE_GAUGE=1; shift ;;
--) shift; break ;;
*) echo "Unknown flags: $*"; echo; print_help ;;
esac
done
GAUGE_ARGS=("$@")
set_derived_vars
#-----------------------------------------------------------------------------
# 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
#-----------------------------------------------------------------------------
if test "$USE_GAUGE" -eq 1
then
BUILD_FLAGS="--flag use-gauge"
fi
BUILD_BENCH="$CABAL_EXECUTABLE v2-build $BUILD_FLAGS $CABAL_BUILD_OPTIONS --enable-benchmarks"
if test "$MEASURE" = "1"
then
run_build "$BUILD_BENCH" $BENCHMARK_PACKAGE_NAME bench "$TARGETS"
run_measurements "$TARGETS"
fi
#-----------------------------------------------------------------------------
# Run reports
#-----------------------------------------------------------------------------
# $1: var name
build_comparison_results () {
local name
local constituents
name=$1
constituents=$(eval "echo -n \$${name}")
mkdir -p "charts/$name"
dest_file="charts/$name/results.csv"
: > $dest_file
for j in $constituents
do
cat "charts/$j/results.csv" >> $dest_file
done
}
if test "$COMPARE" -eq 1
then
DYN_CMP_GRP="$(echo "$TARGETS" | sed -e 's/ /_/g')_cmp"
eval "$DYN_CMP_GRP=\"$TARGETS\""
COMPARISON_REPORTS=$DYN_CMP_GRP
build_comparison_results $DYN_CMP_GRP
else
COMPARISON_REPORTS=""
fi
for i in $COMPARISONS
do
if test "$(has_item "$TARGETS_ORIG" $i)" = $i
then
COMPARISON_REPORTS="$COMPARISON_REPORTS $i"
build_comparison_results $i
fi
done
if test "$RAW" = "0"
then
run_reports "$TARGETS"
run_reports "$COMPARISON_REPORTS"
if test -n "$DYN_CMP_GRP"
then
rm -rf "charts/$DYN_CMP_GRP"
fi
fi
bin/bench-runner.sh --config bin/bench-config.sh "$@"

View File

@ -4,6 +4,10 @@ die () {
exit 1
}
warn () {
>&2 echo -e "Warning: $1"
}
# $1: command
function run_verbose() {
echo "$*"

View File

@ -1,112 +0,0 @@
#------------------------------------------------------------------------------
# test and benchmark groups
#------------------------------------------------------------------------------
# IMPORTANT NOTE: the names "_grp" and "_cmp" suffixes are special, do
# not rename them to something else.
base_stream_grp="\
`bench_only Data.Stream.StreamD` \
`bench_only Data.Stream.StreamK` \
`bench_only Data.Stream.StreamDK`"
prelude_serial_grp="\
Prelude.Serial \
Prelude.WSerial \
Prelude.ZipSerial"
prelude_concurrent_grp="\
Prelude.Async \
Prelude.WAsync \
Prelude.Ahead \
Prelude.Parallel \
Prelude.ZipAsync"
prelude_other_grp="\
`test_only Prelude` \
$(test_only $(dev_build Prelude.Rate)) \
`bench_only Prelude.Rate` \
`test_only Prelude.Fold` \
`test_only Prelude.Concurrent` \
$(bench_only $(dev_build Prelude.Concurrent)) \
`bench_only Prelude.Adaptive`"
array_grp="\
Data.Array \
Data.Array.Foreign \
Data.Array.Prim \
Data.SmallArray \
Data.Array.Prim.Pinned"
base_parser_grp="\
Data.Parser.ParserD \
`bench_only Data.Parser.ParserK`"
parser_grp="\
Data.Fold \
Data.Parser"
list_grp="\
`test_only Data.List.Base` \
`test_only Data.List`"
#------------------------------------------------------------------------------
# Streaming vs non-streaming
#------------------------------------------------------------------------------
# The "o-1-space" groups of these benchmarks are run with long stream
# sizes when --long option is used.
infinite_grp="\
$prelude_serial_grp \
$prelude_concurrent_grp \
`bench_only Prelude.Rate`"
#------------------------------------------------------------------------------
# Benchmark comparison groups
#------------------------------------------------------------------------------
# *_cmp denotes a comparison benchmark, the benchmarks provided in *_cmp
# variables are compared with each other
base_stream_cmp="Data.Stream.StreamD Data.Stream.StreamK"
serial_wserial_cmp="Prelude.Serial Prelude.WSerial"
serial_async_cmp="Prelude.Serial Prelude.Async"
concurrent_cmp="Prelude.Async Prelude.WAsync Prelude.Ahead Prelude.Parallel"
array_cmp="Memory.Array Data.Array.Prim Data.Array Data.Array.Prim.Pinned"
pinned_array_cmp="Memory.Array Data.Array.Prim.Pinned"
base_parser_cmp=$base_parser_grp
COMPARISONS="\
base_stream_cmp \
serial_wserial_cmp \
serial_async_cmp \
concurrent_cmp \
array_cmp \
pinned_array_cmp \
base_parser_cmp"
#------------------------------------------------------------------------------
# All test/benchmark modules must be in at least one of these
#------------------------------------------------------------------------------
# All groups
GROUP_TARGETS="\
base_stream_grp \
prelude_serial_grp \
prelude_concurrent_grp \
prelude_other_grp \
array_grp \
base_parser_grp \
parser_grp \
list_grp \
infinite_grp"
# Not in any groups
INDIVIDUAL_TARGETS="\
Data.Unfold \
Unicode.Stream \
FileSystem.Handle \
`test_only FileSystem.Event` \
`test_only Network.Socket` \
`test_only Network.Inet.TCP` \
`test_only version-bounds`"

View File

@ -77,13 +77,13 @@ extra-source-files:
benchmark/lib/Streamly/Benchmark/Common/*.hs
benchmark/streamly-benchmarks.cabal
bin/bench.sh
bin/bench-exec-one.sh
bin/bench-config.sh
bin/bench-runner.sh
bin/build-lib.sh
bin/ghc.sh
bin/run-ci.sh
bin/mk-hscope.sh
bin/mk-tags.sh
bin/targets.sh
bin/test.sh
configure
configure.ac