diff --git a/README.md b/README.md index efce48f..c714119 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ for the versions: allocator, used by [WebKit](https://webkit.org). - **lt**: The [_ltalloc_](https://github.com/r-lyeh-archived/ltalloc) allocator, a multi-threaded memory allocator based on free lists best suited for many small allocations. + This allocator is unmaintained (upstream archived) and faults under _mstressN_, + _leanN_, and _sh8benchN_ on x86-64 Linux; there are no entries for it in those. - **mesh**: The [_mesh_](https://github.com/plasma-umass/mesh) allocator, a memory allocator that automatically reduces the memory footprint of C/C++ applications. Also tested as **nomesh** with the meshing feature disabled. diff --git a/bench.sh b/bench.sh index fd808d7..7b08f88 100755 --- a/bench.sh +++ b/bench.sh @@ -39,6 +39,7 @@ readonly version_linux=6.5.1 verbose="no" ldpreload="LD_PRELOAD" timecmd="$(type -P time)" # the shell builtin doesn't have all the options we need +timeout_cmd="$(type -P timeout gtimeout 2>/dev/null | head -1)" # on macos: brew install coreutils sedcmd=sed darwin="" extso=".so" @@ -46,6 +47,9 @@ procs=8 repeats=1 # repeats of all tests test_repeats=1 # repeats per test sleep=0 # mini sleeps between tests seem to improve stability +run_limit=0 # time limit per run in seconds (0 = unlimited) +error=0 # binary marker for errors during the run +failed_runs="" # accumulated failed runs case "$OSTYPE" in darwin*) darwin="1" @@ -374,6 +378,8 @@ while : ; do test_repeats="$flag_arg";; -s=*|--sleep=*) sleep="$flag_arg";; + -T=*|--timeout=*) + run_limit="$flag_arg";; -v|--verbose) verbose="yes";; -h|--help|-\?|help|\?) @@ -386,6 +392,7 @@ while : ; do echo " -r= number of repeats of the full suite (=$repeats)" echo " -n= number of repeats of each individual test (=$test_repeats)" echo " -s=, --sleep= seconds of sleep between each test (=$sleep)" + echo " -T=, --timeout= time limit per run in seconds; on expiry the run's whole process group is killed and the run counts as failed (=$run_limit)" echo " --external= read external allocators from , one per line, in the format " echo "" echo " allt run all tests" @@ -454,6 +461,18 @@ while : ; do shift done +# the timeout wrapper: timeout runs the command in its own process group and +# signals that whole group on expiry, so no grandchild (make -j children, +# lean --make workers, ...) can outlive a capped run +run_wrap="" +if test "$run_limit" != "0"; then + if test -z "$timeout_cmd"; then + warning "-T ignored: no timeout command found (on macOS: brew install coreutils)" + else + run_wrap="$timeout_cmd -k 10s ${run_limit}s" + fi +fi + echo "benchmarking on $procs cores." echo "use '-h' or '--help' for help on configuration options." echo "" @@ -527,13 +546,23 @@ function set_spec_bench_dir { readonly allocfill=" " readonly benchfill=" " +function kill_job_tree { # kill and its children (e.g. a redis server running under time) + local p + for p in $(pgrep -P "$1" 2>/dev/null); do + pkill -9 -P "$p" 2>/dev/null + kill -9 "$p" 2>/dev/null + done + kill -9 "$1" 2>/dev/null +} + function run_test_env_cmd { # if ! [ -z "$sleep" ]; then sleep "$sleep" fi echo - echo "run $5: $1 $2: $3 $4" + echo "run $5: $1 $2: $3 $run_wrap $4" # clear temporary output + local run_failed="" if [ -f "$benchres.line" ]; then rm "$benchres.line" fi @@ -562,16 +591,54 @@ function run_test_env_cmd { # "$outfile.server.txt" & - sleep 1s - $redis_dir/redis-cli flushall - sleep 1s - $4 >> "$outfile" - sleep 1s - $redis_dir/redis-cli flushall - sleep 1s - $redis_dir/redis-cli shutdown - sleep 1s + tmpfile=$(mktemp) + ( + $timecmd -a -o "$benchres.line" -f "$1${benchfill:${#1}} $2${allocfill:${#2}} %E %M %U %S %F %R" /usr/bin/env $3 $redis_dir/redis-server > "$outfile.server.txt" + echo $? > "$tmpfile" + ) & + local serverjob=$! + sleep 0.1 + if [ -s "$tmpfile" ]; then + error="1" + failed_runs+="$1:$2 " + return + fi + rm -f "$tmpfile" + # wait (bounded) for the server to accept commands: a server that dies + # under a preloaded allocator must not hang the whole benchmark + local server_ok="" retry + for retry in $(seq 1 60); do + if $timeout_cmd 10s $redis_dir/redis-cli flushall > /dev/null 2>&1; then + server_ok="1"; break + fi + [ -s "$tmpfile" ] && break # server exited while waiting + sleep 1 + done + if [ -z "$server_ok" ]; then + echo "$1 $2: redis server not accepting commands" >&2 + error="1" + failed_runs+="$1:$2 " + kill_job_tree "$serverjob" + wait "$serverjob" 2>/dev/null + return + fi + $run_wrap $4 >> "$outfile" + $timeout_cmd 10s $redis_dir/redis-cli flushall > /dev/null + $timeout_cmd 10s $redis_dir/redis-cli shutdown 2> /dev/null + # a surviving server would silently serve the next allocator's run; + # its measured time is unreliable too + for retry in $(seq 1 20); do + pgrep -P "$serverjob" > /dev/null || break + sleep 0.5 + done + if pgrep -P "$serverjob" > /dev/null; then + echo "$1 $2: redis server did not shut down" >&2 + error="1" + failed_runs+="$1:$2 " + run_failed="1" + kill_job_tree "$serverjob" + fi + wait "$serverjob" 2>/dev/null ;; security) echo $2 >> $outfile @@ -605,9 +672,23 @@ function run_test_env_cmd { # "$outfile";; + $timecmd -a -o "$benchres.line" -f "$1${benchfill:${#1}} $2${allocfill:${#2}} %E %M %U %S %F %R" /usr/bin/env $3 $run_wrap $4 < "$infile" > "$outfile" + if test "$?" != "0"; then + error="1" + failed_runs+="$1:$2 " + run_failed="1" + fi + ;; esac + case "$1" in spec-*) popd;; esac + if test -n "$run_failed"; then + # a crashed, cap-killed, or otherwise failed run measured nothing; + # it shows up in the "Failed tests:" summary instead of the results + rm -f "$benchres.line" + return + fi + # fixup larson with relative time case "$1" in redis*) @@ -633,8 +714,6 @@ function run_test_env_cmd { #