use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use clap::Parser;
use binseq::{BinseqReader, BinseqRecord, ParallelProcessor, ParallelReader, Result};
#[derive(Clone)]
struct RangeProcessor {
counter: Arc<AtomicUsize>,
tid: Option<usize>,
range_start: usize,
range_end: usize,
}
impl RangeProcessor {
fn new(range_start: usize, range_end: usize) -> Self {
Self {
counter: Arc::new(AtomicUsize::new(0)),
tid: None,
range_start,
range_end,
}
}
fn count(&self) -> usize {
self.counter.load(Ordering::Relaxed)
}
}
impl ParallelProcessor for RangeProcessor {
fn process_record<R: BinseqRecord>(&mut self, record: R) -> Result<()> {
let count = self.counter.fetch_add(1, Ordering::Relaxed);
if count.is_multiple_of(10_000)
&& let Some(tid) = self.tid
{
println!(
"Thread {}: Processed {} records (Range: {}-{}, Index: {}, Len: {})",
tid,
count + 1,
self.range_start,
self.range_end,
record.index(),
record.sseq().len(),
);
}
Ok(())
}
fn set_tid(&mut self, tid: usize) {
self.tid = Some(tid);
}
fn get_tid(&self) -> Option<usize> {
self.tid
}
fn on_batch_complete(&mut self) -> Result<()> {
if let Some(tid) = self.tid {
println!("Thread {tid} completed batch processing");
}
Ok(())
}
}
#[derive(Parser)]
struct Cli {
path: PathBuf,
#[clap(short = 'T', long, default_value_t = 0)]
threads: usize,
#[clap(long, default_value_t = 0)]
start: usize,
#[clap(long, default_value_t = 10_000)]
end: usize,
}
fn main() -> Result<()> {
let args = Cli::parse();
let reader = BinseqReader::new(&args.path)?;
let total_records = reader.num_records()?;
println!("File: {}", args.path.display());
println!("Total records in file: {total_records}");
if args.start >= total_records {
eprintln!(
"Error: Start index {} is >= total records {total_records}",
args.start
);
std::process::exit(1);
}
if args.end > total_records {
eprintln!(
"Warning: End index {} is > total records {total_records}, clamping to {total_records}",
args.end
);
}
let end = args.end.min(total_records);
if args.start >= end {
eprintln!(
"Error: Start index {} must be < end index {end}",
args.start
);
std::process::exit(1);
}
println!(
"Processing range: {} to {} ({} records)",
args.start,
end,
end - args.start
);
println!("Using {} threads", args.threads);
println!();
println!("=== Processing full file ===");
let reader_full = BinseqReader::new(&args.path)?;
let processor_full = RangeProcessor::new(0, total_records);
let start_time = std::time::Instant::now();
reader_full.process_parallel(processor_full.clone(), args.threads)?;
let elapsed_full = start_time.elapsed();
println!("Full file processing completed!");
println!("Records processed: {}", processor_full.count());
println!("Time taken: {elapsed_full:.2?}");
println!();
println!("=== Processing specific range ===");
let reader_range = BinseqReader::new(args.path)?;
let processor_range = RangeProcessor::new(args.start, end);
let start_time = std::time::Instant::now();
reader_range.process_parallel_range(processor_range.clone(), args.threads, args.start..end)?;
let elapsed_range = start_time.elapsed();
println!("Range processing completed!");
println!("Records processed: {}", processor_range.count());
println!("Expected records: {}", end - args.start);
println!("Time taken: {elapsed_range:.2?}");
if processor_range.count() > 0 && processor_full.count() > 0 {
let full_rate = processor_full.count() as f64 / elapsed_full.as_secs_f64();
let range_rate = processor_range.count() as f64 / elapsed_range.as_secs_f64();
println!();
println!("=== Performance Comparison ===");
println!("Full file rate: {full_rate:.0} records/sec");
println!("Range rate: {range_rate:.0} records/sec");
if range_rate > full_rate {
println!(
"Range processing was {:.1}x faster per record",
range_rate / full_rate
);
} else {
println!(
"Full file processing was {:.1}x faster per record",
full_rate / range_rate
);
}
}
Ok(())
}