printf: Buffer output

This writes the output once per argument instead of once per format or
escaped char.

An egregious case:

```fish
printf (string repeat -n 200 \\x7f)%s\n (string repeat -n 2000 aaa\n)
```

Has been sped up by ~20x by reducing write() calls from 40000 to 200.

Even a simple

```fish
printf %s\n (string repeat -n 2000 aaa\n)
```

should now be ~1.2x faster by issuing 2000 instead of 4000 write
calls (the `\n` was written separately!).
This commit is contained in:
Fabian Boehm 2022-09-21 17:18:19 +02:00
parent 64927677c8
commit c5b5dd7563

View file

@ -84,6 +84,10 @@ struct builtin_printf_state_t {
// Whether we should stop outputting. This gets set in the case of an error, and also with the
// \c escape.
bool early_exit;
// Our output buffer, so we don't write() constantly.
// Our strategy is simple:
// We print once per argument, and we flush the buffer before the error.
wcstring buff;
explicit builtin_printf_state_t(io_streams_t &s)
: streams(s), exit_code(0), early_exit(false) {}
@ -113,6 +117,12 @@ void builtin_printf_state_t::nonfatal_error(const wchar_t *fmt, ...) {
// Don't error twice.
if (early_exit) return;
// If we have output, write it so it appears first.
if (!buff.empty()) {
streams.out.append(buff);
buff.clear();
}
va_list va;
va_start(va, fmt);
wcstring errstr = vformat_string(fmt, va);
@ -129,6 +139,12 @@ void builtin_printf_state_t::fatal_error(const wchar_t *fmt, ...) {
// Don't error twice.
if (early_exit) return;
// If we have output, write it so it appears first.
if (!buff.empty()) {
streams.out.append(buff);
buff.clear();
}
va_list va;
va_start(va, fmt);
wcstring errstr = vformat_string(fmt, va);
@ -143,7 +159,7 @@ void builtin_printf_state_t::append_output(wchar_t c) {
// Don't output if we're done.
if (early_exit) return;
streams.out.push_back(c);
buff.push_back(c);
}
void builtin_printf_state_t::append_format_output(const wchar_t *fmt, ...) {
@ -154,7 +170,7 @@ void builtin_printf_state_t::append_format_output(const wchar_t *fmt, ...) {
va_start(va, fmt);
wcstring tmp = vformat_string(fmt, va);
va_end(va);
streams.out.append(tmp);
buff.append(tmp);
}
void builtin_printf_state_t::verify_numeric(const wchar_t *s, const wchar_t *end, int errcode) {
@ -682,6 +698,10 @@ maybe_t<int> builtin_printf(parser_t &parser, io_streams_t &streams, const wchar
args_used = state.print_formatted(format, argc, argv);
argc -= args_used;
argv += args_used;
if (!state.buff.empty()) {
streams.out.append(state.buff);
state.buff.clear();
}
} while (args_used > 0 && argc > 0 && !state.early_exit);
#if defined(HAVE_USELOCALE) || defined(__GLIBC__)