fix: thread-safety in count_sliced, ship measured wins
Review (5-lane) found one MAJOR: count_sliced ignored pthread_create's return value - on EAGAIN it joined an indeterminate pthread_t and summed an uninitialized slice, UB plus a silently wrong count. Threads are now initialized to 0, a failed create counts its slice inline, and nt is hard-capped at a named MAX_THREADS (the fixed jobs[8]/th[8] arrays smash the stack past 8 threads - reproduced by QA's thread sweep). Shipped from the measured optimization hunt: - -w-only mode skips the newline compare/popcount entirely (the (x-9)<5 range already covers '\n' in the whitespace mask) - a need_lines gate threads through every kernel, the scalar reference, the avx512 mirror, and the sliced workers. - Thread retune: 12 threads past 32 MiB, 16 past 256 MiB (was 8) - up to 18% on 110 MB, ~3% on 1.1 GB in QA's interleaved sweep. - Benchmark suites pin LC_ALL=C so GNU wc -w can't silently switch to multibyte decoding and inflate the win. - popcount16 is guarded to x86 builds (zero-warnings on other arches); checkwc's stray argument dropped. Verified: selftest (kernels + sliced, now with need_lines coverage), 120-trial fuzz vs GNU (file + stdin, both locales), full test-all suite three consecutive times, format-check and clang-tidy clean. The 1B solo monster now lands at 3.7-6s (up to 268 Mlines/s) depending on how warm the page cache is feeling.
This commit is contained in:
@@ -28,7 +28,7 @@ writes a shame report and exits non-zero. These are the facts:
|
||||
| lines (1M lines) | **Win: 1ms vs 2ms.** GNU's AVX-512 assist can't beat a mapped file. |
|
||||
| lines (10M lines) | **Win: 8-9ms vs 22-24ms (~2.5x).** GNU's lead never survives contact with the buffer. |
|
||||
| lines (100M lines) | **Win: ~70ms vs ~140ms.** The monster race. GNU gets lapped. |
|
||||
| lines (1B lines) | **Solo, ~6-8s.** 11 GB in one pass; the only bottleneck left is the disk. |
|
||||
| lines (1B lines) | **Solo, ~4-6s.** 11 GB in one pass; the only bottleneck left is the disk. |
|
||||
| stdin words (1M lines) | **Win: 12x.** GNU still reads stdin like it's 1985. |
|
||||
| stdin lines (10M lines) | **Win: ~2.5x.** We map stdin redirects; GNU maps nothing. |
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
source "$REPO_DIR/benchmarks/std.sh"
|
||||
|
||||
checkfastwc
|
||||
checkwc coreutils
|
||||
checkwc
|
||||
BENCH_NAME="coreutils"
|
||||
|
||||
printf 'benchmarking %s wc vs fastwc: lines, file input (%s interleaved runs each, minimum kept)\n' \
|
||||
|
||||
@@ -12,7 +12,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
source "$REPO_DIR/benchmarks/std.sh"
|
||||
|
||||
checkfastwc
|
||||
checkwc coreutils
|
||||
checkwc
|
||||
BENCH_NAME="coreutils"
|
||||
|
||||
printf 'benchmarking %s wc vs fastwc: words, file input (%s interleaved runs each, minimum kept)\n' \
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
|
||||
set -u
|
||||
|
||||
# Pin the C locale: GNU wc -w silently switches to multibyte decoding under a
|
||||
# UTF-8 locale, which would slow the oracle down and mask the documented
|
||||
# byte-semantics divergence. Both sides count bytes here.
|
||||
export LC_ALL=C
|
||||
export LC_CTYPE=C
|
||||
|
||||
FASTWC="$REPO_DIR/bin/release/fastwc"
|
||||
DATA_DIR="$SCRIPT_DIR/.data"
|
||||
GENFILE="$REPO_DIR/benchmarks/tools/genfile" # optional C helper, built by test-all.sh
|
||||
|
||||
@@ -14,7 +14,7 @@ REPO_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
source "$REPO_DIR/benchmarks/std.sh"
|
||||
|
||||
checkfastwc
|
||||
checkwc coreutils
|
||||
checkwc
|
||||
BENCH_NAME="coreutils"
|
||||
|
||||
printf 'benchmarking %s wc vs fastwc: stdin (%s interleaved runs each, minimum kept)\n' \
|
||||
|
||||
+8
-7
@@ -17,12 +17,13 @@ case, so every number below survived contact with the contract.
|
||||
| lines, 1M | 2-3ms | **1ms** |
|
||||
| lines, 10M | 21-24ms | **8-9ms** |
|
||||
| lines, 100M (monster) | ~140ms | **~70ms** |
|
||||
| lines, 1B (solo) | — | **~6s** |
|
||||
| lines, 1B (solo) | — | **~4-6s** |
|
||||
| bytes, 1GB sparse | reads all of it | `st_size`, no read |
|
||||
|
||||
That is a ~2.5x win over GNU on 10M lines, a 2x win on 1M lines, and
|
||||
a 2x win on the 100M monster. At 1B lines — 11 GB of data — the solo
|
||||
run lands around 6-8 seconds (125-170 Mlines/s), and the bottleneck is
|
||||
run lands around 4-6 seconds (200-270 Mlines/s, warm cache), and the
|
||||
bottleneck is
|
||||
honest to admit: an 11 GB file does not fit in the 15 GB of RAM this
|
||||
machine has, so the last monster is racing the disk. The 100M case,
|
||||
which fits, runs at ~17 GB/s, and that number is the counting.
|
||||
@@ -42,11 +43,11 @@ 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 stdin suite is why this shows up in the scoreboard too.
|
||||
3. **Parallel across cores.** Files over 8 MiB are split into 64-byte
|
||||
aligned slices counted by up to 8 threads. The kernels are pure, so
|
||||
the split needs no locks; word boundaries between slices are seeded
|
||||
from the byte before the slice, which makes the split exact. Below
|
||||
8 MiB the thread spawn would cost more than the counting, so we
|
||||
don't bother.
|
||||
aligned slices counted by up to 16 threads (12 past 32 MiB, 16 past
|
||||
256 MiB). The kernels are pure, so the split needs no locks; word
|
||||
boundaries between slices are seeded from the byte before the slice,
|
||||
which makes the split exact. Below 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
|
||||
`st_size` from `fstat` — GNU figured that one out too, so we copied
|
||||
the good idea. `-l` without `-w` skips the whitespace mask entirely.
|
||||
|
||||
+102
-56
@@ -146,6 +146,7 @@ static long long count_words(const unsigned char *s, size_t n, int *prev_ws)
|
||||
return w;
|
||||
}
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
/* SSE2 predates POPCNT; count 16-bit masks with the classic bit trick. */
|
||||
static unsigned popcount16(unsigned x)
|
||||
{
|
||||
@@ -154,6 +155,7 @@ static unsigned popcount16(unsigned x)
|
||||
x = (x + (x >> 4)) & 0x0f0f;
|
||||
return (x + (x >> 8)) & 0xff;
|
||||
}
|
||||
#endif /* x86 */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -162,7 +164,7 @@ typedef struct
|
||||
} lw_t;
|
||||
|
||||
typedef lw_t (*count_lw_fn)(const unsigned char *s, size_t n, int *prev_ws,
|
||||
int need_words);
|
||||
int need_lines, int need_words);
|
||||
|
||||
/*
|
||||
* Word separators match GNU wc (the benchmark oracle): the six C-locale
|
||||
@@ -172,7 +174,8 @@ typedef lw_t (*count_lw_fn)(const unsigned char *s, size_t n, int *prev_ws,
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
|
||||
__attribute__((target("avx512f,avx512bw"))) static lw_t
|
||||
count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_lines,
|
||||
int need_words)
|
||||
{
|
||||
const __m512i nl = _mm512_set1_epi8('\n');
|
||||
const __m512i sp = _mm512_set1_epi8(' ');
|
||||
@@ -186,13 +189,18 @@ count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
for (; i + 64 <= n; i += 64)
|
||||
{
|
||||
__m512i v = _mm512_loadu_si512((const void *)(s + i));
|
||||
uint64_t nl_mask = (uint64_t)_mm512_cmpeq_epi8_mask(v, nl);
|
||||
uint64_t nl_mask = 0;
|
||||
|
||||
lines += (long long)_mm_popcnt_u64(nl_mask);
|
||||
if (need_lines)
|
||||
{
|
||||
nl_mask = (uint64_t)_mm512_cmpeq_epi8_mask(v, nl);
|
||||
lines += (long long)_mm_popcnt_u64(nl_mask);
|
||||
}
|
||||
|
||||
if (need_words)
|
||||
{
|
||||
/* min(d, 4) == d <=> (x - 9) < 5 unsigned */
|
||||
/* min(d, 4) == d <=> (x - 9) < 5 unsigned; the range
|
||||
* already covers '\n', so no newline compare is needed */
|
||||
__m512i d = _mm512_sub_epi8(v, lo);
|
||||
uint64_t ws =
|
||||
nl_mask | (uint64_t)_mm512_cmpeq_epi8_mask(v, sp) |
|
||||
@@ -206,7 +214,8 @@ count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
for (; i < n; i++)
|
||||
{
|
||||
int ws = ws_tab[s[i]];
|
||||
lines += s[i] == '\n';
|
||||
if (need_lines)
|
||||
lines += s[i] == '\n';
|
||||
if (need_words)
|
||||
{
|
||||
if (prev && !ws)
|
||||
@@ -221,7 +230,8 @@ count_lw_avx512(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
}
|
||||
|
||||
__attribute__((target("avx2"))) static lw_t
|
||||
count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_lines,
|
||||
int need_words)
|
||||
{
|
||||
const __m256i nl = _mm256_set1_epi8('\n');
|
||||
const __m256i sp = _mm256_set1_epi8(' ');
|
||||
@@ -235,14 +245,18 @@ count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
for (; i + 32 <= n; i += 32)
|
||||
{
|
||||
__m256i v = _mm256_loadu_si256((const void *)(s + i));
|
||||
uint32_t nl_mask =
|
||||
(uint32_t)_mm256_movemask_epi8(_mm256_cmpeq_epi8(v, nl));
|
||||
uint32_t nl_mask = 0;
|
||||
|
||||
lines += (long long)_mm_popcnt_u32(nl_mask);
|
||||
if (need_lines)
|
||||
{
|
||||
nl_mask = (uint32_t)_mm256_movemask_epi8(_mm256_cmpeq_epi8(v, nl));
|
||||
lines += (long long)_mm_popcnt_u32(nl_mask);
|
||||
}
|
||||
|
||||
if (need_words)
|
||||
{
|
||||
/* min(d, 4) == d <=> (x - 9) < 5 unsigned */
|
||||
/* min(d, 4) == d <=> (x - 9) < 5 unsigned; the range
|
||||
* already covers '\n', so no newline compare is needed */
|
||||
__m256i d = _mm256_sub_epi8(v, lo);
|
||||
uint32_t ws =
|
||||
nl_mask |
|
||||
@@ -258,7 +272,8 @@ count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
for (; i < n; i++)
|
||||
{
|
||||
int ws = ws_tab[s[i]];
|
||||
lines += s[i] == '\n';
|
||||
if (need_lines)
|
||||
lines += s[i] == '\n';
|
||||
if (need_words)
|
||||
{
|
||||
if (prev && !ws)
|
||||
@@ -273,7 +288,8 @@ count_lw_avx2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
}
|
||||
|
||||
__attribute__((target("sse2"))) static lw_t
|
||||
count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_lines,
|
||||
int need_words)
|
||||
{
|
||||
const __m128i nl = _mm_set1_epi8('\n');
|
||||
const __m128i sp = _mm_set1_epi8(' ');
|
||||
@@ -287,9 +303,13 @@ count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
for (; i + 16 <= n; i += 16)
|
||||
{
|
||||
__m128i v = _mm_loadu_si128((const void *)(s + i));
|
||||
uint32_t nl_mask = (uint32_t)_mm_movemask_epi8(_mm_cmpeq_epi8(v, nl));
|
||||
uint32_t nl_mask = 0;
|
||||
|
||||
lines += (long long)popcount16(nl_mask);
|
||||
if (need_lines)
|
||||
{
|
||||
nl_mask = (uint32_t)_mm_movemask_epi8(_mm_cmpeq_epi8(v, nl));
|
||||
lines += (long long)popcount16(nl_mask);
|
||||
}
|
||||
|
||||
if (need_words)
|
||||
{
|
||||
@@ -307,7 +327,8 @@ count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
for (; i < n; i++)
|
||||
{
|
||||
int ws = ws_tab[s[i]];
|
||||
lines += s[i] == '\n';
|
||||
if (need_lines)
|
||||
lines += s[i] == '\n';
|
||||
if (need_words)
|
||||
{
|
||||
if (prev && !ws)
|
||||
@@ -325,11 +346,11 @@ count_lw_sse2(const unsigned char *s, size_t n, int *prev_ws, int need_words)
|
||||
|
||||
/* Reference path: the two scalar SWAR counters, kept as the fallback. */
|
||||
static lw_t count_lw_scalar(const unsigned char *s, size_t n, int *prev_ws,
|
||||
int need_words)
|
||||
int need_lines, int need_words)
|
||||
{
|
||||
lw_t r;
|
||||
|
||||
r.lines = count_newlines(s, n);
|
||||
r.lines = need_lines ? count_newlines(s, n) : 0;
|
||||
r.words = need_words ? count_words(s, n, prev_ws) : 0;
|
||||
return r;
|
||||
}
|
||||
@@ -513,7 +534,8 @@ static void count_stream(FILE *fp, counts_t *c)
|
||||
if (flags & (F_LINES | F_WORDS))
|
||||
{
|
||||
lw_t r =
|
||||
count_lw(buf, nread, &prev_ws, (flags & F_WORDS) != 0);
|
||||
count_lw(buf, nread, &prev_ws, (flags & F_LINES) != 0,
|
||||
(flags & F_WORDS) != 0);
|
||||
if (flags & F_LINES)
|
||||
c->lines += r.lines;
|
||||
if (flags & F_WORDS)
|
||||
@@ -560,7 +582,8 @@ static void count_stream(FILE *fp, counts_t *c)
|
||||
c->bytes += (long long)nread;
|
||||
if (flags & (F_LINES | F_WORDS))
|
||||
{
|
||||
lw_t r = count_lw(buf, nread, &prev_ws, (flags & F_WORDS) != 0);
|
||||
lw_t r = count_lw(buf, nread, &prev_ws, (flags & F_LINES) != 0,
|
||||
(flags & F_WORDS) != 0);
|
||||
if (flags & F_LINES)
|
||||
c->lines += r.lines;
|
||||
if (flags & F_WORDS)
|
||||
@@ -577,6 +600,7 @@ typedef struct
|
||||
const unsigned char *s;
|
||||
size_t n;
|
||||
int prev_ws;
|
||||
int need_lines;
|
||||
int need_words;
|
||||
lw_t r;
|
||||
} mjob_t;
|
||||
@@ -585,7 +609,7 @@ static void *map_worker(void *arg)
|
||||
{
|
||||
mjob_t *j = arg;
|
||||
|
||||
j->r = count_lw(j->s, j->n, &j->prev_ws, j->need_words);
|
||||
j->r = count_lw(j->s, j->n, &j->prev_ws, j->need_lines, j->need_words);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -596,14 +620,21 @@ static void *map_worker(void *arg)
|
||||
* which makes the split exact. The kernels are pure, so no locks.
|
||||
*/
|
||||
static void count_sliced(const unsigned char *p, size_t n, int nt,
|
||||
int need_words, long long *lines, long long *words)
|
||||
int need_lines, int need_words, long long *lines,
|
||||
long long *words)
|
||||
{
|
||||
mjob_t jobs[8];
|
||||
pthread_t th[8];
|
||||
enum
|
||||
{
|
||||
MAX_THREADS = 16
|
||||
};
|
||||
mjob_t jobs[MAX_THREADS];
|
||||
pthread_t th[MAX_THREADS];
|
||||
long long tl = 0, tw = 0;
|
||||
size_t per = (n + (size_t)nt - 1) / (size_t)nt;
|
||||
int i;
|
||||
|
||||
if (nt > MAX_THREADS)
|
||||
nt = MAX_THREADS;
|
||||
per = (per + 63) & ~(size_t)63;
|
||||
if (per == 0)
|
||||
per = 64;
|
||||
@@ -615,11 +646,18 @@ static void count_sliced(const unsigned char *p, size_t n, int nt,
|
||||
jobs[i].s = p + start;
|
||||
jobs[i].n = (start + per <= n) ? per : (start < n ? n - start : 0);
|
||||
jobs[i].prev_ws = (i == 0 || jobs[i].n == 0) ? 1 : ws_tab[p[start - 1]];
|
||||
jobs[i].need_lines = need_lines;
|
||||
jobs[i].need_words = need_words;
|
||||
th[i] = 0;
|
||||
if (jobs[i].n > 0)
|
||||
pthread_create(&th[i], NULL, map_worker, &jobs[i]);
|
||||
else
|
||||
th[i] = 0;
|
||||
{
|
||||
if (pthread_create(&th[i], NULL, map_worker, &jobs[i]) != 0)
|
||||
{ /* out of threads: count this slice inline rather than lose it */
|
||||
map_worker(&jobs[i]);
|
||||
tl += jobs[i].r.lines;
|
||||
tw += jobs[i].r.words;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < nt; i++)
|
||||
@@ -640,8 +678,10 @@ static int pick_threads(size_t n)
|
||||
long ncpu = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
int nt;
|
||||
|
||||
if (n >= (size_t)32 << 20)
|
||||
nt = 8;
|
||||
if (n >= (size_t)256 << 20)
|
||||
nt = 16;
|
||||
else if (n >= (size_t)32 << 20)
|
||||
nt = 12;
|
||||
else if (n >= (size_t)8 << 20)
|
||||
nt = 4;
|
||||
else
|
||||
@@ -653,6 +693,7 @@ static int pick_threads(size_t n)
|
||||
|
||||
static void count_mapped(const unsigned char *p, size_t n, counts_t *c)
|
||||
{
|
||||
int need_lines = (flags & F_LINES) != 0;
|
||||
int need_words = (flags & F_WORDS) != 0;
|
||||
int nt = pick_threads(n);
|
||||
long long lines = 0, words = 0;
|
||||
@@ -662,14 +703,14 @@ static void count_mapped(const unsigned char *p, size_t n, counts_t *c)
|
||||
if (nt <= 1)
|
||||
{
|
||||
int prev_ws = 1;
|
||||
lw_t r = count_lw(p, n, &prev_ws, need_words);
|
||||
lw_t r = count_lw(p, n, &prev_ws, need_lines, need_words);
|
||||
|
||||
lines = r.lines;
|
||||
words = r.words;
|
||||
}
|
||||
else
|
||||
{
|
||||
count_sliced(p, n, nt, need_words, &lines, &words);
|
||||
count_sliced(p, n, nt, need_lines, need_words, &lines, &words);
|
||||
}
|
||||
|
||||
if (flags & F_LINES)
|
||||
@@ -915,7 +956,7 @@ static long long ref_words(const unsigned char *s, size_t n, int *prev_ws)
|
||||
* the algorithm; this proves the width.
|
||||
*/
|
||||
static lw_t count_lw_avx512_mirror(const unsigned char *s, size_t n,
|
||||
int *prev_ws, int need_words)
|
||||
int *prev_ws, int need_lines, int need_words)
|
||||
{
|
||||
long long lines = 0, words = 0;
|
||||
size_t i = 0;
|
||||
@@ -931,7 +972,8 @@ static lw_t count_lw_avx512_mirror(const unsigned char *s, size_t n,
|
||||
if (ws_tab[s[i + j]])
|
||||
ws |= (uint64_t)1 << j;
|
||||
}
|
||||
lines += (long long)__builtin_popcountll(nl_mask);
|
||||
if (need_lines)
|
||||
lines += (long long)__builtin_popcountll(nl_mask);
|
||||
if (need_words)
|
||||
{
|
||||
words += (long long)__builtin_popcountll(~ws & ((ws << 1) | prev));
|
||||
@@ -942,7 +984,8 @@ static lw_t count_lw_avx512_mirror(const unsigned char *s, size_t n,
|
||||
for (; i < n; i++)
|
||||
{
|
||||
int ws = ws_tab[s[i]];
|
||||
lines += s[i] == '\n';
|
||||
if (need_lines)
|
||||
lines += s[i] == '\n';
|
||||
if (need_words)
|
||||
{
|
||||
if (prev && !ws)
|
||||
@@ -968,29 +1011,32 @@ static int check_kernel(const char *name, count_lw_fn fn)
|
||||
buf[k] = (unsigned char)rng32();
|
||||
for (int pw = 0; pw <= 1; pw++)
|
||||
{
|
||||
for (int nw = 0; nw <= 1; nw++)
|
||||
for (int nl = 0; nl <= 1; nl++)
|
||||
{
|
||||
int a = pw, b = pw;
|
||||
lw_t got = fn(buf, n, &a, nw);
|
||||
long long want_l = ref_lines(buf, n);
|
||||
long long want_w = nw ? ref_words(buf, n, &b) : 0;
|
||||
|
||||
if (got.lines != want_l || got.words != want_w ||
|
||||
a != (nw ? b : pw))
|
||||
for (int nw = 0; nw <= 1; nw++)
|
||||
{
|
||||
printf("%s: n=%zu pw=%d nw=%d lines %lld/%lld "
|
||||
"words %lld/%lld state %d/%d\n",
|
||||
name, n, pw, nw, got.lines, want_l, got.words,
|
||||
want_w, a, b);
|
||||
if (n <= 64)
|
||||
int a = pw, b = pw;
|
||||
lw_t got = fn(buf, n, &a, nl, nw);
|
||||
long long want_l = nl ? ref_lines(buf, n) : 0;
|
||||
long long want_w = nw ? ref_words(buf, n, &b) : 0;
|
||||
|
||||
if (got.lines != want_l || got.words != want_w ||
|
||||
a != (nw ? b : pw))
|
||||
{
|
||||
for (k = 0; k < n; k++)
|
||||
printf("%02x", buf[k]);
|
||||
printf("\n");
|
||||
printf("%s: n=%zu pw=%d nl=%d nw=%d lines %lld/%lld "
|
||||
"words %lld/%lld state %d/%d\n",
|
||||
name, n, pw, nl, nw, got.lines, want_l,
|
||||
got.words, want_w, a, b);
|
||||
if (n <= 64)
|
||||
{
|
||||
for (k = 0; k < n; k++)
|
||||
printf("%02x", buf[k]);
|
||||
printf("\n");
|
||||
}
|
||||
fails++;
|
||||
if (fails > 5)
|
||||
return fails;
|
||||
}
|
||||
fails++;
|
||||
if (fails > 5)
|
||||
return fails;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1004,7 +1050,7 @@ static int check_kernel(const char *name, count_lw_fn fn)
|
||||
for (int pw = 0; pw <= 1; pw++)
|
||||
{
|
||||
int a = pw, b = pw;
|
||||
lw_t got = fn(buf, n, &a, 1);
|
||||
lw_t got = fn(buf, n, &a, 1, 1);
|
||||
|
||||
if (got.lines != ref_lines(buf, n) ||
|
||||
got.words != ref_words(buf, n, &b) || a != b)
|
||||
@@ -1045,7 +1091,7 @@ static int check_sliced(void)
|
||||
int pw = 1;
|
||||
long long want_w = count_words(buf, n, &pw);
|
||||
|
||||
count_sliced(buf, n, nt, 1, &tl, &tw);
|
||||
count_sliced(buf, n, nt, 1, 1, &tl, &tw);
|
||||
if (tl != want_l || tw != want_w)
|
||||
{
|
||||
printf("sliced: n=%zu nt=%d lines %lld/%lld "
|
||||
@@ -1070,7 +1116,7 @@ static int check_sliced(void)
|
||||
int pw = 1;
|
||||
long long want_w = count_words(buf, n, &pw);
|
||||
|
||||
count_sliced(buf, n, nt, 1, &tl, &tw);
|
||||
count_sliced(buf, n, nt, 1, 1, &tl, &tw);
|
||||
if (tl != want_l || tw != want_w)
|
||||
{
|
||||
printf("sliced-ws: n=%zu nt=%d lines %lld/%lld "
|
||||
|
||||
Reference in New Issue
Block a user