# Performance Characteristics

This document explains the performance characteristics of mojo-toml v0.9.1 and the copying behaviour inherent in Mojo's ownership model.


# Benchmark Results (v0.9.1)

Performance measurements on Apple M1 Max (macOS). See `benchmarks/benchmark_parser.mojo` for Mojo benchmarks and `benchmarks/compare_python.py` for Python baselines.


## Parser Performance (mojo-toml)

| Operation | Throughput | Average Time | Notes |
|----|----|----|----|
| Simple parse (5 keys) | 40,546 parses/sec | 24 μs | Very fast for small configs |
| Nested tables (7 keys) | 4,834 parses/sec | 206 μs | Good for structured configs |
| Arrays (~40 elements) | 7,025 parses/sec | 142 μs | Efficient array handling |
| Dotted keys (9 keys) | 5,176 parses/sec | 193 μs | Comparable to table headers |
| Large document (40+ keys) | 331 parses/sec | 3 ms | Still fast for real-world files |
| Real pixi.toml | 368 parses/sec | 2 ms | Production-ready performance |
| **Table access** | **99,592 accesses/sec** | **10 μs** | **Negligible overhead** |


## Comparison with Python (tomllib)

| Operation                            | mojo-toml | Python tomllib | Speedup       |
|--------------------------------------|-----------|----------------|---------------|
| Simple document (5 keys)             | 24 μs     | 11 μs          | 0.5x (slower) |
| Nested tables (6 keys)               | ~200 μs\* | 22 μs          | 0.1x (slower) |
| Array of tables (2 products)         | ~180 μs\* | 14 μs          | 0.1x (slower) |
| Alternative number bases (3 numbers) | ~20 μs\*  | 8 μs           | 0.4x (slower) |

\*Estimated from similar complexity benchmarks


## Writer Performance Comparison

| Operation                | mojo-toml           | Python tomli_w | Speedup |
|--------------------------|---------------------|----------------|---------|
| Simple document (5 keys) | Not yet benchmarked | 5 μs           | TBD     |
| Nested tables (6 keys)   | Not yet benchmarked | 9 μs           | TBD     |


## Key Findings

1.  **Parsing is fast**: Even large documents parse in milliseconds
2.  **Table access overhead is minimal**: 10 μs per nested `as_table()` call
3.  **Real-world performance is excellent**: pixi.toml parses in ~2ms
4.  **Python is faster for now**: CPython's `tomllib` is currently 2-10x faster due to optimised C implementation
5.  **The copying overhead is acceptable** for typical use cases


# Copying Behavior


## Why Copying Happens

mojo-toml uses **value semantics** throughout, which means data is copied rather than shared. This is a deliberate design choice driven by Mojo's ownership model:

1.  **Safety**: Prevents aliasing issues and use-after-free bugs
2.  **Simplicity**: No lifetime annotations or borrow checker complexity
3.  **Predictability**: Clear ownership semantics


## Where Copying Occurs


### 1. `as_table()` and `as_array()` Methods

``` mojo
fn as_table(self) raises -> Dict[String, TomlValue]:
    # Returns a COPY of the table
    var result = Dict[String, TomlValue]()
    for entry in self.table_value.items():
        result[entry.key] = entry.value.copy()  # Deep copy
    return result^
```

**Why**: Mojo doesn't currently support borrowed returns from struct methods. The copy ensures safe ownership transfer.

**Impact**: 10 μs per call (negligible for typical usage)


### 2. Parser Internal Functions

Functions like `ensure_table_path()` and `set_in_table_path()` use functional-style copying:

``` mojo
fn ensure_table_path(mut self, result: Dict[String, TomlValue], path: List[String]) raises -> Dict[String, TomlValue]:
    # Creates new Dict with modifications
    var new_result = Dict[String, TomlValue]()
    for entry in result.items():
        new_result[entry.key] = entry.value.copy()
    # ... modifications ...
    return new_result^
```

