diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc0e773..c7ebdc55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Improve performance when color output disabled, see #2397 and #2857 (@eth-p) - Relax syntax mapping rule restrictions to allow brace expansion #2865 (@cyqsimon) - Apply clippy fixes #2864 (@cyqsimon) +- Faster startup by offloading glob matcher building to a worker thread #2868 (@cyqsimon) ## Syntaxes diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index c382975e..6fc85321 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -122,6 +122,10 @@ impl App { }; let mut syntax_mapping = SyntaxMapping::new(); + // start building glob matchers for builtin mappings immediately + // this is an appropriate approach because it's statistically likely that + // all the custom mappings need to be checked + syntax_mapping.start_offload_build_all(); if let Some(values) = self.matches.get_many::("ignored-suffix") { for suffix in values { diff --git a/src/syntax_mapping.rs b/src/syntax_mapping.rs index 0dac0c02..a149f9bb 100644 --- a/src/syntax_mapping.rs +++ b/src/syntax_mapping.rs @@ -1,6 +1,14 @@ -use std::path::Path; +use std::{ + path::Path, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, +}; use globset::{Candidate, GlobBuilder, GlobMatcher}; +use once_cell::sync::Lazy; use crate::error::Result; use builtin::BUILTIN_MAPPINGS; @@ -44,7 +52,20 @@ pub struct SyntaxMapping<'a> { /// /// Rules in front have precedence. custom_mappings: Vec<(GlobMatcher, MappingTarget<'a>)>, + pub(crate) ignored_suffixes: IgnoredSuffixes<'a>, + + /// A flag to halt glob matcher building, which is offloaded to another thread. + /// + /// We have this so that we can signal the thread to halt early when appropriate. + halt_glob_build: Arc, +} + +impl<'a> Drop for SyntaxMapping<'a> { + fn drop(&mut self) { + // signal the offload thread to halt early + self.halt_glob_build.store(true, Ordering::Relaxed); + } } impl<'a> SyntaxMapping<'a> { @@ -52,6 +73,29 @@ impl<'a> SyntaxMapping<'a> { Default::default() } + /// Start a thread to build the glob matchers for all builtin mappings. + /// + /// The use of this function while not necessary, is useful to speed up startup + /// times by starting this work early in parallel. + /// + /// The thread halts if/when `halt_glob_build` is set to true. + pub fn start_offload_build_all(&self) { + let halt = Arc::clone(&self.halt_glob_build); + thread::spawn(move || { + for (matcher, _) in BUILTIN_MAPPINGS.iter() { + if halt.load(Ordering::Relaxed) { + break; + } + Lazy::force(matcher); + } + }); + // Note that this thread is not joined upon completion because there's + // no shared resources that need synchronization to be safely dropped. + // If we later add code into this thread that requires interesting + // resources (e.g. IO), it would be a good idea to store the handle + // and join it on drop. + } + pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> { let matcher = make_glob_matcher(from)?; self.custom_mappings.push((matcher, to));