1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

wc: specialize scanning loop on settings. (#3708)

* wc: specialize scanning loop on settings.

The primary computational loop in wc (iterating over all the
characters and computing word lengths, etc) is configured by a
number of boolean options that control the text-scanning behavior.
If we monomorphize the code loop for each possible combination of
scanning configurations, the rustc is able to generate better code
for each instantiation, at the least by removing the conditional
checks on each iteration, and possibly by allowing things like
vectorization.

On my computer (aarch64/macos), I am seeing at least a 5% performance
improvement in release builds on all wc flag configurations
(other than those that were already specialized) against
odyssey1024.txt, with wc -l showing the greatest improvement at 15%.

* Reduce the size of the wc dispatch table by half.

By extracting the handling of hand-written fast-paths to the
same dispatch as the automatic specializations, we can avoid
needing to pass `show_bytes` as a const generic to
`word_count_from_reader_specialized`. Eliminating this parameter
halves the number of arms in the dispatch.
This commit is contained in:
Owen Anderson 2022-07-18 03:16:52 -07:00 committed by GitHub
parent 189a8af450
commit 735db78b3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 180 additions and 26 deletions

View file

@ -306,29 +306,84 @@ fn word_count_from_reader<T: WordCountable>(
mut reader: T, mut reader: T,
settings: &Settings, settings: &Settings,
) -> (WordCount, Option<io::Error>) { ) -> (WordCount, Option<io::Error>) {
let only_count_bytes = settings.show_bytes match (
&& (!(settings.show_chars settings.show_bytes,
|| settings.show_lines settings.show_chars,
|| settings.show_max_line_length settings.show_lines,
|| settings.show_words)); settings.show_max_line_length,
if only_count_bytes { settings.show_words,
) {
// Specialize scanning loop to improve the performance.
(false, false, false, false, false) => unreachable!(),
(true, false, false, false, false) => {
// Fast path when only show_bytes is true.
let (bytes, error) = count_bytes_fast(&mut reader); let (bytes, error) = count_bytes_fast(&mut reader);
return ( (
WordCount { WordCount {
bytes, bytes,
..WordCount::default() ..WordCount::default()
}, },
error, error,
); )
} }
(false, false, true, false, false) | (true, false, true, false, false) => {
// we do not need to decode the byte stream if we're only counting bytes/newlines // Fast path when only (show_bytes || show_lines) is true.
let decode_chars = settings.show_chars || settings.show_words || settings.show_max_line_length; count_bytes_and_lines_fast(&mut reader)
}
if !decode_chars { (_, false, false, false, true) => {
return count_bytes_and_lines_fast(&mut reader); word_count_from_reader_specialized::<_, false, false, false, true>(reader)
}
(_, false, false, true, false) => {
word_count_from_reader_specialized::<_, false, false, true, false>(reader)
}
(_, false, false, true, true) => {
word_count_from_reader_specialized::<_, false, false, true, true>(reader)
}
(_, false, true, false, true) => {
word_count_from_reader_specialized::<_, false, true, false, true>(reader)
}
(_, false, true, true, false) => {
word_count_from_reader_specialized::<_, false, true, true, false>(reader)
}
(_, false, true, true, true) => {
word_count_from_reader_specialized::<_, false, true, true, true>(reader)
}
(_, true, false, false, false) => {
word_count_from_reader_specialized::<_, true, false, false, false>(reader)
}
(_, true, false, false, true) => {
word_count_from_reader_specialized::<_, true, false, false, true>(reader)
}
(_, true, false, true, false) => {
word_count_from_reader_specialized::<_, true, false, true, false>(reader)
}
(_, true, false, true, true) => {
word_count_from_reader_specialized::<_, true, false, true, true>(reader)
}
(_, true, true, false, false) => {
word_count_from_reader_specialized::<_, true, true, false, false>(reader)
}
(_, true, true, false, true) => {
word_count_from_reader_specialized::<_, true, true, false, true>(reader)
}
(_, true, true, true, false) => {
word_count_from_reader_specialized::<_, true, true, true, false>(reader)
}
(_, true, true, true, true) => {
word_count_from_reader_specialized::<_, true, true, true, true>(reader)
}
}
} }
fn word_count_from_reader_specialized<
T: WordCountable,
const SHOW_CHARS: bool,
const SHOW_LINES: bool,
const SHOW_MAX_LINE_LENGTH: bool,
const SHOW_WORDS: bool,
>(
reader: T,
) -> (WordCount, Option<io::Error>) {
let mut total = WordCount::default(); let mut total = WordCount::default();
let mut reader = BufReadDecoder::new(reader.buffered()); let mut reader = BufReadDecoder::new(reader.buffered());
let mut in_word = false; let mut in_word = false;
@ -338,7 +393,7 @@ fn word_count_from_reader<T: WordCountable>(
match chunk { match chunk {
Ok(text) => { Ok(text) => {
for ch in text.chars() { for ch in text.chars() {
if settings.show_words { if SHOW_WORDS {
if ch.is_whitespace() { if ch.is_whitespace() {
in_word = false; in_word = false;
} else if ch.is_ascii_control() { } else if ch.is_ascii_control() {
@ -348,7 +403,7 @@ fn word_count_from_reader<T: WordCountable>(
total.words += 1; total.words += 1;
} }
} }
if settings.show_max_line_length { if SHOW_MAX_LINE_LENGTH {
match ch { match ch {
'\n' | '\r' | '\x0c' => { '\n' | '\r' | '\x0c' => {
total.max_line_length = max(current_len, total.max_line_length); total.max_line_length = max(current_len, total.max_line_length);
@ -363,10 +418,10 @@ fn word_count_from_reader<T: WordCountable>(
} }
} }
} }
if settings.show_lines && ch == '\n' { if SHOW_LINES && ch == '\n' {
total.lines += 1; total.lines += 1;
} }
if settings.show_chars { if SHOW_CHARS {
total.chars += 1; total.chars += 1;
} }
} }

View file

@ -60,7 +60,106 @@ fn test_utf8() {
} }
#[test] #[test]
fn test_utf8_extra() { fn test_utf8_words() {
new_ucmd!()
.arg("-w")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is("87\n");
}
#[test]
fn test_utf8_line_length_words() {
new_ucmd!()
.arg("-Lw")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 87 48\n");
}
#[test]
fn test_utf8_line_length_chars() {
new_ucmd!()
.arg("-Lm")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 442 48\n");
}
#[test]
fn test_utf8_line_length_chars_words() {
new_ucmd!()
.arg("-Lmw")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 87 442 48\n");
}
#[test]
fn test_utf8_chars() {
new_ucmd!()
.arg("-m")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is("442\n");
}
#[test]
fn test_utf8_chars_words() {
new_ucmd!()
.arg("-mw")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 87 442\n");
}
#[test]
fn test_utf8_line_length_lines() {
new_ucmd!()
.arg("-Ll")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 25 48\n");
}
#[test]
fn test_utf8_line_length_lines_words() {
new_ucmd!()
.arg("-Llw")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 25 87 48\n");
}
#[test]
fn test_utf8_lines_chars() {
new_ucmd!()
.arg("-ml")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 25 442\n");
}
#[test]
fn test_utf8_lines_words_chars() {
new_ucmd!()
.arg("-mlw")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 25 87 442\n");
}
#[test]
fn test_utf8_line_length_lines_chars() {
new_ucmd!()
.arg("-Llm")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 25 442 48\n");
}
#[test]
fn test_utf8_all() {
new_ucmd!() new_ucmd!()
.arg("-lwmcL") .arg("-lwmcL")
.pipe_in_fixture("UTF_8_weirdchars.txt") .pipe_in_fixture("UTF_8_weirdchars.txt")