Significantly improve performance by using a buffered writer

Before

```
Benchmark 1: seq 10000000 | bat
  Time (mean ± σ):      6.235 s ±  0.052 s    [User: 3.664 s, System: 2.714 s]
  Range (min … max):    6.172 s …  6.355 s    10 runs
```

After

```
Benchmark 1: seq 10000000 | ./target/release/bat
  Time (mean ± σ):     215.9 ms ±   5.1 ms    [User: 275.4 ms, System: 38.8 ms]
  Range (min … max):   210.3 ms … 224.9 ms    10 runs
```

Using `less` for comparison

```
Benchmark 1: seq 10000000 | less
  Time (mean ± σ):     637.3 ms ±  43.3 ms    [User: 642.1 ms, System: 95.6 ms]
  Range (min … max):   584.5 ms … 700.1 ms    10 runs
```

And raw

```
Benchmark 1: seq 10000000
  Time (mean ± σ):      63.1 ms ±   1.3 ms    [User: 57.1 ms, System: 5.9 ms]
  Range (min … max):    62.1 ms …  66.0 ms    10 runs
```

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
This commit is contained in:
Mohammad AlSaleh 2024-10-09 06:51:52 +03:00
parent eca6b8a376
commit 69a4bedb55
3 changed files with 57 additions and 38 deletions

View File

@ -44,6 +44,7 @@
- Use bat's ANSI iterator during tab expansion, see #2998 (@eth-p)
- Support 'statically linked binary' for aarch64 in 'Release' page, see #2992 (@tzq0301)
- Update options in shell completions and the man page of `bat`, see #2995 (@akinomyoga)
- Significantly improve performance by using a buffered writer, see #3101 (@MoSal)
## Syntaxes

View File

