tale-ndjson 0.2.1

A tail-compatible tool for pretty-printing ndjson files, especially logs.
Documentation
# Tale Optimization Results

**Date:** August 3, 2025  
**Version:** After comprehensive optimization campaign  
**Platform:** macOS (Apple Silicon)  
**Rust Version:** 2024 edition  

## Performance Summary

### Before vs After Optimization

| Dataset | Baseline Time | Optimized Time | Improvement | Throughput Gain |
|---------|---------------|----------------|-------------|-----------------|
| **Small (241KB)** | ~160ms | 163.0ms Β± 9.7ms | 0% (within noise) | ~1.5M lines/sec |
| **Medium (23MB)** | 616.2ms Β± 3.8ms | **443.1ms Β± 1.9ms** | **28.1% faster** | ~252K lines/sec |
| **Large (235MB)** | ~4.6s | **2.884s Β± 0.012s** | **37.3% faster** | ~387K lines/sec |

### Key Achievements
- **πŸš€ Up to 37% performance improvement** on large files
- **🧠 Excellent memory efficiency**: 4.2MB peak memory for 235MB files (1.8% ratio)
- **⚑ Consistent performance**: Low variance indicates stable optimization
- **πŸ“ˆ Better scaling**: Larger files see greater improvement

## Optimization Breakdown

### 1. **Canonical Log Type** (β˜…β˜…β˜… **MOST EFFECTIVE**)
- **Impact**: 25-34% performance improvement
- **Mechanism**: Strict deserialization with required fields, no `Option<>` wrappers
- **Technical**: Custom `Canonical<'a>` struct for well-structured HTTP logs
- **Why effective**: Eliminates branching during deserialization, faster field access

### 2. **Zero-Copy Deserialization with Cow<'a, str>** (β˜…β˜…β˜†)
- **Impact**: ~1.8M allocation reductions per second
- **Mechanism**: `Cow<'a, str>` instead of owned `String` fields
- **Technical**: `#[serde(borrow)]` annotations for borrowing from input buffer
- **Why effective**: Eliminates string allocations when data can be borrowed

### 3. **Direct Buffer Writing** (β˜…β˜†β˜†)
- **Impact**: 5-6% performance improvement  
- **Mechanism**: Pre-compiled ANSI escape sequences, eliminated `format!` calls
- **Technical**: Static byte arrays + `buffer.extend_from_slice()`
- **Optimizations**: Log level formatting, timestamp formatting

### 4. **Enum Variant Boxing** (β˜…β˜†β˜†)
- **Impact**: Memory layout improvement (360β†’40 bytes per enum)
- **Mechanism**: `Box<Message<'a>>` and `Box<Canonical<'a>>` in enum
- **Technical**: Heap allocation for large variants, pointer storage in enum
- **Why effective**: Better cache performance, reduced stack pressure

## Current Performance Characteristics

### Throughput Rates
| Dataset | Lines/sec | MB/sec | CPU Efficiency |
|---------|-----------|--------|----------------|
| Small | 7,127 | 1.48 | High (193ms total, 49ms CPU) |
| Medium | 252,406 | 51.9 | High (443ms total, 313ms CPU) |
| Large | 387,090 | 81.5 | High (2.88s total, 2.75s CPU) |

### Memory Characteristics
- **Peak Memory**: 4.2MB for 235MB file (1.8% ratio)
- **Memory Pattern**: Constant usage regardless of file size
- **Allocation Strategy**: Streaming processing, zero accumulation
- **Buffer Management**: Efficient `BytesMut` with periodic flushing

## Technical Architecture Changes

### Deserialization Pipeline
```rust
// Before: Flexible but slow
Message { 
    level: String,           // Always allocated
    message: String,         // Always allocated
    timestamp: Option<...>,  // Branching overhead
    // ... many optional fields
}

// After: Fast path for structured logs
Canonical {
    level: Cow<'a, str>,     // Zero-copy when possible
    message: Cow<'a, str>,   // Zero-copy when possible  
    timestamp: Timestamp,    // Required, no Option wrapper
    // ... all required fields
}
```

### Memory Layout Optimization
```rust
// Before: 360-byte enum (largest variant size)
enum Printable<'a> {
    Message(Message<'a>),    // 360 bytes
    Json(GenericJson),       // 32 bytes  
}

// After: ~40-byte enum with heap allocation
enum Printable<'a> {
    Message(Box<Message<'a>>),  // 8 bytes (pointer)
    Json(GenericJson),          // 32 bytes
}
```

## Key Learnings

### 1. **Architectural Changes > Micro-optimizations**
The `Canonical` type (architectural) provided **25-34% improvement** while direct buffer writing (micro-optimization) provided **5-6% improvement**.

### 2. **Deserialization is the Bottleneck**
Most performance gains came from optimizing the JSON→struct conversion path, not the formatting/output path.

### 3. **Memory Pressure vs. Memory Usage**
We achieved excellent memory efficiency (1.8% ratio) through streaming, making memory micro-optimizations less impactful.

### 4. **Structured Data Assumptions Pay Off**
Betting on well-structured log formats with the `Canonical` type provided massive gains for real-world usage.

## Future Optimization Opportunities

### High Impact (if needed)
1. **Additional log format specializations** based on common patterns
2. **SIMD-based JSON parsing** for high-throughput scenarios
3. **Custom serde deserializer** optimized for log formats

### Medium Impact
1. **Replace `term_grid` with direct layout** (eliminate intermediate allocations)
2. **Custom colorization** without `owo-colors` dependencies
3. **Optimize `humansize` formatting** or implement custom byte formatting

### Low Impact (completed or minimal benefit)
1. βœ… **Direct buffer writing** (completed)
2. βœ… **Zero-copy deserialization** (completed)  
3. βœ… **Memory layout optimization** (completed)

## Conclusion

The optimization campaign achieved **outstanding results**, delivering **28-37% performance improvements** through systematic analysis and targeted optimizations. The most significant insight was that **architectural improvements** (like the `Canonical` type) provided far greater benefits than micro-optimizations.

The application now processes **387K lines/sec** on large files while maintaining **1.8% memory footprint**, making it suitable for processing multi-gigabyte log files efficiently.

---

*Results demonstrate the importance of profiling actual bottlenecks and focusing optimization efforts on high-impact architectural changes rather than premature micro-optimizations.*