-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Use splice syscall on Linux to greatly improve cat performance
#1289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
a339653
bf6386e
9e7b8eb
6db67e8
f159546
bec6615
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,8 @@ extern crate quick_error; | |
| extern crate unix_socket; | ||
| #[macro_use] | ||
| extern crate uucore; | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| extern crate nix; | ||
|
|
||
| // last synced with: cat (GNU coreutils) 8.13 | ||
| use quick_error::ResultExt; | ||
|
|
@@ -31,6 +33,14 @@ use std::os::unix::fs::FileTypeExt; | |
| #[cfg(unix)] | ||
| use unix_socket::UnixStream; | ||
|
|
||
| /// Linux splice support | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| use nix::fcntl::{splice, SpliceFFlags}; | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| use nix::unistd::pipe; | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| use std::os::unix::io::{AsRawFd, RawFd}; | ||
|
|
||
| static SYNTAX: &str = "[OPTION]... [FILE]..."; | ||
| static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output | ||
| With no FILE, or when FILE is -, read standard input."; | ||
|
|
@@ -100,6 +110,8 @@ struct OutputOptions { | |
|
|
||
| /// Represents an open file handle, stream, or other device | ||
| struct InputHandle { | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| file_descriptor: RawFd, | ||
| reader: Box<Read>, | ||
| is_interactive: bool, | ||
| } | ||
|
|
@@ -168,7 +180,10 @@ pub fn uumain(args: Vec<String>) -> i32 { | |
| files.push("-".to_owned()); | ||
| } | ||
|
|
||
| let can_write_fast = !(show_tabs || show_nonprint || show_ends || squeeze_blank | ||
| let can_write_fast = !(show_tabs | ||
| || show_nonprint | ||
| || show_ends | ||
| || squeeze_blank | ||
| || number_mode != NumberingMode::NumberNone); | ||
|
|
||
| let success = if can_write_fast { | ||
|
|
@@ -190,7 +205,11 @@ pub fn uumain(args: Vec<String>) -> i32 { | |
| write_lines(files, &options).is_ok() | ||
| }; | ||
|
|
||
| if success { 0 } else { 1 } | ||
| if success { | ||
| 0 | ||
| } else { | ||
| 1 | ||
| } | ||
| } | ||
|
|
||
| /// Classifies the `InputType` of file at `path` if possible | ||
|
|
@@ -205,25 +224,13 @@ fn get_input_type(path: &str) -> CatResult<InputType> { | |
|
|
||
| match metadata(path).context(path)?.file_type() { | ||
| #[cfg(unix)] | ||
| ft if ft.is_block_device() => | ||
| { | ||
| Ok(InputType::BlockDevice) | ||
| } | ||
| ft if ft.is_block_device() => Ok(InputType::BlockDevice), | ||
| #[cfg(unix)] | ||
| ft if ft.is_char_device() => | ||
| { | ||
| Ok(InputType::CharacterDevice) | ||
| } | ||
| ft if ft.is_char_device() => Ok(InputType::CharacterDevice), | ||
| #[cfg(unix)] | ||
| ft if ft.is_fifo() => | ||
| { | ||
| Ok(InputType::Fifo) | ||
| } | ||
| ft if ft.is_fifo() => Ok(InputType::Fifo), | ||
| #[cfg(unix)] | ||
| ft if ft.is_socket() => | ||
| { | ||
| Ok(InputType::Socket) | ||
| } | ||
| ft if ft.is_socket() => Ok(InputType::Socket), | ||
| ft if ft.is_dir() => Ok(InputType::Directory), | ||
| ft if ft.is_file() => Ok(InputType::File), | ||
| ft if ft.is_symlink() => Ok(InputType::SymLink), | ||
|
|
@@ -241,6 +248,8 @@ fn open(path: &str) -> CatResult<InputHandle> { | |
| if path == "-" { | ||
| let stdin = stdin(); | ||
| return Ok(InputHandle { | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| file_descriptor: stdin.as_raw_fd(), | ||
| reader: Box::new(stdin) as Box<Read>, | ||
| is_interactive: is_stdin_interactive(), | ||
| }); | ||
|
|
@@ -253,13 +262,17 @@ fn open(path: &str) -> CatResult<InputHandle> { | |
| let socket = UnixStream::connect(path).context(path)?; | ||
| socket.shutdown(Shutdown::Write).context(path)?; | ||
| Ok(InputHandle { | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| file_descriptor: socket.as_raw_fd(), | ||
| reader: Box::new(socket) as Box<Read>, | ||
| is_interactive: false, | ||
| }) | ||
| } | ||
| _ => { | ||
| let file = File::open(path).context(path)?; | ||
| Ok(InputHandle { | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| file_descriptor: file.as_raw_fd(), | ||
| reader: Box::new(file) as Box<Read>, | ||
| is_interactive: false, | ||
| }) | ||
|
|
@@ -276,18 +289,33 @@ fn open(path: &str) -> CatResult<InputHandle> { | |
| /// * `files` - There is no short circuit when encountiner an error | ||
| /// reading a file in this vector | ||
| fn write_fast(files: Vec<String>) -> CatResult<()> { | ||
| let mut writer = stdout(); | ||
| let mut in_buf = [0; 1024 * 64]; | ||
| let mut error_count = 0; | ||
| let writer = stdout(); | ||
| let mut writer_handle = writer.lock(); | ||
|
|
||
| for file in files { | ||
| match open(&file[..]) { | ||
| Ok(mut handle) => while let Ok(n) = handle.reader.read(&mut in_buf) { | ||
| if n == 0 { | ||
| break; | ||
| Ok(mut handle) => { | ||
| // If we're on Linux or Android, try to use the splice() | ||
| // system call for faster writing. | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
| { | ||
| match write_fast_using_splice(&mut handle, writer.as_raw_fd()) { | ||
| Ok(_) => { | ||
| // Writing fast with splice worked! We don't need | ||
| // to fall back on slower writing. | ||
| continue; | ||
| } | ||
| Err(_) => { | ||
| // Ignore any error and fall back to slower | ||
| // writing below. | ||
| } | ||
| } | ||
| } | ||
| writer.write_all(&in_buf[..n]).context(&file[..])?; | ||
| }, | ||
| // If we're not on Linux or Android, or the splice() call failed, | ||
| // fall back on slower writing. | ||
| io::copy(&mut handle.reader, &mut writer_handle).unwrap(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old implementation was actually incorrect in that this should not panic/cause an error. It should write out the error message and then bump the error count like in the |
||
| } | ||
| Err(error) => { | ||
| writeln!(&mut stderr(), "{}", error)?; | ||
| error_count += 1; | ||
|
|
@@ -301,6 +329,37 @@ fn write_fast(files: Vec<String>) -> CatResult<()> { | |
| } | ||
| } | ||
|
|
||
| /// This function is called from `write_fast()` on Linux and Android. The | ||
| /// function `splice()` is used to move data between two file descriptors | ||
| /// without copying between kernel- and userspace. This results in a large | ||
| /// speedup. | ||
| #[cfg(any(target_os = "linux", target_os = "android"))] | ||
|
ArniDagur marked this conversation as resolved.
|
||
| #[inline] | ||
| fn write_fast_using_splice(handle: &mut InputHandle, writer: RawFd) -> Result<(), nix::Error> { | ||
| const BUF_SIZE: usize = 1024 * 16; | ||
|
|
||
| let (pipe_rd, pipe_wr) = pipe()?; | ||
|
|
||
| loop { | ||
| let res = splice( | ||
|
sylvestre marked this conversation as resolved.
|
||
| handle.file_descriptor, | ||
| None, | ||
| pipe_wr, | ||
| None, | ||
| BUF_SIZE, | ||
| SpliceFFlags::empty(), | ||
| )?; | ||
| if res == 0 { | ||
| // We read 0 bytes from the input, | ||
| // which means we're done copying. | ||
| break; | ||
| } | ||
| let _ = splice(pipe_rd, None, writer, None, BUF_SIZE, SpliceFFlags::empty())?; | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// State that persists between output of each file | ||
| struct OutputState { | ||
| /// The current line number | ||
|
|
@@ -420,10 +479,7 @@ fn write_to_end<W: Write>(in_buf: &[u8], writer: &mut W) -> usize { | |
|
|
||
| fn write_tab_to_end<W: Write>(mut in_buf: &[u8], writer: &mut W) -> usize { | ||
| loop { | ||
| match in_buf | ||
| .iter() | ||
| .position(|c| *c == b'\n' || *c == b'\t') | ||
| { | ||
| match in_buf.iter().position(|c| *c == b'\n' || *c == b'\t') { | ||
| Some(p) => { | ||
| writer.write_all(&in_buf[..p]).unwrap(); | ||
| if in_buf[p] == b'\n' { | ||
|
|
@@ -456,7 +512,8 @@ fn write_nonprint_to_end<W: Write>(in_buf: &[u8], writer: &mut W, tab: &[u8]) -> | |
| 128...159 => writer.write_all(&[b'M', b'-', b'^', byte - 64]), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you change this to use byte literals (and maybe fix the other issues mentioned by the person on IRC)? |
||
| 160...254 => writer.write_all(&[b'M', b'-', byte - 128]), | ||
| _ => writer.write_all(&[b'M', b'-', b'^', 63]), | ||
| }.unwrap(); | ||
| } | ||
| .unwrap(); | ||
| count += 1; | ||
| } | ||
| if count != in_buf.len() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mentioned this on the other PR as well, but could you change this to avoid
Box?