fd vs find vs ripgrep: I Created 10,000 Files to Settle This Debate

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5168

    #1

    fd vs find vs ripgrep: I Created 10,000 Files to Settle This Debate

    fd vs find vs ripgrep: I Created 10,000 Files to Settle This Debate

    TL;DR: fd is ~2.5x faster than find for filename searches, rg demolishes grep by ~3x for content searches, and find + grep combined lose on every single benchmark I ran. But there's a catch: both fd and rg skip hidden files by default, which can bite you if you're not paying attention. Here are the receipts.





    Why I Did This

    Every time someone posts a shell one-liner using find on Reddit, there's always that guy in the comments: "jUsT uSe fD, iT's fAsTeR." Then someone else chimes in with "actually ripgrep can do that too."


    I got tired of the anecdotes. I wanted numbers. Real ones. On real files. So I fired up WSL, generated 10,900 files across 1,506 directories (~143 MB of mixed content), and ran actual benchmarks with hyperfine. No synthetic microbenchmarks, no "I feel like X is faster" — just cold, hard terminal output.





    Methodology

    The Test Bed

    I created a directory at /tmp/fd-benchmark containing:


    Plain text files 2,000 file_*.txt — 20 bytes each, contains "test content line N"
    Binary files 2,000 data_*.bin — 15 bytes each
    Log files 1,500 match_*.log — contains unique "match_this_test_N" strings
    Config files 1,000 nested_file_*.cfg
    Nested dir files 1,000 level1_*/level2/level3/deep_*.txt + level1_*/shallow_*.txt
    Hidden root files 1,500 .hidden_* + .config_*.yml
    Hidden dir files 500 .hidden_dir/subdir/deep_hidden_*.txt
    Git objects 500 .git/objects/obj_*
    Multi-ext source files 800 src_*.{py,js,ts,rs,go,java,rb,php,cpp,h,css,html,j son,xml,yaml,md} (50 each)
    Large binary files 100 large_*.dat — 1 MB each (random data)
    Total 10,900






    $ du -sh .
    143M .

    $ find . -type f | wc -l
    10900

    $ find . -type d | wc -l
    1506







    Tools Tested

    find (GNU) 4.9.0 The OG. Ships with every Linux distro.
    fd 10.2.0 Rust-based find alternative. Smarter defaults, colored output.
    grep (GNU) 3.11 Content search. Also the OG.
    rg (ripgrep) 15.1.0 Rust-based grep alternative. Respects .gitignore, crazy fast.


    All benchmarks run with hyperfine --warmup 3 --runs 10 on WSL2 Ubuntu, Intel i7, NVMe SSD.





    Benchmark 1: Simple Filename Search (Find All *.txt Files)

    This is the most common use case. You want every .txt file in a project, recursively.


    What I Ran





    # GNU find
    find . -name '*.txt'

    # fd (extension filter)
    fd -e txt

    # ripgrep listing all files, piped to grep
    rg --files | grep '\.txt$'







    Results





    Benchmark 1: find . -name '*.txt'
    Time (mean ± σ): 94.5 ms ± 17.6 ms [User: 21.1 ms, System: 26.1 ms]
    Range (min … max): 74.8 ms … 139.9 ms 10 runs

    Benchmark 1: fd -e txt
    Time (mean ± σ): 39.9 ms ± 8.2 ms [User: 70.7 ms, System: 134.3 ms]
    Range (min … max): 30.8 ms … 55.3 ms 10 runs

    Benchmark 1: rg --files | grep '\.txt$'
    Time (mean ± σ): 117.6 ms ± 12.8 ms [User: 51.5 ms, System: 106.0 ms]
    Range (min … max): 104.8 ms … 144.0 ms 10 runs







    fd -e txt 39.9 ms 🏆 2.37x faster
    find . -name '*.txt' 94.5 ms baseline
    `rg --files \ grep` 117.6 ms


    ⚠️ The Hidden Files Gotcha

    Here's something the benchmarks don't tell you — fd and find returned different results.






    $ find . -name '*.txt' | wc -l
    3500

    $ fd -e txt | wc -l
    3000







    fd skipped 500 .txt files. Why? Because those 500 files live inside .hidden_dir/, and fd ignores hidden directories by default. It's not a bug — it's intentional. If you want parity with find, you need the -H flag:






    $ fd -H -e txt | wc -l
    3500







    This is either genius or infuriating depending on whether you knew about it before debugging for 20 minutes. (I was debugging for 20 minutes.)


    Winner: fd — but learn the -H flag or it'll silently miss files.





    Benchmark 2: Content Search (Find Files Containing "test")

    You need to know which files reference a string. Classic grep territory.


    What I Ran





    # GNU grep (excluding .git manually, suppressing permission errors)
    grep -r 'test' . --exclude-dir=.git 2>/dev/null

    # ripgrep (auto-excludes .git, hidden dirs, binaries)
    rg 'test'







    Results





    Benchmark 1: grep -r 'test' . --exclude-dir=.git 2>/dev/null
    Time (mean ± σ): 302.7 ms ± 50.8 ms [User: 92.8 ms, System: 100.2 ms]
    Range (min … max): 228.6 ms … 370.8 ms 10 runs

    Benchmark 1: rg 'test'
    Time (mean ± σ): 103.2 ms ± 6.8 ms [User: 135.3 ms, System: 177.5 ms]
    Range (min … max): 95.3 ms … 116.0 ms 10 runs







    rg 'test' 103.2 ms 🏆 2.93x faster
    grep -r 'test' 302.7 ms baseline


    And look at that consistency — rg has a standard deviation of 6.8ms vs grep's 50.8ms. rg is not just faster, it's predictably faster.


    Both returned exactly 3,500 matches:






    $ grep -r 'test' . --exclude-dir=.git 2>/dev/null | wc -l
    3500

    $ rg 'test' | wc -l
    3500







    Unlike the filename search test, there's no hidden-file discrepancy here because our "test" string only appears in non-hidden files. But rg is still skipping hidden dirs and .git by default — it just happened to not matter in this case.


    Winner: rg — not even close. 3x faster with zero tuning.





    Benchmark 3: Finding Hidden Files

    Sometimes you need to find all the dotfiles and dotdirs. Let's see who handles this best.


    What I Ran





    # GNU find — no special flags needed, it sees everything
    find . -name '.*' -type f

    # fd — needs -H to include hidden stuff, regex anchor for dot-prefix
    fd -H -t f '^\.'

    # ripgrep — needs --hidden to see dotfiles
    rg --hidden --files | grep '^\.'







    Results





    Benchmark 1: find . -name '.*' -type f
    Time (mean ± σ): 104.3 ms ± 20.5 ms [User: 16.0 ms, System: 27.6 ms]
    Range (min … max): 79.0 ms … 144.5 ms 10 runs

    Benchmark 1: fd -H -t f '^\.'
    Time (mean ± σ): 40.5 ms ± 9.2 ms [User: 56.9 ms, System: 106.0 ms]
    Range (min … max): 26.6 ms … 57.2 ms 10 runs

    Benchmark 1: rg --hidden --files | grep '^\.'
    Time (mean ± σ): 118.8 ms ± 9.4 ms [User: 64.4 ms, System: 93.3 ms]
    Range (min … max): 106.9 ms … 135.0 ms 10 runs







    fd -H -t f '^\.' 40.5 ms 🏆 2.58x faster
    find . -name '.*' -type f 104.3 ms baseline
    `rg --hidden --files \ grep` 118.8 ms


    All three found exactly 1,500 hidden files (we excluded .git/ objects from this count).






    $ find . -name '.*' -type f -not -path './.git/*' | wc -l
    1500

    $ fd -H -t f '^\.' | wc -l
    1500







    Winner: fd — once you remember -H.





    Benchmark 4: Ignoring .git Directories

    This is one of fd and rg's marquee features — they auto-ignore .git, .svn, and anything in .gitignore. With find, you have to do it manually.


    What I Ran





    # GNU find — manual .git exclusion
    find . -not -path './.git/*' -type f | wc -l

    # fd — auto-ignores .git AND hidden dirs
    fd -t f | wc -l

    # ripgrep — auto-ignores .git AND hidden dirs
    rg --files | wc -l







    Results





    Benchmark 1: find . -not -path './.git/*' -type f | wc -l
    Time (mean ± σ): 139.2 ms ± 16.5 ms [User: 27.9 ms, System: 37.6 ms]
    Range (min … max): 119.4 ms … 173.4 ms 10 runs

    Benchmark 1: fd -t f | wc -l
    Time (mean ± σ): 85.4 ms ± 10.2 ms [User: 82.9 ms, System: 118.2 ms]
    Range (min … max): 69.2 ms … 103.9 ms 10 runs

    Benchmark 1: rg --files | wc -l
    Time (mean ± σ): 129.0 ms ± 12.2 ms [User: 60.7 ms, System: 106.3 ms]
    Range (min … max): 111.4 ms … 151.3 ms 10 runs







    `fd -t f \ wc -l` 85.4 ms
    `rg --files \ wc -l` 129.0 ms
    `find . -not -path './.git/*' \ wc -l` 139.2 ms


    But Again: The Counts Don't Match





    $ find . -not -path './.git/*' -type f | wc -l
    10400

    $ fd -t f | wc -l
    8400







    That's a 2,000-file discrepancy. Here's the breakdown:
    • 10,900 total files
    • Minus 500 .git/ objects = 10,400 (what find reports with -not -path)
    • Minus 2,000 hidden files/dirs = 8,400 (what fd reports by default)


    fd and rg don't just skip .git — they skip all hidden files and directories. This is usually what you want (who searches node_modules/.cache/?), but it's worth knowing.






    $ fd -H -t f | wc -l # with hidden files
    10900







    Winner: fd — but understand what "ignore" actually means.





    Bonus Tests

    Finding Files by Path Pattern





    $ time find . -path '*deep*' -type f | wc -l
    1000
    real 0m0.088s

    $ time fd 'deep' -t f | wc -l
    500
    real 0m0.094s







    Again, fd found half as many because .hidden_dir/subdir/deep_hidden_*.txt (500 files) is in a hidden directory. With -H:






    $ fd -H 'deep' -t f | wc -l
    1000







    Simple File Listing Speed

    When neither tool is filtering (just "list everything"), the performance gap narrows:






    $ time find . -type f | wc -l
    10900
    real 0m0.110s

    $ time fd -t f | wc -l
    8400
    real 0m0.116s







    At raw directory traversal, they're basically tied. fd's advantage comes from its smarter filtering and parallelism, not from faster syscalls.





    The Scorecard

    Filename search (*.txt) fd 2.37x over find
    Content search (test) rg 2.93x over grep
    Hidden file search fd 2.58x over find
    .git exclusion + listing fd 1.63x over find
    Raw listing (no filter) Tie ~identical


    Overall winner: fd + rg as a combo. Replace find with fd, replace grep with rg. You'll never look back.





    The Real Talk: When to Use What

    Use fd when:

    • You're doing any filename-based search (replace find . -name)
    • You want smart defaults that skip .git, node_modules, hidden dirs
    • You want colored, readable output out of the box
    • You're typing into an interactive shell and want the shorter syntax


    Use find when:

    • You're writing a portable script that needs to run on any Linux/Unix
    • You need the insane flexibility of -exec with complex predicates
    • You're on a machine where you can't install Rust tooling (embedded, minimal containers)
    • You need to search hidden files without remembering a flag


    Use rg when:

    • You're searching file contents (this is what it was built for)
    • You want .gitignore-aware searching (no more grepping through node_modules)
    • You need speed — it's SIMD-accelerated and uses memory mapping


    Use grep when:

    • You need POSIX compatibility in scripts
    • You're piping from stdin with complex flags (grep -oP with Perl regex is still great)
    • rg isn't installed and you can't install it


    The Pipe Combo Pattern (don't do this)

    I see this pattern a lot:






    rg --files | grep '\.ts$' | xargs rg 'useState'







    This is slow and redundant. Both fd and rg can filter by extension natively:






    # Instead of the pipe combo:
    fd -e ts -x rg 'useState'

    # Or even better:
    rg --type ts 'useState'







    One command. No pipes. Faster.





    What I Learned

    1. fd is genuinely faster for filename searches — not by 10x like some blog posts claim, but a solid 2-2.5x in real-world use. That's worth the install.
    2. rg vs grep isn't even a contest — 3x faster with better defaults. If you take one thing from this article: alias grep=rg.
    3. Hidden files will bite you. Both fd and rg skip them by default. This is documented, but the first time you spend 30 minutes wondering why your .env file isn't showing up, you'll remember to add -H / --hidden.
    4. The ergonomics matter more than the speed. Even if fd were the same speed as find, I'd still use it. fd whatever is just nicer than find . -name '*whatever*'. The speed is gravy.
    5. hyperfine is a beautiful benchmarking tool. If you're ever debating tool performance, just run the damn benchmark instead of arguing on Hacker News.





    Reproduce This Yourself





    # Install the tools
    cargo install fd-find ripgrep hyperfine
    # or: apt install fd-find ripgrep hyperfine

    # Clone my setup (approximate recreation):
    mkdir -p /tmp/fd-benchmark && cd /tmp/fd-benchmark
    for i in $(seq 1 2000); do echo "test content line $i" > "file_${i}.txt"; done
    for i in $(seq 1 1000); do echo "hidden content $i" > ".hidden_${i}"; done
    mkdir -p .hidden_dir/subdir
    for i in $(seq 1 500); do echo "deep hidden $i" > ".hidden_dir/subdir/deep_hidden_${i}.txt"; done
    mkdir -p .git/objects
    for i in $(seq 1 500); do echo "git object $i" > ".git/objects/obj_${i}"; done
    # ... etc. Full generation script in the article source.

    # Run the benchmarks
    hyperfine -w 3 -r 10 "find . -name '*.txt'" "fd -e txt"
    hyperfine -w 3 -r 10 "grep -r 'test' . --exclude-dir=.git" "rg 'test'"










    Last updated: May 2026. Tools tested: find 4.9.0, fd 10.2.0, grep 3.11, rg 15.1.0. Environment: WSL2 Ubuntu on NVMe SSD.




    More...
Working...