-
-
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
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
a339653
Add rudimentary splicing for cat on Linux
bf6386e
Do proper error handling
9e7b8eb
Compile write_fast_with_splice() only on Linux and Android
6db67e8
Address some of Arcterus's concerns
f159546
Simplify code a whole bunch
bec6615
Do not panic if io::copy fails
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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,20 +289,41 @@ 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; | ||
| } | ||
| _ => { | ||
| // 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. | ||
| match io::copy(&mut handle.reader, &mut writer_handle) { | ||
| Err(error) => { | ||
| eprintln!("error when copying file: {}", error); | ||
| error_count += 1; | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
| Err(error) => { | ||
| writeln!(&mut stderr(), "{}", error)?; | ||
| eprintln!("error when opening file: {}", error); | ||
| error_count += 1; | ||
| } | ||
| } | ||
|
|
@@ -301,6 +335,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.
Show resolved
Hide 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.
Show resolved
Hide 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 +485,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 +518,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() { | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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?