Parallel walk-and-search architecture. PCRE2 JIT. High-performance design.
Below is a benchmark comparison demonstrating the execution time of greg alongside other popular code search tools. The benchmark measures search execution time for the pattern "ls" across a home directory structure (~1,141 files) under Linux / ARM64.
All stdout output was redirected to /dev/null to measure engine processing latency rather than terminal rendering speeds.
| Tool | Average Execution Time (ms) | Relative Performance |
|---|---|---|
| ugrep | 53 ms | 1.00x (Baseline) |
| greg | 54 ms | 1.02x |
ripgrep (rg) | 63 ms | 1.19x |
The Silver Searcher (ag) | 662 ms | 12.49x |
Average execution times show that greg achieves processing speeds comparable to standard high-performance search utilities.
- Parallel walk-and-search — worker threads handle both directory traversal and file searching simultaneously (work-stealing design)
- PCRE2 JIT — full regex with JIT compilation, same engine as ripgrep
- Smart-case — case-insensitive when pattern is all lowercase (like ripgrep)
- Slab-allocated queue — zero per-file
malloc/freeoverhead in the hot path - gitignore support — respects
.gitignoreand.ignorefiles with a lock-free, reference-counted ignore tree supporting standard*and multi-level**wildcards matched iteratively - mmap file reading — memory-mapped I/O with
MADV_SEQUENTIALfor large files - Buffered output — 1 MiB stdout buffer, single
fwriteper file result under mutex, printing no trailing newlines/blank space at the end of the results - JIT compilation — uses the compiled JIT execution path (
pcre2_jit_match) for maximum throughput
git clone <repo>cd greg
make
# Binary is ./gregDependencies:libpcre2-8 (dev package), a C99 compiler, pthreads.
# Debian/Ubuntu/Termux
apt install libpcre2-dev
# macOS
brew install pcre2greg [options] <pattern> [path]
| Flag | Long form | Description |
|---|---|---|
-i | --ignore-case | Search case-insensitively |
-S | --smart-case | Case-insensitive if pattern is all lowercase (default) |
-v | --invert-match | Select non-matching lines |
-F | --fixed-strings | Treat pattern as a literal string |
-n | --line-number | Always show line numbers |
-N | --no-line-number | Never show line numbers |
-l | --files-with-matches | Print only filenames containing matches |
-c | --count | Print match count per file |
-m NUM | --max-count NUM | Stop after NUM matches per file |
-j NUM | --threads NUM | Number of worker threads (default: CPU cores) |
-a | --text | Search binary files |
--raw | Machine-readable output format (<filepath>\n<line_num>:<match_start>:<match_end>\n using 1-based line numbers) | |
--no-ignore | Ignore .gitignore / .ignore rules | |
--follow | Follow symbolic links | |
--hidden | Search hidden files and directories | |
--heading | Group results by filename (default on TTY) | |
--no-heading | Disable grouped output | |
--color always|never | Control ANSI color output | |
-h | --help | Print help |
--version | Print version |
# Search for "TODO" in current directory
greg TODO
# Case-insensitive search
greg -i "error" /var/log
# Only filenames
greg -l "main" src/
# Fixed string (no regex)
greg -F "fn main()".# Limit to 5 matches per file
greg -m 5 "panic".# Search hidden files too
greg --hidden "secret"~main thread
└─ pushes root path as WORK_DIR onto shared queue
worker threads (N = CPU cores)
├─ pop WORK_DIR → opendir/readdir → push child files (WORK_FILE) + subdirs (WORK_DIR)
└─ pop WORK_FILE → mmap → PCRE2 JIT match → buffered output
Key design decisions:
- Slab allocator for queue nodes — 256 nodes per slab, recycled via free-list; no per-item
malloc - Reference-counted ignore tree — parent ignore contexts shared across threads with atomic refcount, no copies
- Iterative Wildcard Matcher — safe backtrack-based iterative glob wildcard matching (supporting
*and**), preventing stack overflows on complex nested directory patterns - Task tracking in queue — automatic termination when
active_tasksreaches zero; no explicit "deactivate" signal needed for the normal path
MIT