first: setup
setting up repo.
This commit is contained in:
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# bench-busybox.sh — benchmark the busybox wc applet against the release
|
||||
# build of fastwc. Fails fast: the moment fastwc is slower than (or
|
||||
# disagrees with) busybox wc, a human readable report is written to
|
||||
# FAILED-benchmark.txt and this script exits non-zero.
|
||||
#
|
||||
# usage: ./bench-busybox.sh
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=std.sh
|
||||
source "$SCRIPT_DIR/std.sh"
|
||||
|
||||
checkfastwc
|
||||
checkwc busybox
|
||||
BENCH_NAME="busybox"
|
||||
|
||||
run_benchmark_suite
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# bench-coreutils.sh — benchmark the GNU Coreutils wc implementation against
|
||||
# the release build of fastwc. Fails fast: the moment fastwc is slower than
|
||||
# (or disagrees with) coreutils wc, a human readable report is written to
|
||||
# FAILED-benchmark.txt and this script exits non-zero.
|
||||
#
|
||||
# usage: ./bench-coreutils.sh
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=std.sh
|
||||
source "$SCRIPT_DIR/std.sh"
|
||||
|
||||
checkfastwc
|
||||
checkwc coreutils
|
||||
BENCH_NAME="coreutils"
|
||||
|
||||
run_benchmark_suite
|
||||
Executable
+257
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# std.sh — shared "standard library" for the fastwc benchmark scripts.
|
||||
#
|
||||
# Provides the helpers every benchmark script needs:
|
||||
# checkfastwc() verify a release build of fastwc exists
|
||||
# checkwc() locate the coreutils or busybox wc implementation
|
||||
# createrandstr() print one random 10-character alphanumeric string
|
||||
# createtxt() create (or reuse) a text file with N such lines
|
||||
# run_benchmark_suite() run every word/line case for the selected wc
|
||||
#
|
||||
# Source this file from a benchmark script, then:
|
||||
# checkfastwc
|
||||
# checkwc coreutils # or: checkwc busybox
|
||||
# BENCH_NAME="coreutils"
|
||||
# run_benchmark_suite
|
||||
#
|
||||
# The suite fails fast: the moment fastwc is slower than (or disagrees
|
||||
# with) the selected wc implementation, it writes a human readable report
|
||||
# to FAILED-benchmark.txt and returns non-zero.
|
||||
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
FASTWC="$SCRIPT_DIR/../bin/release/fastwc"
|
||||
DATA_DIR="$SCRIPT_DIR/.data"
|
||||
GENFILE="$SCRIPT_DIR/tools/genfile" # optional C helper, built by test-all.sh
|
||||
|
||||
BENCH_NAME="${BENCH_NAME:-wc}" # set by the caller: coreutils | busybox
|
||||
BENCH_REPS="${BENCH_REPS:-3}" # interleaved runs per case; minimum is kept
|
||||
RESULT_ROWS="" # accumulated results table
|
||||
|
||||
WC_CMD=() # filled by checkwc(), e.g. (wc) or (busybox wc)
|
||||
TEXT_FILE="" # filled by createtxt()
|
||||
|
||||
if [[ -z "$BENCH_REPS" || "$BENCH_REPS" -lt 1 ]]; then
|
||||
BENCH_REPS=1
|
||||
fi
|
||||
|
||||
# checkfastwc — make sure ../bin/release/fastwc exists and is executable.
|
||||
# Mimics autotools configure: prints "checking for ... yes/no" and bails
|
||||
# out with a helpful message when the release build is missing.
|
||||
checkfastwc() {
|
||||
printf 'checking for release build fastwc... '
|
||||
if [[ -x "$FASTWC" ]]; then
|
||||
printf 'yes\n'
|
||||
return 0
|
||||
fi
|
||||
printf 'no\n'
|
||||
printf 'configure: error: no release build of fastwc found at %s\n' "$FASTWC" >&2
|
||||
printf 'configure: error: run "make release" first to generate one\n' >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# checkwc <coreutils|busybox> — locate the requested wc implementation and
|
||||
# store its invocation in $WC_CMD. Mimics autotools configure output and
|
||||
# exits on failure.
|
||||
checkwc() {
|
||||
local impl="$1"
|
||||
case "$impl" in
|
||||
coreutils)
|
||||
printf 'checking for coreutils wc... '
|
||||
if command -v wc >/dev/null 2>&1 \
|
||||
&& wc --version 2>/dev/null | head -n1 | grep -qi 'GNU coreutils'; then
|
||||
printf 'yes\n'
|
||||
WC_CMD=(wc)
|
||||
else
|
||||
printf 'no\n'
|
||||
printf 'configure: error: GNU Coreutils wc not found in PATH\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
busybox)
|
||||
printf 'checking for busybox wc... '
|
||||
if command -v busybox >/dev/null 2>&1 \
|
||||
&& busybox --list 2>/dev/null | grep -qx 'wc'; then
|
||||
printf 'yes\n'
|
||||
WC_CMD=(busybox wc)
|
||||
else
|
||||
printf 'no\n'
|
||||
printf 'configure: error: busybox (with the wc applet) not found in PATH\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
printf 'checkwc: error: unknown implementation "%s" (expected coreutils or busybox)\n' "$impl" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# createrandstr — print one 10-character random alphanumeric combination.
|
||||
createrandstr() {
|
||||
local chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
local out='' i
|
||||
for ((i = 0; i < 10; i++)); do
|
||||
out+="${chars:$((RANDOM % ${#chars})):1}"
|
||||
done
|
||||
printf '%s\n' "$out"
|
||||
}
|
||||
|
||||
# createtxt <lines> — make sure a text file with <lines> rows of random
|
||||
# 10-character alphanumeric strings exists. A copy generated by a previous
|
||||
# run is reused (checked by exact byte size: 10 chars + '\n' per line), so
|
||||
# repeated benchmark runs are cheap. Prints the path and sets $TEXT_FILE.
|
||||
createtxt() {
|
||||
local lines="$1"
|
||||
local expect=$((lines * 11))
|
||||
local have=0
|
||||
|
||||
TEXT_FILE="$DATA_DIR/words-$lines.txt"
|
||||
|
||||
if [[ -f "$TEXT_FILE" ]]; then
|
||||
have=$(stat -c '%s' "$TEXT_FILE" 2>/dev/null || printf '0')
|
||||
fi
|
||||
|
||||
if [[ "$have" -ne "$expect" ]]; then
|
||||
mkdir -p "$DATA_DIR"
|
||||
if [[ -x "$GENFILE" ]]; then
|
||||
"$GENFILE" "$lines" > "$TEXT_FILE" || {
|
||||
printf 'createtxt: error: failed to generate %s\n' "$TEXT_FILE" >&2
|
||||
return 1
|
||||
}
|
||||
else
|
||||
printf 'createtxt: warning: %s not built, using slow shell fallback\n' "$GENFILE" >&2
|
||||
printf 'createtxt: warning: run ./test-all.sh to build the helper tools\n' >&2
|
||||
: > "$TEXT_FILE"
|
||||
for ((i = 0; i < lines; i++)); do
|
||||
createrandstr >> "$TEXT_FILE"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s\n' "$TEXT_FILE"
|
||||
}
|
||||
|
||||
# time_ms <cmd...> — run a command once and print elapsed wall time in ms.
|
||||
time_ms() {
|
||||
local s e
|
||||
s=$(date +%s%N)
|
||||
"$@" >/dev/null 2>&1
|
||||
e=$(date +%s%N)
|
||||
printf '%s\n' "$(( (e - s) / 1000000 ))"
|
||||
}
|
||||
|
||||
# capture_count <cmd...> — print the first whitespace-separated field of a
|
||||
# command's output, i.e. the count reported by `wc -w/-l` or `fastwc -w/-l`.
|
||||
capture_count() {
|
||||
"$@" 2>/dev/null | awk 'NR == 1 { print $1 }'
|
||||
}
|
||||
|
||||
# write_failed_report <label> <reason> <wc_ms> <fast_ms> — write the
|
||||
# human readable failure report to FAILED-benchmark.txt.
|
||||
write_failed_report() {
|
||||
local label="$1" reason="$2" wc_ms="$3" fast_ms="$4"
|
||||
local report="$SCRIPT_DIR/FAILED-benchmark.txt"
|
||||
|
||||
{
|
||||
printf 'fastwc benchmark FAILED\n'
|
||||
printf '=======================\n'
|
||||
printf 'implementation : %s wc\n' "$BENCH_NAME"
|
||||
printf 'failed test : %s\n' "$label"
|
||||
printf 'failure : %s\n' "$reason"
|
||||
printf '\nresults\n'
|
||||
printf '%s\n' '-------'
|
||||
printf '%-28s %10s %10s %8s %s\n' 'test' 'wc' 'fastwc' 'ratio' 'status'
|
||||
printf '%s' "$RESULT_ROWS"
|
||||
printf '\nfastwc must never be slower than %s wc — benchmark aborted.\n' "$BENCH_NAME"
|
||||
} > "$report"
|
||||
|
||||
printf '\nbenchmark FAILED (%s): %s\n' "$BENCH_NAME" "$reason" >&2
|
||||
printf 'full results written to %s\n' "$report" >&2
|
||||
}
|
||||
|
||||
# run_case <words|lines> <n-lines> <-w|-l> — create (or reuse) the text file,
|
||||
# then race the selected wc against fastwc. Fails the benchmark the moment
|
||||
# fastwc is slower or reports a different count.
|
||||
run_case() {
|
||||
local mode="$1" lines="$2" flag="$3"
|
||||
local noun='lines'; [[ "$lines" -eq 1 ]] && noun='line'
|
||||
local label="${mode} (${lines} ${noun})"
|
||||
local file wc_count fast_count wc_ms fast_ms ratio verdict reason row
|
||||
local i d
|
||||
|
||||
file=$(createtxt "$lines") || return 1
|
||||
|
||||
# correctness: fastwc must report the same count as the reference wc
|
||||
wc_count=$(capture_count "${WC_CMD[@]}" "$flag" "$file")
|
||||
fast_count=$(capture_count "$FASTWC" "$flag" "$file")
|
||||
|
||||
# speed: interleaved timing so both commands see identical cache warmth;
|
||||
# keep the minimum of $BENCH_REPS runs each to reduce noise
|
||||
wc_ms=''
|
||||
fast_ms=''
|
||||
for ((i = 0; i < BENCH_REPS; i++)); do
|
||||
d=$(time_ms "${WC_CMD[@]}" "$flag" "$file")
|
||||
[[ -z "$wc_ms" || "$d" -lt "$wc_ms" ]] && wc_ms="$d"
|
||||
|
||||
d=$(time_ms "$FASTWC" "$flag" "$file")
|
||||
[[ -z "$fast_ms" || "$d" -lt "$fast_ms" ]] && fast_ms="$d"
|
||||
done
|
||||
|
||||
if [[ -n "$wc_ms" && "$wc_ms" -gt 0 ]]; then
|
||||
ratio=$(awk -v f="$fast_ms" -v w="$wc_ms" 'BEGIN { printf "%.2fx", f / w }')
|
||||
else
|
||||
ratio='-'
|
||||
fi
|
||||
|
||||
verdict='PASS'
|
||||
reason=''
|
||||
if [[ "$fast_count" != "$wc_count" ]]; then
|
||||
verdict='FAIL'
|
||||
reason="output mismatch (fastwc: ${fast_count}, ${BENCH_NAME} wc: ${wc_count})"
|
||||
elif (( fast_ms > wc_ms )); then
|
||||
verdict='FAIL'
|
||||
reason="fastwc was slower (fastwc: ${fast_ms}ms vs ${BENCH_NAME} wc: ${wc_ms}ms)"
|
||||
fi
|
||||
|
||||
row=$(printf '%-28s %10s %10s %8s %s\n' \
|
||||
"$label" "wc: ${wc_ms}ms" "fastwc: ${fast_ms}ms" "$ratio" "$verdict")
|
||||
RESULT_ROWS+="${row}"$'\n'
|
||||
printf '%s\n' "$row"
|
||||
|
||||
if [[ "$verdict" == 'FAIL' ]]; then
|
||||
write_failed_report "$label" "$reason" "$wc_ms" "$fast_ms"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# run_benchmark_suite — run every word and line case for the wc selected by
|
||||
# checkwc(). Returns non-zero the first time fastwc loses.
|
||||
run_benchmark_suite() {
|
||||
local rc=0 size
|
||||
|
||||
printf 'benchmarking %s wc vs fastwc (%s interleaved runs each, minimum kept)\n' \
|
||||
"$BENCH_NAME" "$BENCH_REPS"
|
||||
printf '%-28s %10s %10s %8s %s\n' 'test' 'wc' 'fastwc' 'ratio' 'status'
|
||||
|
||||
printf '%s\n' '--- words ---'
|
||||
for size in 1 10 100 1000 10000 100000; do
|
||||
run_case words "$size" -w || { rc=1; break; }
|
||||
done
|
||||
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
printf '%s\n' '--- lines ---'
|
||||
for size in 10000 100000 1000000 10000000; do
|
||||
run_case lines "$size" -l || { rc=1; break; }
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
printf '\nall %s benchmarks passed — fastwc was never slower than %s wc\n' \
|
||||
"$BENCH_NAME" "$BENCH_NAME"
|
||||
fi
|
||||
return $rc
|
||||
}
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-all.sh — compile the benchmark helper tools first, then run every
|
||||
# benchmark (coreutils and busybox). Exits non-zero if any of them fails.
|
||||
#
|
||||
# usage: ./test-all.sh
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# 1. build the helper tools before running any benchmark so createtxt()
|
||||
# can use the fast C generator instead of the slow shell fallback
|
||||
mkdir -p "$SCRIPT_DIR/tools"
|
||||
if ! cc -O2 -Wall -o "$SCRIPT_DIR/tools/genfile" "$SCRIPT_DIR/tools/genfile.c"; then
|
||||
printf 'test-all: error: failed to compile %s\n' "$SCRIPT_DIR/tools/genfile.c" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'built %s\n' "$SCRIPT_DIR/tools/genfile"
|
||||
|
||||
# 2. run each benchmark (both run regardless, so every result is reported)
|
||||
"$SCRIPT_DIR/bench-coreutils.sh"
|
||||
rc_coreutils=$?
|
||||
"$SCRIPT_DIR/bench-busybox.sh"
|
||||
rc_busybox=$?
|
||||
|
||||
# 3. summarize
|
||||
if [[ $rc_coreutils -ne 0 || $rc_busybox -ne 0 ]]; then
|
||||
printf '\ntest-all: FAILED (coreutils=%s, busybox=%s)\n' "$rc_coreutils" "$rc_busybox" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '\ntest-all: all benchmarks passed\n'
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* genfile — generate a text file of N lines, each a random 10-character
|
||||
* alphanumeric string followed by a newline. Used by the fastwc benchmarks
|
||||
* (createtxt in std.sh) to build test data quickly; std.sh falls back to a
|
||||
* slow shell loop when this binary has not been built.
|
||||
*
|
||||
* usage: genfile <lines>
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LINE_LEN 10
|
||||
#define CHUNK (64 * 1024)
|
||||
|
||||
static const char alpha[] =
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"0123456789";
|
||||
#define ALPHA_LEN (sizeof(alpha) - 1)
|
||||
|
||||
static uint64_t state;
|
||||
|
||||
static uint64_t next(void)
|
||||
{
|
||||
state ^= state << 13;
|
||||
state ^= state >> 7;
|
||||
state ^= state << 17;
|
||||
return state;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
long n;
|
||||
long i;
|
||||
int j;
|
||||
char buf[CHUNK];
|
||||
size_t used = 0;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s <lines>\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
n = atol(argv[1]);
|
||||
if (n < 0) {
|
||||
fprintf(stderr, "genfile: invalid line count: %s\n", argv[1]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* seed from time + pid so each run produces fresh data */
|
||||
state = (uint64_t)time(NULL) ^ ((uint64_t)getpid() << 32) ^ 0x9E3779B97F4A7C15ULL;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
for (j = 0; j < LINE_LEN; j++)
|
||||
buf[used++] = alpha[next() % ALPHA_LEN];
|
||||
buf[used++] = '\n';
|
||||
if (used + LINE_LEN + 1 > CHUNK) {
|
||||
if (fwrite(buf, 1, used, stdout) != used)
|
||||
return 1;
|
||||
used = 0;
|
||||
}
|
||||
}
|
||||
if (used && fwrite(buf, 1, used, stdout) != used)
|
||||
return 1;
|
||||
if (fclose(stdout) != 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user