Performance Benefits of fff.nvim's Rust Backend: SIMD-Accelerated Fuzzy Finding in Neovim

fff.nvim achieves orders-of-magnitude faster file searching than pure-Lua implementations by offloading CPU-intensive fuzzy matching to a Rust backend that leverages SIMD instructions, multi-threading across all logical CPUs, and cache-friendly data structures.

fff.nvim is a Neovim file finder plugin that splits its architecture between a minimal Lua UI layer and a high-performance Rust backend. This design eliminates the overhead of interpreted Lua for filesystem scanning and fuzzy string matching, delivering sub-second search results even in repositories with 100,000+ files.

Architecture Overview: Thin Lua, Fast Rust

The plugin employs a deliberate architectural split that keeps the Lua side lightweight while pushing all heavy computation to compiled machine code.

  • Lua UI Layer (lua/fff/*.lua): Handles the picker interface, parses user input, and forwards calls to the Rust side. This minimal wrapper approach avoids the algorithmic overhead of pure-Lua implementations.

  • Rust Backend (crates/fff-core, crates/fff-c): Performs filesystem scanning, builds file-item caches, and executes fuzzy-search algorithms. The core data structures—FileItem, Score, and FilePicker—store pre-computed frecency (file open frequency) and git status, enabling rapid filtering without additional I/O operations.

  • FFI Glue (crates/fff-c/src/lib.rs): Exposes Rust functions as C-compatible symbols (fff_*) that Lua can call directly via package.loadlib. This creates a stable ABI where Lua receives plain pointers and frees them using dedicated fff_free_* functions.

SIMD-Accelerated Fuzzy Matching

The Rust backend utilizes the neo_frizbee crate to evaluate millions of candidates in parallel using CPU vector registers.

In crates/fff-core/src/score.rs, the match_and_score_files function constructs a ScoringContext and invokes neo_frizbee::match_list_parallel. This SIMD-based approach provides approximately 5× speed-up on x86_64 compared to scalar implementations by processing multiple characters simultaneously within CPU registers.

// crates/fff-core/src/score.rs
let options = neo_frizbee::Config {
    max_typos: Some(context.max_typos),
    sort: false,
    scoring: Scoring {
        capitalization_bonus: if has_uppercase { 8 } else { 0 },
        matching_case_bonus: if has_uppercase { 4 } else { 0 },
        ..Default::default()
    },
};
let path_matches = match_fuzzy_parts(
    fuzzy_parts, 
    &working_files, 
    &options, 
    context.max_threads
);

The algorithm also detects path separators in queries and falls back to specialized filename matching to maintain tight inner loops.

Multi-Threaded Execution

By default, fff.nvim utilizes all available logical CPUs through std::thread::available_parallelism.

Both the fuzzy-matching stage and filename-fallback stages in score.rs use neo_frizbee::match_list_parallel, which distributes work across the configured thread count. For small result sets (under 10,000 matches), the system automatically falls back to single-threaded execution to minimize synchronization overhead.

// crates/fff-core/src/score.rs
let matches = neo_frizbee::match_list_parallel(
    fuzzy_parts[0],
    &fallback_filenames,
    &options,
    if path_matches.len() > 10_000 { context.max_threads } else { 1 },
);

This multi-core scaling reduces search latency in large projects from seconds to sub-second response times.

Intelligent Caching and Scoring

The backend employs cache-friendly data structures that pre-compute expensive metadata during the initial filesystem scan.

  • Frecency tracking: Files are scored based on how recently and frequently they were opened
  • Git status boost: Dirty files receive a 15% scoring bonus, prioritizing active work
  • Pre-computed path components: Eliminating string parsing during the search hot path

These optimizations occur in crates/fff-core/src/file_picker.rs, where the FilePicker struct maintains the working set in memory layouts optimized for CPU cache lines.

Zero-Copy FFI Integration

The Rust library compiles to a shared object (libfff_nvim.so, .dll, or .dylib) loaded directly via package.loadlib in lua/fff/rust/init.lua. This approach eliminates Lua require-time cpath pollution and ensures the exact binary is used without intermediate copying.

-- lua/fff/rust/init.lua
local function try_load_library()
  for _, path_pattern in ipairs(paths) do
    local actual_path = resolve_path(path_pattern:gsub('%?', 'fff_nvim'))
    local stat = vim.uv.fs_stat(actual_path)
    if stat and stat.type == 'file' then
      local loader, err = package.loadlib(actual_path, 'luaopen_fff_nvim')
      -- ...
    end
  end
end

The FFI boundary in crates/fff-c/src/lib.rs returns opaque pointers to FffSearchResult and FffGrepResult structures, which Lua wrappers expose as tables without data duplication until conversion is necessary.

Practical Implementation Examples

Opening the Fuzzy File Picker

local fff = require('fff')
local ui = require('fff.picker_ui')

ui.open({
  prompt = 'Find file: ',
  initial_query = '',
  on_select = function(item)
    -- item.path populated by Rust search results
    vim.cmd('edit ' .. item.path)
  end,
})

Performing Live Grep Searches

local fff = require('fff')
local result = fff.fuzzy.live_grep('fn main', 0, 0, false, 0, 0, 0, false, 0, 10)

for i = 0, result.count - 1 do
  local match = result:get_match(i)  -- calls fff_grep_result_get_match
  print(string.format('%s:%d %s', match.file_path, match.line, match.text))
end

Forcing a Filesystem Rescan

local fff_rust = require('fff.rust')
fff_rust.scan_files()  -- Re-index after mass file changes

Summary

  • fff.nvim offloads all CPU-bound work to a Rust backend while keeping the UI in Lua, minimizing interpreter overhead.
  • SIMD acceleration via neo_frizbee provides ~5× speedup on modern x86_64 processors.
  • Multi-threading automatically scales to all logical CPUs, handling 100,000+ file repositories in sub-second times.
  • Zero-copy FFI through package.loadlib eliminates memory duplication between Lua and Rust.
  • Cache-friendly structures with pre-computed frecency and git status avoid post-filter passes and I/O bottlenecks.

Frequently Asked Questions

How does fff.nvim's Rust backend improve search speed compared to Telescope or fzf-lua?

Pure-Lua file finders must execute fuzzy matching algorithms within the Lua interpreter, which lacks SIMD instructions and true multi-threading. According to the fff.nvim source code, the Rust backend in crates/fff-core/src/score.rs executes fuzzy matching using vectorized CPU instructions across multiple threads, resulting in orders-of-magnitude faster performance on large codebases.

What is the neo_frizbee crate and why does it matter for performance?

The neo_frizbee crate is the underlying Rust library used in crates/fff-core/src/score.rs that provides match_list_parallel. It implements SIMD-accelerated fuzzy string matching algorithms that process multiple bytes simultaneously using CPU vector registers, delivering the approximately 5× speedup over scalar implementations observed in benchmarks.

How does the multi-threading in fff.nvim work with large codebases?

The Rust backend detects the number of logical CPUs via std::thread::available_parallelism and passes this as max_threads to neo_frizbee::match_list_parallel. As implemented in score.rs, the system automatically uses parallel processing when candidate lists exceed 10,000 items, while smaller sets use single-threaded execution to avoid synchronization overhead.

Is there a performance penalty for the FFI communication between Lua and Rust?

The FFI design in crates/fff-c/src/lib.rs minimizes overhead by returning opaque pointers (FffResult) rather than copying data structures across the boundary. Lua calls dedicated fff_free_* functions to release memory, ensuring zero-copy operations where the Lua side only handles thin wrappers around Rust-allocated data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →