[Update, 16 October 2026] Speed today — complex searches went from 1.07–3.53× to 1.22–4.56×
The tables in this article are from v1.7.3.7 (4–5 October 2026) and the uvp 1.8.1.3 test build (6 October). The current release, v1.9.0 (14 October 2026), was measured on the same Mac.
ripgrep ÷ uvf/uvp(first + second run)This article (then) Now (v1.9.0) Three complex searches, kernel source, about 60,000 files ( uvp)1.87–3.53× 2.35–4.56× Three complex searches, one 1 GB file ( uvp)1.07–1.92× 1.22–2.13× Seven searches, 3 GB / 10 GB (free uvf)per item 1.05–1.24× / 1/1.01–1.15× overall 1.08× / 1.08× (on par) uvpfirst run (index build included, 3 GB plain string)1/1.05 about 1/1.3 (within on par) Over the seven searches
uvpis 3.44× (3 GB) and 3.55× (10 GB), index build included. The 86,000-file and Rust-rewrite tables were not part of this re-measurement. Details: Benchmarks and the v1.9.0 “Full Spectrum” article.
It was not the language.
ripgrep is written in Rust; my uvf and uvp are written in C# (.NET). Three weeks ago, a search on a 50 GB file took 205 s against ripgrep’s 56 s — nearly four times slower. I kept asking myself whether Rust was simply faster.
Here is the record of chasing ripgrep in three arenas, plus what happened when I rewrote the same design in Rust. Where the gap really came from becomes clear partway through section 2.
- The short answer: the gap was in how many times we read, how many times we opened files, and how we narrowed — not the language. After fixing those, the free uvf is on par with ripgrep and uvp, which keeps an index, is faster
- 1. Huge files: from 205 s to on par. What helped was how many times we read, and how
- 2. Many small files: from 19x behind to on par. What helped was how many times we opened files
- 3. Complex searches: ripgrep works line by line, uvp chains stages
- What I learned: the difference between Rust and C# was almost entirely a difference in design
- Being honest
- Conditions behind the numbers
- Links
The short answer: the gap was in how many times we read, how many times we opened files, and how we narrowed — not the language. After fixing those, the free uvf is on par with ripgrep and uvp, which keeps an index, is faster
Huge files (50 GB of OpenStreetMap XML): uvf went from 205 s to 50.82 s, on par with ripgrep (54.94–55.81 s). Many small files (the Linux kernel source, 86,000 files): the second run used to take 19.3 times as long as ripgrep’s; over eight searches, cold plus hot, it is now 1/1.08 — on par. The paid uvp is 4.0x. Complex searches (multi-line blocks, lines near each other, collecting into CSV): uvp is 1.07–3.53x ripgrep (with perl or awk added where ripgrep needs them). Against a feature-rich grep app for Windows, uvp was 8.6–43x faster, and even ripgrep was 7.9–17.9x faster than that app. Finally, a Rust rewrite with the same design ran at the same speed as the C# version (36.85 s vs 37.55 s), and the gap to ripgrep did not shrink. Mac (Apple M4, 10 cores), 32 GB RAM, external USB SSD. Details at the end.
uvfanduvpare the search commands of UwView, which I develop (uvfis the free edition,uvpbelongs to the paid UwView Pro). The current stable release is v1.7.3.6.9; v1.7.3.7 is a Mac-only preview. The complex searches in section 3 use features coming in the next release, v1.8 (ranges-range, masking-mask, formatting-format), measured on a test build (uvp 1.8.1.3). Versions and dates for each figure are listed at the end.
1. Huge files: from 205 s to on par. What helped was how many times we read, and how
Time to find 東京 (94,979 hits) in 50 GB of XML (890 million lines), each right after dropping the cache (cold).
| When / version | uvf |
ripgrep | Ratio |
|---|---|---|---|
| 2026-09-15, v1.6.0 | 205 s | 56 s | ripgrep 3.7x faster |
| Two days later | 50.82 s | 54.94–55.81 s | On par |
None of the fixes had anything to do with Rust versus C#.
- The file was being read two and a half times. Collecting line starts (about 100 s), searching (about 82 s), re-reading the matched lines (about 20 s). These became a single read
- Sequential reads through mmap used only 57% of the disk’s bandwidth. mmap gave 551 MB/s;
preadin 4 MB chunks gave 966 MB/s, level with a raw read (dd, 927 MB/s) - Counting newlines with SIMD did not save a single second when reading from disk. The disk was the bottleneck (it did pay off later, for the second run from cache — see 1.7.3.6.9 below)
Rust’s ripgrep is just as sensitive to how it reads. It reads large files through mmap by default, but on Windows 11, 50 GB with a regex went from 282.84 s to 97.83 s (2.89x) with --no-mmap (no change on macOS). How you read matters before which language you use.
The main changes after that:
- v1.7.3.6.7: regex plus ignore-case (
-E -i) went from 1/1.68 of ripgrep to 1/1.07. The anchor characters chosen were=and", which match almost every attribute in XML. Anchors are now chosen from letters - v1.7.3.6.9: the second run from cache edged past ripgrep (3 GB: 0.29 s vs 0.31 s; 10 GB: 0.88 s vs 0.97 s). Counting newlines 16 bytes at a time took 3 GB from 0.15 s to 0.028 s
- There was exactly one C#-specific issue. The shipped
uvfis NativeAOT (compiled ahead of time), where .NET regular expressions run interpreted. Only for heavy patterns that must be applied to every line,uvfhands the search to the GUI executable in the same folder, where the JIT works
Where things stand now (v1.7.3.7, seven searches, cold plus hot):
| One large file | uvf ratio |
uvp ratio |
|---|---|---|
| 3 GB | 1.05–1.24x (only -E -v 1/1.10) |
1/1.05–4.86x |
| 10 GB | 1/1.01–1.15x | 1/1.10–5.29x |
| 50 GB | 1.06–1.23x | 1.68–9.61x |
uvp‘s 1/1.05 and 1/1.10 are the first search, which builds the index (.uwvz). From the second search on, it reads the index.
2. Many small files: from 19x behind to on par. What helped was how many times we opened files
Having caught up on huge files, I relaxed — until the Linux kernel source (86,000 files, 1.9 GB) showed a completely different face.
| Version | uvf second run (PageTransHuge) |
ripgrep | Ratio |
|---|---|---|---|
| 1.7.3.6.5 | 26.58 s | 1.38 s | ripgrep 19.3x faster |
| 1.7.3.6.7 | 3.40 s | 1.44 s | ripgrep 2.4x faster |
The cause was not the search but how many times files were opened. Just opening and closing 86,000 files one by one takes 7.7 s. Before searching, the old version opened every file three times in sequence to check “is this a pbf?” and “is this compressed?”. Now only files whose name settles the format are checked up front; the rest are decided from the first bytes when the search opens them.
Next, five searches, cold plus hot combined:
| Version | uvf |
ripgrep | Ratio | What changed |
|---|---|---|---|---|
| 1.7.3.6.7 | 52.18 s | 31.50 s | 1/1.66 | Fewer opens (above) |
| 1.7.3.6.9 | 36.21 s | 30.22 s | 1/1.20 (on par) | Idle workers fetch the next file; files are opened by absolute path (just opening and reading with 8 workers: 2.33 s to 1.59 s); files of 256 KB or less are read in one go |
| 1.7.3.7 (eight searches) | 60.85 s | 56.25 s | 1/1.08 (on par) | — |
uvp took a longer road. Its first version (1.7.3.6) crashed trying to bundle 86,000 files into one index (the list of source files grew to 20.9 MB, over a 1 MB limit). With the limit fixed it could bundle them, but building the index took 56.31 s. It now takes 8.4 s (including the first search), and eight searches cold plus hot take 14.02 s — 4.0x ripgrep.
Rewriting it in Rust
This is where I tested what I most wanted to know: would the same design, written in Rust, catch ripgrep?
I built two Rust versions of uvf‘s search, called ruvf: port, a one-to-one translation, and idiom, which hands the searching to Rust’s regex crate. After confirming that their output matches uvf byte for byte, I measured them on the 86,000-file kernel tree with five searches (cold plus hot, median of three).
| ripgrep | uvf (C#) |
ruvf-idiom (Rust) | ruvf-port (Rust) |
|---|---|---|---|
| 30.62 s | 37.55 s | 36.85 s | 42.89 s |
The Rust version ran at the same speed as the C# version (idiom vs uvf: 1.02x). The gap to ripgrep (uvf at 1.23x ripgrep’s time) did not shrink in Rust. port is slower because it copied uvf‘s habit of rebuilding the regex for every file (Rust’s regex is heavier to build than .NET’s); building it once makes it match idiom. Startup is 1.7 ms for ruvf, 2.7 ms for ripgrep and 4.7 ms for uvf — about 3 ms apart.
The gap was in the design, not the language. My working theory: ripgrep starts searching while it walks the directories, whereas uvf finishes walking first so it can print files in name order. That can be fixed without leaving C#.
There was one place where Rust was clearly faster: regular expressions with no required literal string to extract (2–5x on 200 MB). The reasons: .NET regex runs interpreted under NativeAOT, and Rust’s regex narrows lines first even with a single character or a byte sequence. Adding the second to the C# uvf made those patterns 2.3–6.1x faster; [0-9]{4}-[0-9]{2} now matches ripgrep (1.34 s vs 1.33 s) and [ぁ-ん]{3,} beats the Rust version (0.45 s vs 1.17 s). What I learned from Rust was not the language but the way to narrow.
The full details — startup times, all ten regex patterns, and a case where writing it “the Rust way” was slower — are in a Qiita article (Japanese): Rust で書き直せば ripgrep に並ぶのか
3. Complex searches: ripgrep works line by line, uvp chains stages
The last arena is three searches presented as typical uses of a feature-rich grep app for Windows. None can be written as a single-line match.
- Example 1: take the multi-line blocks from
[ERROR] SessionStartto[INFO] SessionEnd, keep only those that do not containError_Code: 503, and mask the IP addresses inside asx.x.x.x - Example 2: places where
DatabaseTimeoutandUser_ID: 9999appear within 10 lines, with noIgnoredWarning - Example 3: pull User, Action and Time out of lines where they come in any order, and collect them as CSV
With ripgrep, example 1 is a multi-line rg -U -P regex piped into perl, example 2 collects lines with rg and checks nearness with awk, and example 3 is rg -P -o -r '$1,$2,$3'. From v1.8, uvp writes each as one command.
# Example 1: write the session blocks without 503, with IPs masked
uvp app.log -seq '[ERROR] SessionStart,[INFO] SessionEnd' -range \
-grep 'Error_Code: 503' -v \
-mask '\b(\d{1,3}\.){3}\d{1,3}\b' 'x.x.x.x'
# Example 3: extract User, Action and Time into CSV (any order)
uvp '*.log' '^(?=.*User: (\S+))(?=.*Action: (\S+))(?=.*Time: (.+?)\s*$)' -E \
-format '$1,$2,$3' --csv --csv-header 'name,action,time' -out result.csv
Two arenas: kernel is the kernel’s *.c and *.h files (about 60,000 files, 1.8 GB) with example snippets inserted into about 6% of them; big is a single 1 GB log. Cold plus hot combined; uvp and ripgrep produced the same results every time.
| Arena | Example | Mac: ripgrep ÷ uvp | Windows: ripgrep ÷ uvp | Windows: grep app ÷ uvp |
|---|---|---|---|---|
| kernel | Ex. 1 range, exclude, mask | 1.87 | 1.85 | 20.26 |
| kernel | Ex. 2 nearby, AND, NOT | 2.14 | 1.65 | 22.92 |
| kernel | Ex. 3 collect to CSV | 3.53 | 2.39 | 42.94 |
| big | Ex. 1 | 1.07 | 1/1.03 | 9.07 |
| big | Ex. 2 | 1.46 | 1.09 | 8.62 |
| big | Ex. 3 | 1.92 | 2.27 | 38.50 |
(Above 1 means uvp is faster. 1/1.03 means uvp takes 1.03 times as long — on par.)
For speed, the feature-rich grep app was not usable. It runs on Windows only, and its trial cannot write results, so only time was measured. It was also asked to do only the search with the same pattern — no collecting, masking or excluding. Even so it took 8.6–43 times as long as uvp and 7.9–17.9 times as long as ripgrep, and a single kernel example took two to three minutes cold. GUI conveniences such as checking replacements on screen were not part of this comparison.
ripgrep really is fast. Even with perl or awk attached, the gap to uvp stays within 1.1–3.5x. uvp comes out ahead because its index shortens the second read and it runs the stages (range, exclude, mask, format) in one pass.
To be honest: looking at the second (hot) run alone, uvp is 1/1.2 and 1/1.1 on the Mac kernel examples 1 and 2 (on par), because opening the index adds a fixed cost. On Windows, the first kernel run (including building the index) takes 65–74 s, against 6–9 s on the Mac. The cause has not been investigated yet.
What I learned: the difference between Rust and C# was almost entirely a difference in design
- Speed was decided by how many times we read, how many times we opened files, and how we narrowed. Reads for huge files, opens for small files, and narrowing clues for regex. All of them were fixable without changing language
- The only C#-specific issue was interpreted regex under NativeAOT. Handing heavy patterns to the JIT executable and narrowing with clues first left no gap
- Do not assume “it’s fast because it’s Rust”. Rust’s ripgrep itself varies 2.89x depending on how it reads (mmap or pread)
- For a one-off search, ripgrep is still the best tool.
uvp, with its index, comes out ahead when you ask the same place many times and when you chain stages for complex searches
Being honest
- On small files,
uvf‘s second run is still slower (1/1.4 of ripgrep on the kernel tree; about 2.1 s of fixed cost for opening each file). Cold plus hot it is on par (1/1.08) uvp‘s first pass when bundling many files is slow (12 mixed plain and gz files: 24.95 s vs 39.78 s — ripgrep 1.59x faster)uvp‘s second run carries a fixed cost of about 0.12 s (one XML split into 1,000 files: 0.043 s vs 0.126 s — ripgrep 2.93x faster)- The
uvpin section 3 is a test build (1.8.1.3); ranges, masking and formatting are planned for v1.8 - ruvf is an experiment for comparison and will not be distributed
- Timings vary between environments. Do not compare seconds across operating systems
Conditions behind the numbers
- Mac (Apple M4, 10 cores, 32 GB RAM), external USB SSD. The Windows columns come from a different machine (Windows 11, x64, 8 logical cores, 16 GB RAM)
- Cold = first run after
sudo purgeand a 10 s wait; hot = the run straight after. Ratio = ripgrep’s time ÷ ours (cold plus hot ratios use the combined time) - Section 1: v1.6.0 and two days later, 15–17 September 2026 (cold, uvf). ripgrep mmap: September 2026 (ripgrep 15.2.0). Table: v1.7.3.7 (4–5 October 2026)
- Section 2: 1.7.3.6.5 and 1.7.3.6.7 on 2–3 October 2026, 1.7.3.6.9 on 4 October, eight searches on 1.7.3.7 (5 October). ruvf on 4 October (rustc 1.99.0, regex 1.13.1, memchr 2.8.3; uvf 1.7.3.6.9)
- Section 3: uvp 1.8.1.3 (test build), ripgrep 15.2.0, 6 October 2026. The grep app was a trial version, timed only
- Every item’s output was compared with ripgrep (or sed and similar): zero mismatches
Links
- Counting “file opens” on the 86,000-file kernel tree (Zenn, Japanese): https://zenn.dev/amru195704/articles/5f2a6203ec7f61
- ruvf (the Rust rewrite) vs uvf and ripgrep (Qiita, Japanese): https://qiita.com/amru195704/items/149c10ff2be8c82c9bb7
- ripgrep’s mmap vs
--no-mmapon 50 GB across three OSes: https://uvp.y42u.net/en/blog/uvp-rg-no-mmap-50gb-en/ - Counting newlines took 40% of the second search: https://uvp.y42u.net/en/blog/uwview-wf12-newline-hot-en/
- Regex compared (what .NET and Rust can and cannot write): https://uvp.y42u.net/en/blog/uwview-wf09-regex-vs-ripgrep-en/
- Every place where ripgrep still wins: https://uvp.y42u.net/en/blog/uwview-wf14-where-ripgrep-wins-en/
- First Light recap (only cutting the number of reads): https://uvp.y42u.net/en/blog/uwview-fl13-first-light-recap-en/
- All measured data (benchmarks): https://uvp.y42u.net/en/benchmarks-en/
- Source code (GitHub): https://github.com/amru195704/UwView
If you ask the same logs again and again, try UwView Pro with its index. Searches from the second one on get a step faster, and logs are stored at about 1/9 the size (all OSes, one-time or monthly plans, 14-day free trial).
From the developer: apps, Kindle books and public projects are listed at GitHub: amru195704.

