# Wasot Performance Tips

Performance patterns and anti-patterns specific to wasot's text processing pipeline.

## Quick Reference

| Pattern | Bad | Good | Impact |
|---------|-----|------|--------|
| Regex in loops | `Regex::new()` per line | `OnceLock<Regex>` | 172x faster |
| String escaping | Always `s.replace()` | `Cow<str>` when no change | 10-20% faster |
| Dots for levels | `for _ in 0..n { push('.') }` | Static lookup table | 5-10% faster |
| Vec creation | `Vec::new()` then grow | `Vec::with_capacity(n)` | 10-15% fewer allocs |
| Format strings | `format!()` for checks | `strip_prefix()` parsing | Avoids allocation |

## Patterns

### 1. Cache Compiled Regexes

Regex compilation is expensive (~300KB allocations per `Regex::new()`).

```rust
// BAD: Compiles on every call
fn extract_id(input: &str) -> Option<&str> {
    let regex = Regex::new(r"\s:[A-Za-z0-9]+$").unwrap();
    regex.find(input).map(|m| m.as_str())
}

// GOOD: Compiles once
fn extract_id(input: &str) -> Option<&str> {
    use std::sync::OnceLock;
    static REGEX: OnceLock<Regex> = OnceLock::new();
    let regex = REGEX.get_or_init(|| {
        Regex::new(r"\s:[A-Za-z0-9]+$").unwrap()
    });
    regex.find(input).map(|m| m.as_str())
}
```

**Real example:** `src/parsers/wasot/parser.rs:188-200` - Fixed 172x speedup.

### 2. Use Cow<str> for Conditional String Operations

When a string operation often returns the input unchanged, use `Cow` to avoid allocation.

```rust
// BAD: Always allocates
fn escape_quotes(s: &str) -> String {
    s.replace('"', "\\\"")
}

// GOOD: Only allocates when needed
fn escape_quotes(s: &str) -> Cow<'_, str> {
    if s.contains('"') {
        Cow::Owned(s.replace('"', "\\\""))
    } else {
        Cow::Borrowed(s)
    }
}
```

**Real example:** `src/consumers/wasot_reconstructor.rs:43-51`

### 3. Pre-compute Repeated String Patterns

For patterns that repeat (like indentation dots), use a static lookup table.

```rust
// BAD: Loop with individual pushes
fn add_dots(line: &mut String, level: usize) {
    for _ in 0..level {
        line.push('.');
    }
}

// GOOD: Static lookup for common cases
fn dots_for_level(level: usize) -> &'static str {
    const DOTS: [&str; 11] = [
        "", ".", "..", "...", "....", ".....",
        "......", ".......", "........", ".........", "..........",
    ];
    if level < DOTS.len() {
        DOTS[level]
    } else {
        Box::leak(".".repeat(level).into_boxed_str())
    }
}
```

**Real example:** `src/consumers/wasot_reconstructor.rs:89-109`

### 4. Pre-allocate Vectors When Size is Known

When you know how many elements a Vec will hold, pre-allocate.

```rust
// BAD: Grows dynamically
fn process_lines(lines: Vec<String>) -> Vec<String> {
    let mut output = Vec::new();
    for line in lines {
        output.push(transform(line));
    }
    output
}

// GOOD: Pre-allocate
fn process_lines(lines: Vec<String>) -> Vec<String> {
    let mut output = Vec::with_capacity(lines.len());
    for line in lines {
        output.push(transform(line));
    }
    output
}
```

**Real example:** `src/parsers/wasot/interceptor.rs:118-120`

### 5. Avoid format!() for Comparisons

When checking if a string matches a pattern, parse instead of formatting.

```rust
// BAD: Allocates just to compare
fn footnote_exists(lines: &[String], marker: u32, link: &str) -> bool {
    let expected = format!("*{} {}", marker, link);
    lines.iter().any(|line| line.trim() == expected)
}

// GOOD: Parse without allocation
fn footnote_exists(lines: &[String], marker: u32, link: &str) -> bool {
    let marker_str = marker.to_string();
    lines.iter().any(|line| {
        let trimmed = line.trim();
        trimmed.strip_prefix('*')
            .and_then(|rest| rest.strip_prefix(&marker_str))
            .map(|rest| rest.trim_start().starts_with(link))
            .unwrap_or(false)
    })
}
```

**Real example:** `src/consumers/wasot_reconstructor.rs:444-459`

### 6. Use Byte Operations for ASCII Checks

For ASCII-only checks, use byte operations instead of char iterators.

```rust
// BAD: Creates iterator, may allocate
fn is_uri(target: &str) -> bool {
    let mut chars = target.chars();
    if let Some(first) = chars.next() {
        if first.is_ascii_lowercase() {
            let remaining: String = chars.collect(); // Allocates!
            return remaining.contains("://");
        }
    }
    false
}

// GOOD: Byte slice operations
fn is_uri(target: &str) -> bool {
    if target.len() < 4 {
        return false;
    }
    let first = target.as_bytes()[0];
    first.is_ascii_lowercase() && target[1..].contains("://")
}
```

**Real example:** `src/parsers/wasot/interceptor.rs:85-103`

### 7. Avoid format!() in Hot Paths - Use write!()

When building strings incrementally, `write!()` to an existing String is cheaper than `format!()`.

```rust
// BAD: Creates new String
let suffix = format!(" :*{}", counter);
content.push_str(&suffix);

// GOOD: Write directly
use std::fmt::Write;
write!(content, " :*{}", counter).unwrap();
```

**Real example:** `src/parsers/wasot/interceptor.rs:179-181`

## Profiling Workflow

1. **Baseline**: `./scripts/benchmark_cli.py --quick`
2. **Profile**: `./scripts/profile.py` (DHAT heap analysis)
3. **Identify**: Look for high `tb` (total bytes) in allocation sites
4. **Fix**: Apply patterns above
5. **Verify**: Re-run benchmark, check allocation reduction

## Common Hotspots in Wasot

| Location | What to Check |
|----------|---------------|
| `parser.rs` | Regex compilation, string allocations per line |
| `interceptor.rs` | Link processing, footnote generation |
| `wasot_reconstructor.rs` | Output formatting, escape functions |
| `block.rs` | Text element creation, Vec growth |

## Performance Targets

| File Size | Lines | Target Time | Target Rate |
|-----------|-------|-------------|-------------|
| 1 MB | ~22K | < 50ms | > 500K lines/s |
| 100 MB | ~2.2M | < 2s | > 1M lines/s |

Current performance: **2.5M lines/second** on 100MB files.

## See Also

- `docs/profiling_guide.md` - Detailed profiling instructions
- `reports/performance_analysis_2026-02-11.md` - Regex fix case study
- `scripts/benchmark_cli.py` - Benchmark tool with assertions
- `scripts/profile.py` - DHAT profiling helper
