Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55aab093e7 | ||
|
|
af61660c42 |
@@ -19,7 +19,7 @@ BENCH_NAME="coreutils"
|
|||||||
|
|
||||||
printf 'benchmarking %s wc vs fastwc: lines, file input (%s interleaved runs each, minimum kept)\n' \
|
printf 'benchmarking %s wc vs fastwc: lines, file input (%s interleaved runs each, minimum kept)\n' \
|
||||||
"$BENCH_NAME" "$BENCH_REPS"
|
"$BENCH_NAME" "$BENCH_REPS"
|
||||||
printf '%-28s %10s %10s %8s %s\n' 'test' 'wc' 'fastwc' 'ratio' 'status'
|
printf '%-28s %10s %22s %8s %s\n' 'test' 'wc' 'fastwc (ms µs)' 'ratio' 'status'
|
||||||
|
|
||||||
printf '%s\n' '--- lines ---'
|
printf '%s\n' '--- lines ---'
|
||||||
if ! run_cases lines -l 10000 100000 1000000 10000000; then
|
if ! run_cases lines -l 10000 100000 1000000 10000000; then
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ BENCH_NAME="coreutils"
|
|||||||
|
|
||||||
printf 'benchmarking %s wc vs fastwc: words, file input (%s interleaved runs each, minimum kept)\n' \
|
printf 'benchmarking %s wc vs fastwc: words, file input (%s interleaved runs each, minimum kept)\n' \
|
||||||
"$BENCH_NAME" "$BENCH_REPS"
|
"$BENCH_NAME" "$BENCH_REPS"
|
||||||
printf '%-28s %10s %10s %8s %s\n' 'test' 'wc' 'fastwc' 'ratio' 'status'
|
printf '%-28s %10s %22s %8s %s\n' 'test' 'wc' 'fastwc (ms µs)' 'ratio' 'status'
|
||||||
|
|
||||||
printf '%s\n' '--- words ---'
|
printf '%s\n' '--- words ---'
|
||||||
if ! run_cases words -w 1 10 100 1000 10000 100000; then
|
if ! run_cases words -w 1 10 100 1000 10000 100000; then
|
||||||
|
|||||||
+38
-21
@@ -131,6 +131,17 @@ time_ms() {
|
|||||||
printf '%s\n' "$(( (e - s) / 1000000 ))"
|
printf '%s\n' "$(( (e - s) / 1000000 ))"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# time_us <cmd...> — run a command once and print elapsed wall time in µs.
|
||||||
|
# More precise than time_ms: sub-millisecond runs come out as e.g. 812,
|
||||||
|
# not 0. The benchmark keeps the µs reading for fastwc and derives the ms.
|
||||||
|
time_us() {
|
||||||
|
local s e
|
||||||
|
s=$(date +%s%N)
|
||||||
|
"$@" >/dev/null 2>&1
|
||||||
|
e=$(date +%s%N)
|
||||||
|
printf '%s\n' "$(( (e - s) / 1000 ))"
|
||||||
|
}
|
||||||
|
|
||||||
# capture_count <cmd...> — print the first whitespace-separated field of a
|
# 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`.
|
# command's output, i.e. the count reported by `wc -w/-l` or `fastwc -w/-l`.
|
||||||
capture_count() {
|
capture_count() {
|
||||||
@@ -151,7 +162,7 @@ write_failed_report() {
|
|||||||
printf 'failure : %s\n' "$reason"
|
printf 'failure : %s\n' "$reason"
|
||||||
printf '\nresults\n'
|
printf '\nresults\n'
|
||||||
printf '%s\n' '-------'
|
printf '%s\n' '-------'
|
||||||
printf '%-28s %10s %10s %8s %s\n' 'test' 'wc' 'fastwc' 'ratio' 'status'
|
printf '%-28s %10s %22s %8s %s\n' 'test' 'wc' 'fastwc (ms µs)' 'ratio' 'status'
|
||||||
printf '%s' "$RESULT_ROWS"
|
printf '%s' "$RESULT_ROWS"
|
||||||
printf '\nfastwc must never be slower than %s wc — benchmark aborted.\n' "$BENCH_NAME"
|
printf '\nfastwc must never be slower than %s wc — benchmark aborted.\n' "$BENCH_NAME"
|
||||||
} > "$report"
|
} > "$report"
|
||||||
@@ -167,7 +178,7 @@ run_case() {
|
|||||||
local mode="$1" lines="$2" flag="$3"
|
local mode="$1" lines="$2" flag="$3"
|
||||||
local noun='lines'; [[ "$lines" -eq 1 ]] && noun='line'
|
local noun='lines'; [[ "$lines" -eq 1 ]] && noun='line'
|
||||||
local label="${mode} (${lines} ${noun})"
|
local label="${mode} (${lines} ${noun})"
|
||||||
local file wc_count fast_count wc_ms fast_ms ratio verdict reason row
|
local file wc_count fast_count wc_ms fast_us fast_ms ratio verdict reason row
|
||||||
local i d
|
local i d
|
||||||
|
|
||||||
file=$(createtxt "$lines") || return 1
|
file=$(createtxt "$lines") || return 1
|
||||||
@@ -177,16 +188,18 @@ run_case() {
|
|||||||
fast_count=$(capture_count "$FASTWC" "$flag" "$file")
|
fast_count=$(capture_count "$FASTWC" "$flag" "$file")
|
||||||
|
|
||||||
# speed: interleaved timing so both commands see identical cache warmth;
|
# speed: interleaved timing so both commands see identical cache warmth;
|
||||||
# keep the minimum of $BENCH_REPS runs each to reduce noise
|
# keep the minimum of $BENCH_REPS runs each to reduce noise; fastwc is
|
||||||
|
# timed in µs so sub-millisecond wins are visible in the report
|
||||||
wc_ms=''
|
wc_ms=''
|
||||||
fast_ms=''
|
fast_us=''
|
||||||
for ((i = 0; i < BENCH_REPS; i++)); do
|
for ((i = 0; i < BENCH_REPS; i++)); do
|
||||||
d=$(time_ms "${WC_CMD[@]}" "$flag" "$file")
|
d=$(time_ms "${WC_CMD[@]}" "$flag" "$file")
|
||||||
[[ -z "$wc_ms" || "$d" -lt "$wc_ms" ]] && wc_ms="$d"
|
[[ -z "$wc_ms" || "$d" -lt "$wc_ms" ]] && wc_ms="$d"
|
||||||
|
|
||||||
d=$(time_ms "$FASTWC" "$flag" "$file")
|
d=$(time_us "$FASTWC" "$flag" "$file")
|
||||||
[[ -z "$fast_ms" || "$d" -lt "$fast_ms" ]] && fast_ms="$d"
|
[[ -z "$fast_us" || "$d" -lt "$fast_us" ]] && fast_us="$d"
|
||||||
done
|
done
|
||||||
|
fast_ms=$(( fast_us / 1000 ))
|
||||||
|
|
||||||
# ratio: how many times faster fastwc is than the reference (wc / fastwc)
|
# ratio: how many times faster fastwc is than the reference (wc / fastwc)
|
||||||
if [[ -n "$wc_ms" && "$wc_ms" -gt 0 ]]; then
|
if [[ -n "$wc_ms" && "$wc_ms" -gt 0 ]]; then
|
||||||
@@ -209,8 +222,9 @@ run_case() {
|
|||||||
reason="fastwc was slower (fastwc: ${fast_ms}ms vs ${BENCH_NAME} wc: ${wc_ms}ms)"
|
reason="fastwc was slower (fastwc: ${fast_ms}ms vs ${BENCH_NAME} wc: ${wc_ms}ms)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
row=$(printf '%-28s %10s %10s %8s %s\n' \
|
row=$(printf '%-28s %10s %22s %8s %s\n' \
|
||||||
"$label" "wc: ${wc_ms}ms" "fastwc: ${fast_ms}ms" "$ratio" "$verdict")
|
"$label" "wc: ${wc_ms}ms" "fastwc: ${fast_ms}ms (${fast_us}µs)" \
|
||||||
|
"$ratio" "$verdict")
|
||||||
RESULT_ROWS+="${row}"$'\n'
|
RESULT_ROWS+="${row}"$'\n'
|
||||||
printf '%s\n' "$row"
|
printf '%s\n' "$row"
|
||||||
|
|
||||||
@@ -228,7 +242,7 @@ run_stdin_case() {
|
|||||||
local mode="$1" lines="$2" flag="$3"
|
local mode="$1" lines="$2" flag="$3"
|
||||||
local noun='lines'; [[ "$lines" -eq 1 ]] && noun='line'
|
local noun='lines'; [[ "$lines" -eq 1 ]] && noun='line'
|
||||||
local label="stdin ${mode} (${lines} ${noun})"
|
local label="stdin ${mode} (${lines} ${noun})"
|
||||||
local file wc_count fast_count wc_ms fast_ms ratio verdict reason row
|
local file wc_count fast_count wc_ms fast_us fast_ms ratio verdict reason row
|
||||||
local i d
|
local i d
|
||||||
|
|
||||||
file=$(createtxt "$lines") || return 1
|
file=$(createtxt "$lines") || return 1
|
||||||
@@ -237,14 +251,15 @@ run_stdin_case() {
|
|||||||
fast_count=$(capture_count "$FASTWC" "$flag" < "$file")
|
fast_count=$(capture_count "$FASTWC" "$flag" < "$file")
|
||||||
|
|
||||||
wc_ms=''
|
wc_ms=''
|
||||||
fast_ms=''
|
fast_us=''
|
||||||
for ((i = 0; i < BENCH_REPS; i++)); do
|
for ((i = 0; i < BENCH_REPS; i++)); do
|
||||||
d=$(time_ms "${WC_CMD[@]}" "$flag" < "$file")
|
d=$(time_ms "${WC_CMD[@]}" "$flag" < "$file")
|
||||||
[[ -z "$wc_ms" || "$d" -lt "$wc_ms" ]] && wc_ms="$d"
|
[[ -z "$wc_ms" || "$d" -lt "$wc_ms" ]] && wc_ms="$d"
|
||||||
|
|
||||||
d=$(time_ms "$FASTWC" "$flag" < "$file")
|
d=$(time_us "$FASTWC" "$flag" < "$file")
|
||||||
[[ -z "$fast_ms" || "$d" -lt "$fast_ms" ]] && fast_ms="$d"
|
[[ -z "$fast_us" || "$d" -lt "$fast_us" ]] && fast_us="$d"
|
||||||
done
|
done
|
||||||
|
fast_ms=$(( fast_us / 1000 ))
|
||||||
|
|
||||||
# ratio: how many times faster fastwc is than the reference (wc / fastwc)
|
# ratio: how many times faster fastwc is than the reference (wc / fastwc)
|
||||||
if [[ -n "$wc_ms" && "$wc_ms" -gt 0 ]]; then
|
if [[ -n "$wc_ms" && "$wc_ms" -gt 0 ]]; then
|
||||||
@@ -267,8 +282,9 @@ run_stdin_case() {
|
|||||||
reason="fastwc was slower (fastwc: ${fast_ms}ms vs ${BENCH_NAME} wc: ${wc_ms}ms)"
|
reason="fastwc was slower (fastwc: ${fast_ms}ms vs ${BENCH_NAME} wc: ${wc_ms}ms)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
row=$(printf '%-28s %10s %10s %8s %s\n' \
|
row=$(printf '%-28s %10s %22s %8s %s\n' \
|
||||||
"$label" "wc: ${wc_ms}ms" "fastwc: ${fast_ms}ms" "$ratio" "$verdict")
|
"$label" "wc: ${wc_ms}ms" "fastwc: ${fast_ms}ms (${fast_us}µs)" \
|
||||||
|
"$ratio" "$verdict")
|
||||||
RESULT_ROWS+="${row}"$'\n'
|
RESULT_ROWS+="${row}"$'\n'
|
||||||
printf '%s\n' "$row"
|
printf '%s\n' "$row"
|
||||||
|
|
||||||
@@ -286,7 +302,7 @@ run_solo_case() {
|
|||||||
local lines="$1" flag="$2"
|
local lines="$1" flag="$2"
|
||||||
local noun='lines'; [[ "$lines" -eq 1 ]] && noun='line'
|
local noun='lines'; [[ "$lines" -eq 1 ]] && noun='line'
|
||||||
local label="solo ${lines} ${noun}"
|
local label="solo ${lines} ${noun}"
|
||||||
local file ms best='' bytes gbps mlps i d
|
local file best_us='' best_ms bytes gbps mlps i d
|
||||||
|
|
||||||
file=$(createtxt "$lines") || {
|
file=$(createtxt "$lines") || {
|
||||||
printf '%-28s %s\n' "$label" 'SKIP (could not create test data)'
|
printf '%-28s %s\n' "$label" 'SKIP (could not create test data)'
|
||||||
@@ -294,16 +310,17 @@ run_solo_case() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((i = 0; i < BENCH_REPS; i++)); do
|
for ((i = 0; i < BENCH_REPS; i++)); do
|
||||||
d=$(time_ms "$FASTWC" "$flag" "$file")
|
d=$(time_us "$FASTWC" "$flag" "$file")
|
||||||
[[ -z "$best" || "$d" -lt "$best" ]] && best="$d"
|
[[ -z "$best_us" || "$d" -lt "$best_us" ]] && best_us="$d"
|
||||||
done
|
done
|
||||||
|
best_ms=$(( best_us / 1000 ))
|
||||||
|
|
||||||
bytes=$((lines * 11))
|
bytes=$((lines * 11))
|
||||||
gbps=$(awk -v b="$bytes" -v ms="$best" 'BEGIN { printf "%.2f", b / ms / 1e6 }')
|
gbps=$(awk -v b="$bytes" -v ms="$best_ms" 'BEGIN { if (ms < 1) ms = 1; printf "%.2f", b / ms / 1e6 }')
|
||||||
mlps=$(awk -v l="$lines" -v ms="$best" 'BEGIN { printf "%.1f", l / ms / 1e3 }')
|
mlps=$(awk -v l="$lines" -v ms="$best_ms" 'BEGIN { if (ms < 1) ms = 1; printf "%.1f", l / ms / 1e3 }')
|
||||||
|
|
||||||
printf '%-28s %12s %12s %14s\n' \
|
printf '%-28s %25s %12s %14s\n' \
|
||||||
"$label" "fastwc: ${best}ms" "${gbps} GB/s" "${mlps} Mlines/s"
|
"$label" "fastwc: ${best_ms}ms (${best_us}µs)" "${gbps} GB/s" "${mlps} Mlines/s"
|
||||||
}
|
}
|
||||||
|
|
||||||
# run_cases <words|lines> <-w|-l> <size...> — run run_case for every size,
|
# run_cases <words|lines> <-w|-l> <size...> — run run_case for every size,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ BENCH_NAME="coreutils"
|
|||||||
|
|
||||||
printf 'benchmarking %s wc vs fastwc: stdin (%s interleaved runs each, minimum kept)\n' \
|
printf 'benchmarking %s wc vs fastwc: stdin (%s interleaved runs each, minimum kept)\n' \
|
||||||
"$BENCH_NAME" "$BENCH_REPS"
|
"$BENCH_NAME" "$BENCH_REPS"
|
||||||
printf '%-28s %10s %10s %8s %s\n' 'test' 'wc' 'fastwc' 'ratio' 'status'
|
printf '%-28s %10s %22s %8s %s\n' 'test' 'wc' 'fastwc (ms µs)' 'ratio' 'status'
|
||||||
|
|
||||||
printf '%s\n' '--- stdin lines ---'
|
printf '%s\n' '--- stdin lines ---'
|
||||||
if ! run_stdin_cases lines -l 10000 100000 1000000 10000000; then
|
if ! run_stdin_cases lines -l 10000 100000 1000000 10000000; then
|
||||||
|
|||||||
+6
-5
@@ -43,11 +43,12 @@ which fits, runs at ~17 GB/s, and that number is the counting.
|
|||||||
the data comes through stdin, but how we read it is our business.
|
the data comes through stdin, but how we read it is our business.
|
||||||
The stdin suite is why this shows up in the scoreboard too.
|
The stdin suite is why this shows up in the scoreboard too.
|
||||||
3. **Parallel across cores.** Files over 8 MiB are split into 64-byte
|
3. **Parallel across cores.** Files over 8 MiB are split into 64-byte
|
||||||
aligned slices counted by up to 16 threads (12 past 32 MiB, 16 past
|
aligned slices counted by up to one thread per core (capped at 24)
|
||||||
256 MiB). The kernels are pure, so the split needs no locks; word
|
past 256 MiB, 12 past 32 MiB, 4 past 8 MiB. The kernels are pure, so
|
||||||
boundaries between slices are seeded from the byte before the slice,
|
the split needs no locks; word boundaries between slices are seeded
|
||||||
which makes the split exact. Below 8 MiB the thread spawn would cost
|
from the byte before the slice, which makes the split exact. Below
|
||||||
more than the counting, so we don't bother.
|
8 MiB the thread spawn would cost more than the counting, so we don't
|
||||||
|
bother.
|
||||||
4. **No work that isn't asked for.** `-c` on a regular file is
|
4. **No work that isn't asked for.** `-c` on a regular file is
|
||||||
`st_size` from `fstat` — GNU figured that one out too, so we copied
|
`st_size` from `fstat` — GNU figured that one out too, so we copied
|
||||||
the good idea. `-l` without `-w` skips the whitespace mask entirely.
|
the good idea. `-l` without `-w` skips the whitespace mask entirely.
|
||||||
|
|||||||
+21
-13
@@ -36,6 +36,11 @@ enum
|
|||||||
F_BYTES = 1 << 3, /* -c: bytes */
|
F_BYTES = 1 << 3, /* -c: bytes */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
MAX_THREADS = 24, /* pick_threads() and count_sliced() agree on this */
|
||||||
|
};
|
||||||
|
|
||||||
static int flags = 0;
|
static int flags = 0;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -623,18 +628,15 @@ static void count_sliced(const unsigned char *p, size_t n, int nt,
|
|||||||
int need_lines, int need_words, long long *lines,
|
int need_lines, int need_words, long long *lines,
|
||||||
long long *words)
|
long long *words)
|
||||||
{
|
{
|
||||||
enum
|
|
||||||
{
|
|
||||||
MAX_THREADS = 16
|
|
||||||
};
|
|
||||||
mjob_t jobs[MAX_THREADS];
|
mjob_t jobs[MAX_THREADS];
|
||||||
pthread_t th[MAX_THREADS];
|
pthread_t th[MAX_THREADS];
|
||||||
long long tl = 0, tw = 0;
|
long long tl = 0, tw = 0;
|
||||||
size_t per = (n + (size_t)nt - 1) / (size_t)nt;
|
size_t per;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (nt > MAX_THREADS)
|
if (nt > MAX_THREADS)
|
||||||
nt = MAX_THREADS;
|
nt = MAX_THREADS; /* cap first: per is derived from the real nt */
|
||||||
|
per = (n + (size_t)nt - 1) / (size_t)nt;
|
||||||
per = (per + 63) & ~(size_t)63;
|
per = (per + 63) & ~(size_t)63;
|
||||||
if (per == 0)
|
if (per == 0)
|
||||||
per = 64;
|
per = 64;
|
||||||
@@ -678,16 +680,18 @@ static int pick_threads(size_t n)
|
|||||||
long ncpu = sysconf(_SC_NPROCESSORS_ONLN);
|
long ncpu = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
int nt;
|
int nt;
|
||||||
|
|
||||||
|
if (ncpu <= 0)
|
||||||
|
ncpu = 1;
|
||||||
if (n >= (size_t)256 << 20)
|
if (n >= (size_t)256 << 20)
|
||||||
nt = 16;
|
nt = (int)ncpu; /* monsters: one thread per core, readahead wins */
|
||||||
else if (n >= (size_t)32 << 20)
|
else if (n >= (size_t)32 << 20)
|
||||||
nt = 12;
|
nt = (int)ncpu < 12 ? (int)ncpu : 12;
|
||||||
else if (n >= (size_t)8 << 20)
|
else if (n >= (size_t)8 << 20)
|
||||||
nt = 4;
|
nt = (int)ncpu < 4 ? (int)ncpu : 4;
|
||||||
else
|
else
|
||||||
nt = 1;
|
nt = 1;
|
||||||
if (ncpu > 0 && nt > ncpu)
|
if (nt > MAX_THREADS)
|
||||||
nt = (int)ncpu;
|
nt = MAX_THREADS;
|
||||||
return nt;
|
return nt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,13 +1107,16 @@ static int check_sliced(void)
|
|||||||
static unsigned char buf[9001];
|
static unsigned char buf[9001];
|
||||||
int fails = 0;
|
int fails = 0;
|
||||||
size_t n, k;
|
size_t n, k;
|
||||||
|
static const int tcs[] = {
|
||||||
|
1, 2, 3, 4, 8, 12, 16, MAX_THREADS, MAX_THREADS + 1};
|
||||||
|
|
||||||
for (n = 0; n <= 9000; n += (n < 300 ? 1 : 37))
|
for (n = 0; n <= 9000; n += (n < 300 ? 1 : 37))
|
||||||
{
|
{
|
||||||
for (k = 0; k < n; k++)
|
for (k = 0; k < n; k++)
|
||||||
buf[k] = (unsigned char)rng32();
|
buf[k] = (unsigned char)rng32();
|
||||||
for (int nt = 1; nt <= 8; nt++)
|
for (size_t ti = 0; ti < sizeof tcs / sizeof tcs[0]; ti++)
|
||||||
{
|
{
|
||||||
|
int nt = tcs[ti];
|
||||||
long long tl = 0, tw = 0;
|
long long tl = 0, tw = 0;
|
||||||
long long want_l = count_newlines(buf, n);
|
long long want_l = count_newlines(buf, n);
|
||||||
int pw = 1;
|
int pw = 1;
|
||||||
@@ -1133,8 +1140,9 @@ static int check_sliced(void)
|
|||||||
{
|
{
|
||||||
for (k = 0; k < n; k++)
|
for (k = 0; k < n; k++)
|
||||||
buf[k] = (unsigned char)" \t\n\v\f\r\xa0x"[rng32() % 8];
|
buf[k] = (unsigned char)" \t\n\v\f\r\xa0x"[rng32() % 8];
|
||||||
for (int nt = 1; nt <= 8; nt++)
|
for (size_t ti = 0; ti < sizeof tcs / sizeof tcs[0]; ti++)
|
||||||
{
|
{
|
||||||
|
int nt = tcs[ti];
|
||||||
long long tl = 0, tw = 0;
|
long long tl = 0, tw = 0;
|
||||||
long long want_l = count_newlines(buf, n);
|
long long want_l = count_newlines(buf, n);
|
||||||
int pw = 1;
|
int pw = 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user