# How Hash Table-Based Enable/Disable Detection Optimizes tui-logger Performance

> Discover how tui-logger uses hash table-based enable/disable detection to optimize performance by caching log filters. Reduce overhead and boost speed.

- Repository: [gin66/tui-logger](https://github.com/gin66/tui-logger)
- Tags: performance
- Published: 2026-03-02

---

**tui-logger hashes each log target once and caches the resolved LevelFilter in a HashMap, reducing per-record overhead from complex filter parsing to a single integer lookup.**

The `gin66/tui-logger` crate implements a high-performance logging backend for Terminal User Interface (TUI) applications. Its **hash table-based enable/disable detection** mechanism eliminates redundant filter evaluations by caching target-to-level mappings after the first encounter, keeping UI threads responsive under heavy logging loads.

## How Hash Table-Based Enable/Disable Detection Works

The optimization centers on a `HotSelect` structure that maintains a `HashMap<u64, LevelFilter>`, mapping pre-computed hash values to their effective log levels.

### Fast Target Hashing with fast_str_hash

In [`src/logger/fast_hash.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/fast_hash.rs) (lines 1-7), the `fast_str_hash` function implements a lightweight, deterministic hashing algorithm based on Java's `String.hashCode()`. This converts log target strings into `u64` values with minimal computational cost:

```rust
// Located in src/logger/fast_hash.rs
pub fn fast_str_hash(s: &str) -> u64 {
    // Java String.hashCode() implementation: h = 31*h + c
    let mut hash: u64 = 0;
    for c in s.chars() {
        hash = hash.wrapping_mul(31).wrapping_add(c as u64);
    }
    hash
}

```

This hash function ensures consistent `u64` identifiers for log targets without the overhead of cryptographic hashing.

### Cache Population in move_events()

When the logger processes events in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs) (lines 59-86), the `move_events()` method populates the hash table lazily. Upon encountering a new target for the first time, the code:

1. Computes the hash using `fast_str_hash`
2. Constructs `log::Metadata` for each possible `LevelFilter`
3. Queries the `env_filter` to determine the first matching level
4. Stores the resulting `LevelFilter` in `hot_select.hashtable`

This one-time evaluation captures the effective filtering decision for reuse across all subsequent log records from that target.

### Fast-Path Enabled Check

The `Log::enabled` implementation in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs) (lines 71-80) leverages the cached values:

```rust
// Simplified from src/logger/inner.rs
fn enabled(&self, metadata: &Metadata) -> bool {
    let h = fast_str_hash(metadata.target());
    if let Some(&levelfilter) = self.hot_select.hashtable.get(&h) {
        // Fast path: integer comparison only
        metadata.level() <= levelfilter
    } else {
        // Slow path: full env_filter evaluation
        self.env_filter.enabled(metadata)
    }
}

```

When a hash exists in the table, the logger avoids the `env_filter` entirely, comparing only integer values to determine if the record should be processed.

## Performance Impact: With vs. Without Hash Table

The **hash table-based enable/disable detection** fundamentally changes the overhead profile of logging operations:

- **Without caching**: Every `log::info!` or `log::debug!` call triggers `env_filter.enabled(metadata)`, which parses filter directives and performs string comparisons against the target name. This cost repeats for every log line, even those sharing identical targets.
- **With caching**: After the first encounter, subsequent calls perform only a `u64` hash calculation, a single `HashMap` lookup, and an integer comparison. The `O(1)` lookup eliminates per-record parsing overhead, making logging effectively free for known targets.

This distinction proves critical in TUI applications where the main thread must process UI refreshes alongside logging operations.

## Source Code Implementation Details

The optimization spans three critical source files in the `gin66/tui-logger` repository:

- **[`src/logger/fast_hash.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/fast_hash.rs)**: Contains the `fast_str_hash` function that generates the `u64` identifiers used as hash table keys.
- **[`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs)**: Defines the `HotSelect` structure housing the `HashMap`, implements the `move_events()` cache population logic (lines 59-86), and contains the `enabled()` fast-path check (lines 71-80).
- **[`src/logger/api.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/api.rs)**: Exposes `init_logger()` and `set_default_level()`, which configure the underlying filter that the hash table caches.

## Practical Usage Example

When using `tui-logger` in a Rust application, the hash table populates automatically during the first log emission for each unique target:

```rust
use log::{info, debug, warn};
use tui_logger::init_logger;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize with trace level filtering
    init_logger(log::LevelFilter::Trace)?;
    
    // First call: cache miss, evaluates env_filter
    info!("Application starting");
    
    // Second call: cache hit, O(1) integer lookup
    info!("Processing configuration");
    
    // Different target (warn): new cache entry created
    warn!("Low disk space detected");
    
    // Same target as above: immediate hash table lookup
    warn!("Closing files to free resources");
    
    Ok(())
}

```

In this example, the second `info!` call and second `warn!` call execute the fast path, reading directly from the `HotSelect` hash table without re-evaluating filter expressions.

## Summary

- **tui-logger** uses a `HashMap<u64, LevelFilter>` in `HotSelect` to cache filtering decisions per target.
- The `fast_str_hash` function in [`src/logger/fast_hash.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/fast_hash.rs) generates cheap `u64` hashes based on Java's `String.hashCode()` algorithm.
- `move_events()` in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs) (lines 59-86) populates the cache lazily on first target encounter.
- The `enabled()` method (lines 71-80) performs a single integer lookup and comparison for cached targets, avoiding `env_filter` parsing overhead.
- This optimization reduces per-log-call overhead from string parsing to constant-time integer operations, maintaining UI responsiveness.

## Frequently Asked Questions

### What is the hash table used for in tui-logger?

The hash table stores a mapping between log target hashes (`u64`) and their resolved `LevelFilter` values. According to the `gin66/tui-logger` source code in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs), this allows the logger to check `enabled()` status via a single integer lookup rather than parsing filter expressions on every log call.

### How does the fast_str_hash function work?

Located in [`src/logger/fast_hash.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/fast_hash.rs) (lines 1-7), `fast_str_hash` implements the Java `String.hashCode()` algorithm by iterating through characters and calculating `hash = hash * 31 + c`. This produces a deterministic `u64` value suitable for `HashMap` keys while remaining computationally inexpensive compared to standard library hashing.

### What happens when a new log target is encountered?

When `move_events()` in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs) (lines 59-86) processes a record from a previously unseen target, it computes the hash, evaluates the `env_filter` to determine the effective level, and stores the result in the hash table. Subsequent calls with the same target use the cached value immediately.

### Does this optimization affect all log levels equally?

Yes. The hash table stores `LevelFilter` values, which represent the maximum enabled level for a target. The `enabled()` method performs an integer comparison `metadata.level() <= levelfilter`, making the fast path equally efficient regardless of whether the stored level is Trace, Debug, Info, Warn, or Error.