feat: Parallele Verarbeitung mit rayon bei vielen Dateien

- Bei >= 100 Dateien wird rayon für parallele Berechnung genutzt
- clean_filename() wird parallel ausgeführt (CPU-intensive Operation)
- Tatsächliche Umbenennung bleibt sequenziell (I/O-bound, Race-Conditions vermeiden)
- Bei < 100 Dateien sequenziell (Overhead vermeiden)
- RenameOperation-Struct für geplante Umbenennungen

Performance-Gewinn bei großen Verzeichnisbäumen mit vielen Dateien.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-10 10:24:50 +01:00
commit ca49404dfa

View file

@ -10,8 +10,10 @@ 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};
use std::fs;
use std::path::PathBuf;
use walkdir::WalkDir;
// Standard-Ausschlussmuster (ähnlich wie detox)
@ -28,6 +30,16 @@ const DEFAULT_EXCLUDES: &[&str] = &[
"__pycache__/**",
];
// Schwellwert für parallele Verarbeitung (bei weniger Dateien lohnt sich Overhead nicht)
const PARALLEL_THRESHOLD: usize = 100;
/// Repräsentiert eine geplante Umbenennungsoperation
#[derive(Debug)]
struct RenameOperation {
old_path: PathBuf,
new_path: PathBuf,
}
/// Startpunkt des Programms
fn main() -> Result<()> {
// Initialisiere Logger
@ -92,56 +104,86 @@ fn main() -> Result<()> {
None
};
// Umbenennen in absteigender Tiefe
for entry in entries {
// Berechne Umbenennungen (parallel bei vielen Dateien)
let rename_ops: Vec<RenameOperation> = if entries.len() >= PARALLEL_THRESHOLD {
// Parallel mit rayon
entries
.par_iter()
.filter_map(|entry| {
let old_path = entry.path();
// Ebenentiefe 0 -> überspringen
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
return None;
}
// Special Files nur mit --special
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
return None;
}
// Dateiname ermitteln und bereinigen
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, false)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
old_path: old_path.to_path_buf(),
new_path,
})
})
.collect()
} else {
// Sequenziell bei wenigen Dateien
entries
.iter()
.filter_map(|entry| {
let old_path = entry.path();
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
return None;
}
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
return None;
}
let filename = old_path.file_name()?;
let new_name = clean_filename(filename, &config, false)?;
let new_path = old_path.with_file_name(&new_name);
Some(RenameOperation {
old_path: old_path.to_path_buf(),
new_path,
})
})
.collect()
};
// Umbenennungen sequenziell ausführen
for op in rename_ops {
if let Some(bar) = &progress_bar {
bar.inc(1);
}
let old_path = entry.path();
// Ebenentiefe 0 -> überspringen wir als Verzeichnis, außer --modify_root ist gesetzt
if entry.depth() == 0 && entry.file_type().is_dir() && !args.modify_root {
if args.verbose {
debug!("Skip root directory: {}", old_path.display());
}
continue;
if !args.quiet {
info!("{} -> {}", op.old_path.display(), op.new_path.display());
}
// Special Files (Symlinks, etc.) nur mit --special verarbeiten
let file_type = entry.file_type();
if !args.special && (!file_type.is_file() && !file_type.is_dir()) {
if args.verbose {
debug!("Skip special file: {}", old_path.display());
}
continue;
if args.verbose {
debug!("Rename: {:?} -> {:?}", op.old_path, op.new_path);
}
// Dateiname (oder Verzeichnisname) ermitteln
let filename = old_path.file_name().ok_or_else(|| {
anyhow::anyhow!(
"Konnte Dateinamen nicht ermitteln für: {}",
old_path.display()
)
})?;
// Verarbeiten und ggf. umbenennen
if let Some(new_name) = clean_filename(filename, &config, args.verbose) {
let new_path = old_path.with_file_name(&new_name);
if !args.quiet {
info!("{} -> {}", old_path.display(), new_path.display());
}
if !args.dry_run && is_safe_rename(old_path, &new_path, args.force) {
fs::rename(old_path, &new_path).with_context(|| {
format!(
"Fehler beim Umbenennen: {} -> {}",
old_path.display(),
new_path.display()
)
})?;
}
if !args.dry_run && is_safe_rename(&op.old_path, &op.new_path, args.force) {
fs::rename(&op.old_path, &op.new_path).with_context(|| {
format!(
"Fehler beim Umbenennen: {} -> {}",
op.old_path.display(),
op.new_path.display()
)
})?;
}
}