Reduce lock contention (round 1)

- Use an async (i.e. unlimited buffer) MPSC channel instead of an
  Arc<Mutex<Vec>> for storing the scored matches in Sorter
- Use Arc<Matcher> instead of Arc<Mutex<Matcher>> for the matcher, as
  it's not mutated and appears to be threadsafe.

This cuts average iteration time (on the benchmarked machine) from
25.98ms to 16.08ms for the ivy_files benchmark.
This commit is contained in:
Xymist 2022-08-26 16:01:22 +01:00
parent ce28b248fa
commit 7fb8be541a
3 changed files with 26 additions and 17 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ compile_commands.json
.luacheckcache .luacheckcache
benchmarks benchmarks
flamegraph* flamegraph*
perf.data*

View file

@ -66,18 +66,19 @@ pub extern "C" fn ivy_files(c_pattern: *const c_char, c_base_dir: *const c_char)
} }
pub fn inner_files(pattern: String, base_dir: String) -> String { pub fn inner_files(pattern: String, base_dir: String) -> String {
// Bail out early if the pattern is empty its never going to find anything let mut output = String::new();
// Bail out early if the pattern is empty; it's never going to find anything
if pattern.is_empty() { if pattern.is_empty() {
return String::new(); return output;
} }
let files = get_files(&base_dir); let files = get_files(&base_dir);
let mut output = String::new();
let sorter_options = sorter::Options::new(pattern); let sorter_options = sorter::Options::new(pattern);
let files = sorter::sort_strings(sorter_options, files); let files = sorter::sort_strings(sorter_options, files);
for file in files.lock().unwrap().iter() { for file in files.iter() {
output.push_str(&file.content); output.push_str(&file.content);
output.push('\n'); output.push('\n');
} }

View file

@ -1,8 +1,8 @@
use super::matcher; use super::matcher;
use super::thread_pool; use super::thread_pool;
use std::sync::mpsc;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex;
pub struct Match { pub struct Match {
pub score: i64, pub score: i64,
@ -23,30 +23,37 @@ impl Options {
} }
} }
pub fn sort_strings(options: Options, strings: Vec<String>) -> Arc<Mutex<Vec<Match>>> { pub fn sort_strings(options: Options, strings: Vec<String>) -> Vec<Match> {
let matches: Arc<Mutex<Vec<Match>>> = Arc::new(Mutex::new(Vec::new())); let mut matches = Vec::new();
let matcher = Arc::new(Mutex::new(matcher::Matcher::new(options.pattern))); let matcher = Arc::new(matcher::Matcher::new(options.pattern));
let pool = thread_pool::ThreadPool::new(std::thread::available_parallelism().unwrap().get()); let pool = thread_pool::ThreadPool::new(std::thread::available_parallelism().unwrap().get());
let (tx, rx) = mpsc::channel::<Match>();
for string in strings { for string in strings {
let thread_matcher = Arc::clone(&matcher); let thread_matcher = Arc::clone(&matcher);
let thread_matches = Arc::clone(&matches); let thread_transmitter = tx.clone();
pool.execute(move || { pool.execute(move || {
let score = thread_matcher.lock().unwrap().score(string.to_string()); let score = thread_matcher.score(string.to_string());
if score > 25 { if score > 25 {
let mut tmp = thread_matches.lock().unwrap(); thread_transmitter
let content = string.clone(); .send(Match {
tmp.push(Match { score, content }); score,
content: string,
})
.expect("Failed to push data to channel");
} }
}) })
} }
drop(pool); drop(pool);
drop(tx);
matches while let Ok(result) = rx.recv() {
.lock() matches.push(result)
.unwrap() }
.sort_by(|a, b| a.score.cmp(&b.score));
matches.sort_by(|a, b| a.score.cmp(&b.score));
matches matches
} }