From 26ce1d98185f298a3e36b40f663f653edf963ec1 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 30 Aug 2026 21:50:24 +0200 Subject: [PATCH] Fastest way to read a text file: retarget net10.0, add async reads, re-run benchmark - Both projects net7.0 -> net10.0; Nullable enabled on the sample project. - BenchmarkDotNet 0.13.7 -> 0.15.8. - Test packages to current: Microsoft.NET.Test.Sdk 18.9.0, xunit 2.9.3, xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1. This clears NU1903 (Newtonsoft.Json 9.0.1 came in transitively through the old test SDK). - Primary constructor on WaysToReadATextFileInCsharp. - UseBufferedStreamObjectWithNoFileStreamBuffer moves to the FileStreamOptions overload. FileStreamOptions.BufferSize documents "0 or 1 means that buffering should be disabled"; the five-argument FileStream constructor documents its bufferSize as greater than zero and throws on zero. Same behaviour, but the section now stands on the API whose docs state what it teaches. - Three async reads with matching benchmarks and tests: UseFileReadAllTextAsync, UseFileReadLinesAsync, UseStreamReaderReadToEndAsync. The StreamReader one is built over a FileStream with FileOptions.Asynchronous rather than the path-taking constructor, which does not set that flag and so fakes async file access on the thread pool. - Benchmark file resolved with AppContext.BaseDirectory and copied to the output directory, replacing a six-level Directory.GetParent walk tied to one layout. --- .../FastestWayToReadATextFileInCsharp.csproj | 12 +++-- .../WaysToReadATextFileInCsharp.cs | 51 ++++++++++++++----- .../WaysToReadATextFileInCsharpBenchmark.cs | 14 ++++- .../Tests/Tests.csproj | 10 ++-- .../Tests/WaysToReadATextFileInCsharpTests.cs | 24 +++++++++ 5 files changed, 90 insertions(+), 21 deletions(-) diff --git a/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp.csproj b/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp.csproj index 381ff1cf6a..398911013c 100644 --- a/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp.csproj +++ b/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp.csproj @@ -2,13 +2,19 @@ Exe - net7.0 + net10.0 enable - disable + enable - + + + + + + PreserveNewest + diff --git a/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharp.cs b/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharp.cs index 6fb6df97db..35999a6413 100644 --- a/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharp.cs +++ b/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharp.cs @@ -3,14 +3,9 @@ namespace FastestWayToReadATextFileInCsharp; -public class WaysToReadATextFileInCsharp +public class WaysToReadATextFileInCsharp(string sampleFilePath) { - private readonly string _sampleFilePath; - - public WaysToReadATextFileInCsharp(string sampleFilePath) - { - _sampleFilePath = sampleFilePath; - } + private readonly string _sampleFilePath = sampleFilePath; public string UseFileReadAllLines() { @@ -132,11 +127,13 @@ public string UseBufferedStreamObjectWithNoFileStreamBuffer() { var stringBuilder = new StringBuilder(); - using var fileStream = new FileStream(_sampleFilePath, - FileMode.Open, - FileAccess.Read, - FileShare.Read, - 0); + using var fileStream = new FileStream(_sampleFilePath, new FileStreamOptions + { + Mode = FileMode.Open, + Access = FileAccess.Read, + Share = FileShare.Read, + BufferSize = 0 + }); using var bufferedStream = new BufferedStream(fileStream); using var streamReader = new StreamReader(bufferedStream); @@ -148,4 +145,34 @@ public string UseBufferedStreamObjectWithNoFileStreamBuffer() return stringBuilder.ToString(); } + + public async Task UseFileReadAllTextAsync() + => await File.ReadAllTextAsync(_sampleFilePath); + + public async Task UseFileReadLinesAsync() + { + var stringBuilder = new StringBuilder(); + + await foreach (var line in File.ReadLinesAsync(_sampleFilePath)) + { + stringBuilder.AppendLine(line); + } + stringBuilder.Length -= Environment.NewLine.Length; + + return stringBuilder.ToString(); + } + + public async Task UseStreamReaderReadToEndAsync() + { + using var fileStream = new FileStream(_sampleFilePath, new FileStreamOptions + { + Mode = FileMode.Open, + Access = FileAccess.Read, + Share = FileShare.Read, + Options = FileOptions.Asynchronous | FileOptions.SequentialScan + }); + using var streamReader = new StreamReader(fileStream); + + return await streamReader.ReadToEndAsync(); + } } \ No newline at end of file diff --git a/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharpBenchmark.cs b/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharpBenchmark.cs index cbe33d96c0..2a97cf921f 100644 --- a/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharpBenchmark.cs +++ b/files-csharp/FastestWayToReadATextFileInCsharp/FastestWayToReadATextFileInCsharp/WaysToReadATextFileInCsharpBenchmark.cs @@ -10,7 +10,7 @@ namespace FastestWayToReadATextFileInCsharp; public class WaysToReadATextFileInCsharpBenchmark { private static readonly string SampleFilePath - = Path.Combine(Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.Parent.Parent.Parent.Parent.FullName, "BenchmarkTextFile.txt"); + = Path.Combine(AppContext.BaseDirectory, "BenchmarkTextFile.txt"); private readonly WaysToReadATextFileInCsharp _textFileReader = new(SampleFilePath); @@ -53,4 +53,16 @@ public string BufferedStreamObject() [Benchmark] public string BufferedStreamWithNoFileStreamBuffer() => _textFileReader.UseBufferedStreamObjectWithNoFileStreamBuffer(); + + [Benchmark] + public async Task FileReadAllTextAsync() + => await _textFileReader.UseFileReadAllTextAsync(); + + [Benchmark] + public async Task FileReadLinesAsync() + => await _textFileReader.UseFileReadLinesAsync(); + + [Benchmark] + public async Task StreamReaderReadToEndAsync() + => await _textFileReader.UseStreamReaderReadToEndAsync(); } \ No newline at end of file diff --git a/files-csharp/FastestWayToReadATextFileInCsharp/Tests/Tests.csproj b/files-csharp/FastestWayToReadATextFileInCsharp/Tests/Tests.csproj index 5fced33ef9..6a1905ff23 100644 --- a/files-csharp/FastestWayToReadATextFileInCsharp/Tests/Tests.csproj +++ b/files-csharp/FastestWayToReadATextFileInCsharp/Tests/Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -9,13 +9,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/files-csharp/FastestWayToReadATextFileInCsharp/Tests/WaysToReadATextFileInCsharpTests.cs b/files-csharp/FastestWayToReadATextFileInCsharp/Tests/WaysToReadATextFileInCsharpTests.cs index df4632c518..9e27ec3bed 100644 --- a/files-csharp/FastestWayToReadATextFileInCsharp/Tests/WaysToReadATextFileInCsharpTests.cs +++ b/files-csharp/FastestWayToReadATextFileInCsharp/Tests/WaysToReadATextFileInCsharpTests.cs @@ -100,6 +100,30 @@ public void WhenReadingATextFileWithBufferedStreamObjectWithFileStreamBufferDisa Assert.Equal(ExpectedText, result); } + [Fact] + public async Task WhenReadingATextFileWithFileReadAllTextAsync_ThenReturnsExpectedText() + { + var result = await _textFileReader.UseFileReadAllTextAsync(); + + Assert.Equal(ExpectedText, result); + } + + [Fact] + public async Task WhenReadingATextFileWithFileReadLinesAsync_ThenReturnsExpectedText() + { + var result = await _textFileReader.UseFileReadLinesAsync(); + + Assert.Equal(ExpectedText, result); + } + + [Fact] + public async Task WhenReadingATextFileWithStreamReaderReadToEndAsync_ThenReturnsExpectedText() + { + var result = await _textFileReader.UseStreamReaderReadToEndAsync(); + + Assert.Equal(ExpectedText, result); + } + public void Dispose() { if (File.Exists(TempFilePath))