From 458365f594e405ceca1e6eb2c15ed8ea2e4bb5f8 Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:19:27 +0900 Subject: [PATCH 1/2] cat: improve splice fast-path --- .vscode/cspell.dictionaries/workspace.wordlist.txt | 1 + src/uu/cat/src/splice.rs | 11 ++++++++--- src/uucore/src/lib/features/pipes.rs | 7 ++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 72f2d95f11e..a32ca0770ed 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -374,6 +374,7 @@ weblate algs wasm wasip +SETPIPE # * stty terminal flags brkint diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index a8b01e22605..268f0296c71 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -7,9 +7,8 @@ use super::{CatResult, FdReadable, InputHandle}; use rustix::io::{read, write}; use std::os::{fd::AsFd, unix::io::AsRawFd}; -use uucore::pipes::{pipe, splice, splice_exact}; +use uucore::pipes::{MAX_ROOTLESS_PIPE_SIZE, pipe, splice, splice_exact}; -const SPLICE_SIZE: usize = 1024 * 128; const BUF_SIZE: usize = 1024 * 16; /// This function is called from `write_fast()` on Linux and Android. The @@ -24,10 +23,16 @@ pub(super) fn write_fast_using_splice( handle: &InputHandle, write_fd: &S, ) -> CatResult { + use nix::fcntl::{FcntlArg, fcntl}; let (pipe_rd, pipe_wr) = pipe()?; + // improve performance + let _ = fcntl( + write_fd, + FcntlArg::F_SETPIPE_SZ(MAX_ROOTLESS_PIPE_SIZE as i32), + ); loop { - match splice(&handle.reader, &pipe_wr, SPLICE_SIZE) { + match splice(&handle.reader, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { Ok(n) => { if n == 0 { return Ok(false); diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index 776cf2711aa..6f613558e65 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -4,21 +4,26 @@ // file that was distributed with this source code. //! Thin zero-copy-related wrappers around functions from the `rustix` crate. + #[cfg(any(target_os = "linux", target_os = "android"))] use std::fs::File; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::fd::AsFd; - #[cfg(any(target_os = "linux", target_os = "android"))] use rustix::pipe::SpliceFlags; +pub const MAX_ROOTLESS_PIPE_SIZE: usize = 1024 * 1024; /// A wrapper around [`rustix::pipe::pipe`] that ensures the pipe is cleaned up. /// /// Returns two `File` objects: everything written to the second can be read /// from the first. +/// This is used only for resolving the limitation for splice: one of a input or output should be pipe #[cfg(any(target_os = "linux", target_os = "android"))] pub fn pipe() -> std::io::Result<(File, File)> { let (read, write) = rustix::pipe::pipe()?; + // improve performance for splice + let _ = fcntl(&read, FcntlArg::F_SETPIPE_SZ(MAX_ROOTLESS_PIPE_SIZE as i32)); + Ok((File::from(read), File::from(write))) } From 4e84e912d0a63b102194bf767e5c1ab7040d9976 Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:21:43 +0900 Subject: [PATCH 2/2] cat: avoid pipe() if stdout is pipe --- .../workspace.wordlist.txt | 2 +- src/uu/cat/src/splice.rs | 53 +++++++++++-------- src/uucore/src/lib/features/pipes.rs | 6 +-- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index a32ca0770ed..04a1402158b 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -367,6 +367,7 @@ uutils # * function names getcwd +setpipe # * other getlimits @@ -374,7 +375,6 @@ weblate algs wasm wasip -SETPIPE # * stty terminal flags brkint diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 268f0296c71..14e88dd6e44 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -23,33 +23,40 @@ pub(super) fn write_fast_using_splice( handle: &InputHandle, write_fd: &S, ) -> CatResult { - use nix::fcntl::{FcntlArg, fcntl}; - let (pipe_rd, pipe_wr) = pipe()?; - // improve performance - let _ = fcntl( - write_fd, - FcntlArg::F_SETPIPE_SZ(MAX_ROOTLESS_PIPE_SIZE as i32), - ); - - loop { - match splice(&handle.reader, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { - Ok(n) => { - if n == 0 { - return Ok(false); + const FIRST_PIPE_SIZE: usize = 64 * 1024; + if splice(&handle.reader, &write_fd, FIRST_PIPE_SIZE).is_ok() { + // fcntl improves performance for large file which is large overhead for small files + let _ = rustix::pipe::fcntl_setpipe_size(write_fd, MAX_ROOTLESS_PIPE_SIZE); + loop { + match splice(&handle.reader, &write_fd, MAX_ROOTLESS_PIPE_SIZE) { + Ok(1..) => {} + Ok(0) => return Ok(false), + Err(_) => return Ok(true), + } + } + } else { + // output is not pipe. Needs broker to use splice() which is high cost for small files + let (pipe_rd, pipe_wr) = pipe()?; + loop { + match splice(&handle.reader, &pipe_wr, MAX_ROOTLESS_PIPE_SIZE) { + Ok(n) => { + if n == 0 { + return Ok(false); + } + if splice_exact(&pipe_rd, write_fd, n).is_err() { + // If the first splice manages to copy to the intermediate + // pipe, but the second splice to stdout fails for some reason + // we can recover by copying the data that we have from the + // intermediate pipe to stdout using normal read/write. Then + // we tell the caller to fall back. + copy_exact(&pipe_rd, write_fd, n)?; + return Ok(true); + } } - if splice_exact(&pipe_rd, write_fd, n).is_err() { - // If the first splice manages to copy to the intermediate - // pipe, but the second splice to stdout fails for some reason - // we can recover by copying the data that we have from the - // intermediate pipe to stdout using normal read/write. Then - // we tell the caller to fall back. - copy_exact(&pipe_rd, write_fd, n)?; + Err(_) => { return Ok(true); } } - Err(_) => { - return Ok(true); - } } } } diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index 6f613558e65..2615912c08d 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -5,12 +5,12 @@ //! Thin zero-copy-related wrappers around functions from the `rustix` crate. +#[cfg(any(target_os = "linux", target_os = "android"))] +use rustix::pipe::{SpliceFlags, fcntl_setpipe_size}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::fs::File; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::fd::AsFd; -#[cfg(any(target_os = "linux", target_os = "android"))] -use rustix::pipe::SpliceFlags; pub const MAX_ROOTLESS_PIPE_SIZE: usize = 1024 * 1024; /// A wrapper around [`rustix::pipe::pipe`] that ensures the pipe is cleaned up. @@ -22,7 +22,7 @@ pub const MAX_ROOTLESS_PIPE_SIZE: usize = 1024 * 1024; pub fn pipe() -> std::io::Result<(File, File)> { let (read, write) = rustix::pipe::pipe()?; // improve performance for splice - let _ = fcntl(&read, FcntlArg::F_SETPIPE_SZ(MAX_ROOTLESS_PIPE_SIZE as i32)); + let _ = fcntl_setpipe_size(&read, MAX_ROOTLESS_PIPE_SIZE); Ok((File::from(read), File::from(write))) }