perf: defer locale, quote, and table setup until a count needs it

This commit is contained in:
2026-09-09 05:23:54 -04:00
parent 7a8b763416
commit 7b3a535173
+102 -9
View File
@@ -78,6 +78,34 @@ static int posixly_correct = 0;
static enum total_mode total_mode = TOTAL_AUTO; static enum total_mode total_mode = TOTAL_AUTO;
static const char *files_from = NULL; /* --files0-from=F, or NULL */ static const char *files_from = NULL; /* --files0-from=F, or NULL */
/*
* LC_CTYPE handling. A freshly exec'd process already runs in the C
* locale, so when the environment selects C/POSIX (or selects nothing)
* the setlocale() call is skipped: glibc would only re-derive the same
* state, at a cost measurable on the 1-line benchmark inputs. When the
* locale really is multibyte we adopt it exactly like GNU does.
*/
static int in_c_locale; /* LC_CTYPE resolves to C/POSIX, or is unset */
static int locale_done; /* setup_locale() has run */
static void setup_locale(void)
{
const char *v;
if (locale_done)
return;
locale_done = 1;
v = getenv("LC_ALL");
if (v == NULL || v[0] == '\0')
v = getenv("LC_CTYPE");
if (v == NULL || v[0] == '\0')
v = getenv("LANG");
in_c_locale = v == NULL || v[0] == '\0' || strcmp(v, "C") == 0 ||
strcmp(v, "POSIX") == 0;
if (!in_c_locale)
setlocale(LC_CTYPE, "");
}
/* gnulib quote() style: locale quotation marks in a UTF-8 locale, /* gnulib quote() style: locale quotation marks in a UTF-8 locale,
* ASCII apostrophes elsewhere. Only argmatch diagnostics use it. */ * ASCII apostrophes elsewhere. Only argmatch diagnostics use it. */
static int curly_quotes; static int curly_quotes;
@@ -141,6 +169,30 @@ static void init_isprint_tab(void)
isprint_tab[i] = isprint((unsigned char)i) ? 1 : 0; isprint_tab[i] = isprint((unsigned char)i) ? 1 : 0;
} }
/* The C/POSIX byte classes are fixed: isspace() is the six ASCII
* whitespace bytes and isprint() the range 0x20..0x7e. Precomputing
* them skips 512 ctype() calls per exec in the common C-locale run. */
static void init_ws_tab_c(void)
{
int i;
memset(ws_tab, 0, sizeof ws_tab);
for (i = '\t'; i <= '\r'; i++)
ws_tab[i] = 1;
ws_tab[' '] = 1;
ws_tab[0xa0] = (unsigned char)!posixly_correct;
nbsp_ws = ws_tab[0xa0];
}
static void init_isprint_tab_c(void)
{
int i;
memset(isprint_tab, 0, sizeof isprint_tab);
for (i = 0x20; i <= 0x7e; i++)
isprint_tab[i] = 1;
}
static void try_help(void) static void try_help(void)
{ {
fprintf(stderr, "Try 'fastwc --help' for more information.\n"); fprintf(stderr, "Try 'fastwc --help' for more information.\n");
@@ -187,9 +239,19 @@ static void qfile(FILE *out, const char *s)
fputs(s, out); fputs(s, out);
} }
/* quote(): always-quoting locale style used by argmatch diagnostics. */ /* quote(): always-quoting locale style used by argmatch diagnostics.
* The quote style is only needed on the --total error path, so the
* locale work stays out of every other invocation. */
static void qarg(FILE *out, const char *s) static void qarg(FILE *out, const char *s)
{ {
static int qarg_ready;
if (!qarg_ready)
{
qarg_ready = 1;
setup_locale();
init_quote_style();
}
fputs(curly_quotes ? "\xe2\x80\x98" : "'", out); fputs(curly_quotes ? "\xe2\x80\x98" : "'", out);
fputs(s, out); fputs(s, out);
fputs(curly_quotes ? "\xe2\x80\x99" : "'", out); fputs(curly_quotes ? "\xe2\x80\x99" : "'", out);
@@ -540,6 +602,34 @@ static count_lw_fn pick_kernel(void)
} }
#endif #endif
/* One-time state the counting paths need, run after the options are
* known so --help/--version/usage errors pay none of it. The locale is
* only locale-sensitive for -m/-w/-L, the per-byte tables only for word
* and -L counting, and the SIMD kernel only for byte-path counts. */
static void init_count_state(void)
{
if (flags & (F_CHARS | F_WORDS | F_MAXLEN))
{
setup_locale();
if (flags & F_WORDS)
{
if (in_c_locale)
init_ws_tab_c();
else
init_ws_tab();
}
if (flags & F_MAXLEN)
{
if (in_c_locale)
init_isprint_tab_c();
else
init_isprint_tab();
}
}
if (flags & (F_LINES | F_WORDS))
count_lw = pick_kernel();
}
/* /*
* The Unicode white space set as GNU wc 9.11 sees it: glibc's iswspace() * The Unicode white space set as GNU wc 9.11 sees it: glibc's iswspace()
* (which omits the no-break spaces U+00A0, U+2007, U+202F) plus those * (which omits the no-break spaces U+00A0, U+2007, U+202F) plus those
@@ -1413,16 +1503,10 @@ int main(int argc, char **argv)
long n_args = 0; long n_args = 0;
total_t totals = {0, 0, 0, 0, 0}; total_t totals = {0, 0, 0, 0, 0};
setlocale(LC_CTYPE, "");
posixly_correct = getenv("POSIXLY_CORRECT") != NULL; posixly_correct = getenv("POSIXLY_CORRECT") != NULL;
init_quote_style();
init_ws_tab();
init_isprint_tab();
count_lw = pick_kernel();
/* Line-buffer stdout like GNU, so parallel processes do not
* interleave their rows. */
setvbuf(stdout, NULL, _IOLBF, 0);
/* Option parsing needs no locale, so --help/--version and usage
* errors return before any of the one-time startup state exists. */
while ((optc = getopt_long(argc, argv, "clLmw", longopts, NULL)) != -1) while ((optc = getopt_long(argc, argv, "clLmw", longopts, NULL)) != -1)
switch (optc) switch (optc)
{ {
@@ -1469,6 +1553,11 @@ int main(int argc, char **argv)
if (flags == 0) if (flags == 0)
flags = F_LINES | F_WORDS | F_BYTES; /* wc default: -l -w -c */ flags = F_LINES | F_WORDS | F_BYTES; /* wc default: -l -w -c */
/* Line-buffer stdout like GNU, so parallel processes do not
* interleave their rows. */
setvbuf(stdout, NULL, _IOLBF, 0);
init_count_state();
if (files_from) if (files_from)
{ {
FILE *stream; FILE *stream;
@@ -1908,7 +1997,11 @@ int main(void)
/* The CLI build's helpers are compiled out of reach here. */ /* The CLI build's helpers are compiled out of reach here. */
(void)usage; (void)usage;
(void)try_help; (void)try_help;
(void)setup_locale;
(void)init_isprint_tab; (void)init_isprint_tab;
(void)init_isprint_tab_c;
(void)init_ws_tab_c;
(void)init_count_state;
(void)init_quote_style; (void)init_quote_style;
(void)qaf; (void)qaf;
(void)qfile; (void)qfile;