Skip to content

cut: validate UTF-8 once per line and batch writes in -c and -b - #13744

Draft
sylvestre wants to merge 1 commit into
uutils:mainfrom
sylvestre:cut-perf
Draft

cut: validate UTF-8 once per line and batch writes in -c and -b#13744
sylvestre wants to merge 1 commit into
uutils:mainfrom
sylvestre:cut-perf

Conversation

@sylvestre

Copy link
Copy Markdown
Contributor

The -c walk decoded one character at a time even on valid UTF-8, and every line cost at least two write_all calls through a dyn Write.

On 40k lines of ~100 characters, a third of them multi-byte, cutting -c 20-70 drops from 28.5 ms to 19.6 ms (GNU cut: 30.1 ms); short mixed lines gain about 10%. Output is byte-identical to the previous implementation on valid, invalid, and truncated UTF-8, including lines made only of stray continuation bytes.

@codspeed-hq

codspeed-hq Bot commented Aug 4, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 10.02%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 7 regressed benchmarks
✅ 338 untouched benchmarks
⏩ 46 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory cut_characters 20.4 KB 32.4 KB -37.03%
Memory cut_bytes 20.5 KB 32.5 KB -36.87%
Simulation df_with_path 571.4 µs 699.3 µs -18.28%
Memory cut_characters_long_lines 69 KB 81 KB -14.82%
Simulation cut_bytes 18.7 ms 19.5 ms -4.24%
Simulation cksum_multiple_files 60.6 ms 63 ms -3.8%
Simulation du_max_depth_balanced_tree[(6, 4, 10)] 59.8 ms 62.1 ms -3.62%
Simulation cut_characters_long_lines 34.6 ms 24.1 ms +43.3%
Simulation cut_characters 23.3 ms 21.2 ms +9.81%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing sylvestre:cut-perf (1b1cfbc) with main (21d4e96)

Open in CodSpeed

Footnotes

  1. 46 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

The -c walk decoded one character at a time even on valid UTF-8, and
every line cost at least two write_all calls through a dyn Write.

Validate the span the walk can touch (four bytes per character wanted,
capped at the line) once per line with simdutf8. Within that prefix
every non-continuation byte starts a character, so the walk consumes
whole spans at once -- n characters occupy at least n bytes -- counting
the character starts inside each span with bytecount, which the tree
already uses in wc and tr. Everything else -- invalid input, short
walks, -b -n, non-UTF-8 locales -- keeps the exact per-character path,
whose ASCII-run scan now comes from bstr's find_non_ascii_byte instead
of a hand-rolled loop.

Selected parts of lines are collected in a buffer flushed at 8 KiB --
enough to batch hundreds of lines per write while staying resident in
L1 -- unless stdout is a terminal, where flushing stays per line to
keep output interactive.

On 40k lines of ~100 characters, a third of them multi-byte, cutting
-c 20-70 drops from 28.5 ms to 19.6 ms (GNU cut: 30.1 ms); short mixed
lines gain about 10%. Output is byte-identical to the previous
implementation on valid, invalid, and truncated UTF-8, including lines
made only of stray continuation bytes.
Comment thread src/uu/cut/src/cut.rs
Comment on lines 143 to +148
if let Err(e) = result {
return Err(USimpleError::new(1, e.to_string()));
}
if let Err(e) = out.write_all(&buf) {
return Err(USimpleError::new(1, e.to_string()));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit odd, and I would use map_err in both cases. In the first case it also allows you to get rid of the result variable.

Comment thread src/uu/cut/src/cut.rs
while 0 < cap && cap < line.len() && line[cap] & 0xC0 == 0x80 {
cap -= 1;
}
match simdutf8::basic::from_utf8(&line[..cap]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spell checker complains about simdutf here and in other places.

@sylvestre

Copy link
Copy Markdown
Contributor Author

yeah, sorry, wip given the perf regressions :)
a false good idea !

@sylvestre
sylvestre marked this pull request as draft August 5, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants