perf: Parallelverarbeitung wiederherstellen (2.4-2.9x Speedup)

rayon's par_iter() auf indexierten Collections bewahrt die Reihenfolge,
daher war die Entfernung im vorherigen Commit unnötig. Mapping-Logik
in Closure extrahiert um Code-Duplizierung zu vermeiden.

Benchmark (dry-run, quiet):
  5.050 Einträge: 48ms seq → 20ms par (2.4x)
  20.050 Einträge: 186ms seq → 63ms par (2.9x)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-13 04:21:31 +01:00
commit b08ff38a49

View file

@ -11,6 +11,7 @@ use config::Config;
use glob::Pattern;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, info};
use rayon::prelude::*;
use sanitizer::{clean_filename, is_excluded, is_safe_rename, Sequence};
use std::fs;
use std::io::IsTerminal;
@ -31,6 +32,9 @@ const DEFAULT_EXCLUDES: &[&str] = &[
"__pycache__/**",
];
// Schwellwert für parallele Verarbeitung (bei weniger Dateien lohnt sich der Overhead nicht)
const PARALLEL_THRESHOLD: usize = 100;
/// Repräsentiert eine geplante Umbenennungsoperation
#[derive(Debug)]
struct RenameOperation {
@ -161,39 +165,45 @@ fn main() -> Result<()> {
None
};
// Berechne Umbenennungen
// Hinweis: Die Reihenfolge (tiefste zuerst) muss erhalten bleiben,
// damit Parent-Verzeichnisse nicht vor ihren Kindern umbenannt werden.
let rename_ops: Vec<RenameOperation> = entries
.iter()
.filter_map(|entry| {
let old_path = entry.path();
// Berechne Umbenennungen (parallel bei vielen Dateien, sequentiell bei wenigen)
// Hinweis: rayon's par_iter() auf indexierten Collections bewahrt die Reihenfolge,
// sodass die tiefenbasierte Sortierung erhalten bleibt.
let map_entry = |entry: &walkdir::DirEntry| -> Option<RenameOperation> {
let old_path = entry.path();
// Ebenentiefe 0 -> überspringen
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
return None;
// Ebenentiefe 0 -> überspringen
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
return None;
}
// Special Files (Symlinks, Sockets, etc.) nur mit --special
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
if args.verbose && file_type.is_symlink() {
debug!(
"Überspringe Symlink: {} (nutze --special um Symlink-Namen zu bereinigen)",
old_path.display()
);
}
return None;
}
// Special Files (Symlinks, Sockets, etc.) nur mit --special
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
if args.verbose && file_type.is_symlink() {
debug!("Überspringe Symlink: {} (nutze --special um Symlink-Namen zu bereinigen)", old_path.display());
}
return None;
}
// Dateiname ermitteln und bereinigen
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, &sequence, false)?;
let new_path = old_path.with_file_name(&new_name);
// Dateiname ermitteln und bereinigen
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, &sequence, false)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
old_path: old_path.to_path_buf(),
new_path,
})
Some(RenameOperation {
old_path: old_path.to_path_buf(),
new_path,
})
.collect();
};
let rename_ops: Vec<RenameOperation> = if entries.len() >= PARALLEL_THRESHOLD {
entries.par_iter().filter_map(map_entry).collect()
} else {
entries.iter().filter_map(map_entry).collect()
};
// Statistiken
let total_processed = entries.len();