@ -1,4 +1,4 @@
use std::io::{self, BufRead, Write};
use std::io::{self, BufRead, BufWriter, Write};
use crate::assets::HighlightingAssets;
use crate::config::{Config, VisibleLines};
@ -88,9 +88,12 @@ impl<'b> Controller<'b> {
clircle::Identifier::stdout()
};
const BUF_W_SZ: usize = 1 << 14;
let mut writer = match output_buffer {
Some(buf) => OutputHandle::FmtWrite(buf),
None => OutputHandle::IoWrite(output_type.handle()?),
None => {
OutputHandle::IoWrite(BufWriter::with_capacity(BUF_W_SZ, output_type.handle()?))
}
};
let mut no_errors: bool = true;
let stderr = io::stderr();
@ -124,10 +127,10 @@ impl<'b> Controller<'b> {
Ok(no_errors)
}
fn print_input<R: BufRead>(
fn print_input<R: BufRead, W: io::Write>(
&self,
input: Input,
writer: &mut OutputHandle,
writer: &mut OutputHandle<W>,
stdin: R,
stdout_identifier: Option<&Identifier>,
is_first: bool,
@ -174,7 +177,7 @@ impl<'b> Controller<'b> {
None
};
let mut printer: Box<dyn Printer> = if self.config.loop_through {
let mut printer: Box<dyn Printer<_>> = if self.config.loop_through {
Box::new(SimplePrinter::new(self.config))
} else {
Box::new(InteractivePrinter::new(
@ -196,10 +199,10 @@ impl<'b> Controller<'b> {
)
}
fn print_file(
fn print_file<W: io::Write>(
&self,
printer: &mut dyn Printer,
writer: &mut OutputHandle,
printer: &mut dyn Printer<W>,
writer: &mut OutputHandle<W>,
input: &mut OpenedInput,
add_header_padding: bool,
#[cfg(feature = "git")] line_changes: &Option<LineChanges>,
@ -234,10 +237,10 @@ impl<'b> Controller<'b> {
Ok(())
}
fn print_file_ranges(
fn print_file_ranges<W: io::Write>(
&self,
printer: &mut dyn Printer,
writer: &mut OutputHandle,
printer: &mut dyn Printer<W>,
writer: &mut OutputHandle<W>,
reader: &mut InputReader,
line_ranges: &LineRanges,
) -> Result<()> {
@ -279,6 +282,7 @@ impl<'b> Controller<'b> {
line_number += 1;
line_buffer.clear();
}
writer.flush()?;
Ok(())
}
}

View File

@ -1,5 +1,5 @@
use std::fmt;
use std::io;
use std::io::{self, BufWriter, Write};
use std::vec::Vec;
use nu_ansi_term::Color::{Fixed, Green, Red, Yellow};
@ -67,35 +67,42 @@ const EMPTY_SYNTECT_STYLE: syntect::highlighting::Style = syntect::highlighting:
font_style: FontStyle::empty(),
};
pub enum OutputHandle<'a> {
IoWrite(&'a mut dyn io::Write),
pub enum OutputHandle<'a, W: io::Write> {
IoWrite(BufWriter<W>),
FmtWrite(&'a mut dyn fmt::Write),
}
impl<'a> OutputHandle<'a> {
impl<'a, W: io::Write> OutputHandle<'a, W> {
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> {
match self {
Self::IoWrite(handle) => handle.write_fmt(args).map_err(Into::into),
Self::FmtWrite(handle) => handle.write_fmt(args).map_err(Into::into),
}
}
pub(crate) fn flush(&mut self) -> Result<()> {
match self {
Self::IoWrite(handle) => handle.flush().map_err(Into::into),
Self::FmtWrite(_handle) => Ok(()),
}
}
}
pub(crate) trait Printer {
pub(crate) trait Printer<W: io::Write> {
fn print_header(
&mut self,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
input: &OpenedInput,
add_header_padding: bool,
) -> Result<()>;
fn print_footer(&mut self, handle: &mut OutputHandle, input: &OpenedInput) -> Result<()>;
fn print_footer(&mut self, handle: &mut OutputHandle<W>, input: &OpenedInput) -> Result<()>;
fn print_snip(&mut self, handle: &mut OutputHandle) -> Result<()>;
fn print_snip(&mut self, handle: &mut OutputHandle<W>) -> Result<()>;
fn print_line(
&mut self,
out_of_range: bool,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
line_number: usize,
line_buffer: &[u8],
) -> Result<()>;
@ -115,28 +122,28 @@ impl<'a> SimplePrinter<'a> {
}
}
impl<'a> Printer for SimplePrinter<'a> {
impl<'a, W: io::Write> Printer<W> for SimplePrinter<'a> {
fn print_header(
&mut self,
_handle: &mut OutputHandle,
_handle: &mut OutputHandle<W>,
_input: &OpenedInput,
_add_header_padding: bool,
) -> Result<()> {
Ok(())
}
fn print_footer(&mut self, _handle: &mut OutputHandle, _input: &OpenedInput) -> Result<()> {
fn print_footer(&mut self, _handle: &mut OutputHandle<W>, _input: &OpenedInput) -> Result<()> {
Ok(())
}
fn print_snip(&mut self, _handle: &mut OutputHandle) -> Result<()> {
fn print_snip(&mut self, _handle: &mut OutputHandle<W>) -> Result<()> {
Ok(())
}
fn print_line(
&mut self,
out_of_range: bool,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
_line_number: usize,
line_buffer: &[u8],
) -> Result<()> {
@ -321,9 +328,9 @@ impl<'a> InteractivePrinter<'a> {
})
}
fn print_horizontal_line_term(
fn print_horizontal_line_term<W: io::Write>(
&mut self,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
style: Style,
) -> Result<()> {
writeln!(
@ -334,7 +341,11 @@ impl<'a> InteractivePrinter<'a> {
Ok(())
}
fn print_horizontal_line(&mut self, handle: &mut OutputHandle, grid_char: char) -> Result<()> {
fn print_horizontal_line<W: io::Write>(
&mut self,
handle: &mut OutputHandle<W>,
grid_char: char,
) -> Result<()> {
if self.panel_width == 0 {
self.print_horizontal_line_term(handle, self.colors.grid)?;
} else {
@ -372,7 +383,10 @@ impl<'a> InteractivePrinter<'a> {
}
}
fn print_header_component_indent(&mut self, handle: &mut OutputHandle) -> Result<()> {
fn print_header_component_indent<W: io::Write>(
&mut self,
handle: &mut OutputHandle<W>,
) -> Result<()> {
if self.config.style_components.grid() {
write!(
handle,
@ -387,18 +401,18 @@ impl<'a> InteractivePrinter<'a> {
}
}
fn print_header_component_with_indent(
fn print_header_component_with_indent<W: io::Write>(
&mut self,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
content: &str,
) -> Result<()> {
self.print_header_component_indent(handle)?;
writeln!(handle, "{content}")
}
fn print_header_multiline_component(
fn print_header_multiline_component<W: io::Write>(
&mut self,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
content: &str,
) -> Result<()> {
let mut content = content;
@ -446,10 +460,10 @@ impl<'a> InteractivePrinter<'a> {
}
}
impl<'a> Printer for InteractivePrinter<'a> {
impl<'a, W: io::Write> Printer<W> for InteractivePrinter<'a> {
fn print_header(
&mut self,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
input: &OpenedInput,
add_header_padding: bool,
) -> Result<()> {
@ -549,7 +563,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
Ok(())
}
fn print_footer(&mut self, handle: &mut OutputHandle, _input: &OpenedInput) -> Result<()> {
fn print_footer(&mut self, handle: &mut OutputHandle<W>, _input: &OpenedInput) -> Result<()> {
if self.config.style_components.grid()
&& (self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable)
{
@ -559,7 +573,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
}
}
fn print_snip(&mut self, handle: &mut OutputHandle) -> Result<()> {
fn print_snip(&mut self, handle: &mut OutputHandle<W>) -> Result<()> {
let panel = self.create_fake_panel(" ...");
let panel_count = panel.chars().count();
@ -586,7 +600,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
fn print_line(
&mut self,
out_of_range: bool,
handle: &mut OutputHandle,
handle: &mut OutputHandle<W>,
line_number: usize,
line_buffer: &[u8],
) -> Result<()> {