**Why**: Mojo's ownership prevents in-place mutation of borrowed parameters in some contexts.

**Impact**: O(depth × keys) during parsing, but amortized over the entire parse operation (still 2-3ms for large files)


### 3. KeyValuePair Extraction

``` mojo
var parsed_key = pair.key
var parsed_value = pair.value.copy()
```

**Why**: Mojo prevents partial destruction - can't move `pair.value` while `pair.key` is still needed.

**Impact**: One copy per key-value pair during parsing (included in overall parse time)


# Performance Recommendations


## ✅ Do This (Efficient)

``` mojo
// Cache table references if accessing multiple times
var db = config["database"].as_table()
var host = db["host"].as_string()
var port = db["port"].as_int()
var user = db["user"].as_string()  // ~26 μs total
```


## ❌ Avoid This (Wasteful)

``` mojo
// Don't call as_table() repeatedly
var host = config["database"].as_table()["host"].as_string()
var port = config["database"].as_table()["port"].as_int()
var user = config["database"].as_table()["user"].as_string()  // ~30 μs total
```

The difference is small (4 μs) but caching is better practice.


## Reuse Parsers

``` mojo
// Efficient: Reuse parser instance
var parser = Parser(tokens1^)
var data1 = parser.parse()

parser.reset(tokens2^)
var data2 = parser.parse()
```

This avoids allocating new Parser objects.


# Future Optimizations


## Potential Improvements

1.  **Borrowed References**: If Mojo adds support for borrowed struct method returns:

    ``` mojo
    fn as_table_ref(borrowed self) -> borrowed Dict[String, TomlValue]:
        return self.table_value  // Zero-copy!
    ```

2.  **In-Place Mutation**: If Mojo's ownership allows more flexible mutation:

    ``` mojo
    fn ensure_table_path_mut(mut self, mut result: Dict[String, TomlValue], ...)
        // Mutate result directly instead of copying
    ```

3.  **Arena Allocation**: Use a memory arena for parser-internal structures


## Why Not Now?

- **Mojo limitations**: Current language doesn't support borrowed returns from methods
- **Minimal impact**: Benchmark shows copying overhead is negligible (10 μs)
- **Correctness first**: Value semantics ensure safety and correctness
- **Future-proof**: Can optimize when Mojo adds features without API changes


# Is Performance Acceptable?

**Yes, for typical use cases:**

1.  **Configuration files parse in milliseconds**: Real pixi.toml parses in ~2ms
2.  **Table access is fast**: Negligible 10 μs overhead per `as_table()` call
3.  **Throughput is good**: 40K+ parses/sec for simple configs, 300+ for large documents
4.  **Value semantics provide safety**: No aliasing bugs or lifetime complexity

**Python is currently faster** (2-10x) because CPython's `tomllib` is implemented in optimised C. However: - Pure Python parsers like `tomli` are slower (5-10ms vs 2ms for pixi.toml) - Mojo is still early-stage; compiler optimisations will improve - The difference is negligible for config file use cases (microseconds)


# Conclusion

The copying behaviour in mojo-toml is:

1.  **Necessary**: Due to Mojo's current ownership model
2.  **Fast enough**: 10 μs per table access is negligible for config files
3.  **Acceptable**: Real-world files parse in 2-3ms - fast enough for any config loading scenario
4.  **Improvable**: Future Mojo features could eliminate copies and match C performance

For 99% of use cases (reading configuration files), the current performance is excellent. The value semantics provide safety and simplicity without meaningful performance cost.


# Running Benchmarks

Both benchmarks generate markdown reports with full system information:

``` bash
# Mojo benchmark (with report generation)
pixi run benchmark-mojo

# Python baseline (tomllib/tomli_w comparison)
pixi run benchmark-python
```

Reports are saved to `benchmarks/reports/` with complete system information. You can also run the raw Mojo benchmark:

``` bash
mojo -I src benchmarks/benchmark_parser.mojo
```

If you have specific performance requirements or large-scale parsing needs, please file an issue with your use case.
