Skip to content

Instantly share code, notes, and snippets.

@RikkiGibson
Last active February 26, 2026 06:02
Show Gist options
  • Select an option

  • Save RikkiGibson/3c61e2f3ba6700268966778f4394745b to your computer and use it in GitHub Desktop.

Select an option

Save RikkiGibson/3c61e2f3ba6700268966778f4394745b to your computer and use it in GitHub Desktop.
Directory.EnumerateFiles perf
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<FindFirstFileBenchmark>();
[MemoryDiagnoser]
public partial class FindFirstFileBenchmark
{
[Params(@"F:\src\roslyn")]
public string BasePath { get; set; } = "";
[Params(@"F:\src\roslyn\artifacts\bin\Microsoft.Build.Tasks.CodeAnalysis\Debug\net10.0\cs\")]
public string SearchPath { get; set; } = "";
// ───────────────────────────── Managed ─────────────────────────────
[Benchmark(Baseline = true)]
public bool DirectoryEnumerateFiles()
{
var searchPath = SearchPath;
while (searchPath != BasePath && searchPath is { })
{
if (Directory.EnumerateFiles(searchPath, "*.docx").Any())
return true;
searchPath = Path.GetDirectoryName(searchPath);
}
return false;
}
// ──────────────────────────── Native ────────────────────────────
[Benchmark]
public unsafe bool FindFirstFileW()
{
var searchPath = SearchPath;
while (searchPath != BasePath && searchPath is { })
{
if (find(searchPath))
return true;
searchPath = Path.GetDirectoryName(searchPath);
}
return false;
bool find(string searchPath)
{
string searchPattern = Path.Combine(searchPath, "*.docx");
WIN32_FIND_DATAW findData;
var handle = NativeMethods.FindFirstFileW(searchPattern, &findData);
if (handle == new nint(-1))
return false;
NativeMethods.FindClose(handle);
return true;
}
}
}
// ═══════════════════════════════ P/Invoke ═══════════════════════════════
internal static partial class NativeMethods
{
[LibraryImport("kernel32.dll", EntryPoint = "FindFirstFileW", StringMarshalling = StringMarshalling.Utf16)]
internal static unsafe partial nint FindFirstFileW(string lpFileName, WIN32_FIND_DATAW* lpFindFileData);
[LibraryImport("kernel32.dll", EntryPoint = "FindNextFileW", StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool FindNextFileW(nint hFindFile, WIN32_FIND_DATAW* lpFindFileData);
[LibraryImport("kernel32.dll", EntryPoint = "FindClose")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool FindClose(nint hFindFile);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal unsafe struct WIN32_FIND_DATAW
{
public FileAttributes dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
public fixed char cFileName[260];
public fixed char cAlternateFileName[14];
}
// * Summary *
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26200.7840)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 10.0.103
[Host] : .NET 10.0.3 (10.0.326.7603), X64 RyuJIT AVX2
DefaultJob : .NET 10.0.3 (10.0.326.7603), X64 RyuJIT AVX2
| Method | BasePath | SearchPath | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------------------------ |---------------- |------------------------ |---------:|--------:|---------:|------:|--------:|-------:|----------:|------------:|
| DirectoryEnumerateFiles | F:\\src\\roslyn | F:\\sr(...)0\\cs\\ [80] | 227.7 us | 2.02 us | 1.89 us | 1.00 | 0.01 | 0.2441 | 3.38 KB | 1.00 |
| FindFirstFileW | F:\\src\\roslyn | F:\\sr(...)0\\cs\\ [80] | 383.3 us | 7.60 us | 11.37 us | 1.68 | 0.05 | - | 1.94 KB | 0.57 |
// * Legends *
BasePath : Value of the 'BasePath' parameter
SearchPath : Value of the 'SearchPath' parameter
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
Ratio : Mean of the ratio distribution ([Current]/[Baseline])
RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline])
Gen0 : GC Generation 0 collects per 1000 operations
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
Alloc Ratio : Allocated memory ratio distribution ([Current]/[Baseline])
1 us : 1 Microsecond (0.000001 sec)
// * Diagnostic Output - MemoryDiagnoser *
// ***** BenchmarkRunner: End *****
Run time: 00:00:56 (56.9 sec), executed benchmarks: 2
Global total time: 00:01:03 (63.12 sec), executed benchmarks: 2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment