1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+ // See the LICENSE file in the project root for more information.
4+
5+ using System . Buffers ;
6+ using System . Collections . Generic ;
7+ using System . Linq ;
8+ using System . Runtime . CompilerServices ;
9+ using System . Runtime . InteropServices ;
10+ using System . Threading ;
11+ using System . Threading . Tasks ;
12+ using BenchmarkDotNet . Attributes ;
13+ using BenchmarkDotNet . Extensions ;
14+ using MicroBenchmarks ;
15+ using Microsoft . Win32 . SafeHandles ;
16+
17+ namespace System . IO . Tests
18+ {
19+ [ BenchmarkCategory ( Categories . Libraries , Categories . NoWASM ) ]
20+ [ OperatingSystemsFilter ( allowed : true , platforms : OS . Windows ) ] // NO_BUFFERING is supported only on Windows: https://github.com/dotnet/runtime/issues/27408
21+ public class Perf_RandomAccess_NoBuffering
22+ {
23+ private const int OneKibibyte = 1 << 10 ; // 1024
24+ private const int OneMibibyte = OneKibibyte << 10 ;
25+ private const int HundredMibibytes = OneMibibyte * 100 ;
26+ private const FileOptions NoBuffering = ( FileOptions ) 0x20000000 ;
27+ private const FileOptions AsyncNoBuffering = FileOptions . Asynchronous | NoBuffering ;
28+
29+ static int PageSize = Environment . SystemPageSize ;
30+
31+ private Dictionary < long , string > _sourceFilePaths , _destinationFilePaths ;
32+ private Dictionary < int , AlignedMemory [ ] > _countToAlignedMemory ;
33+ private Dictionary < int , System . Memory < byte > [ ] > _countToMemory ;
34+ private Dictionary < int , System . ReadOnlyMemory < byte > [ ] > _countToReadOnlyMemory ;
35+
36+ private void Setup ( params long [ ] fileSizes )
37+ {
38+ _countToAlignedMemory = new Dictionary < int , AlignedMemory [ ] > ( )
39+ {
40+ { 4 , Enumerable . Range ( 0 , 4 ) . Select ( _ => AlignedMemory . Allocate ( ( nuint ) PageSize , ( nuint ) PageSize ) ) . ToArray ( ) } ,
41+ { 16 , Enumerable . Range ( 0 , 16 ) . Select ( _ => AlignedMemory . Allocate ( ( nuint ) PageSize , ( nuint ) PageSize ) ) . ToArray ( ) } ,
42+ } ;
43+ _countToMemory = _countToAlignedMemory . ToDictionary ( pair => pair . Key , pair => pair . Value . Select ( aligned => aligned . Memory ) . ToArray ( ) ) ;
44+ _countToReadOnlyMemory = _countToAlignedMemory . ToDictionary ( pair => pair . Key , pair => pair . Value . Select ( aligned => ( System . ReadOnlyMemory < byte > ) aligned . Memory ) . ToArray ( ) ) ;
45+
46+ _sourceFilePaths = fileSizes . ToDictionary ( size => size , size => CreateFileWithRandomContent ( size ) ) ;
47+ _destinationFilePaths = fileSizes . ToDictionary ( size => size , size => CreateFileWithRandomContent ( size ) ) ;
48+
49+ static string CreateFileWithRandomContent ( long fileSize )
50+ {
51+ string filePath = FileUtils . GetTestFilePath ( ) ;
52+ File . WriteAllBytes ( filePath , ValuesGenerator . Array < byte > ( ( int ) fileSize ) ) ;
53+ return filePath ;
54+ }
55+ }
56+
57+ [ GlobalCleanup ]
58+ public void Cleanup ( )
59+ {
60+ foreach ( string filePath in _sourceFilePaths . Values . Concat ( _destinationFilePaths . Values ) )
61+ {
62+ File . Delete ( filePath ) ;
63+ }
64+ foreach ( IDisposable alignedMemory in _countToAlignedMemory . Values . SelectMany ( buffers => buffers ) )
65+ {
66+ alignedMemory . Dispose ( ) ;
67+ }
68+ }
69+
70+ [ GlobalSetup ( Targets = new [ ] { nameof ( ReadScatterAsync ) , nameof ( WriteGatherAsync ) } ) ]
71+ public void SetupBigFileBenchmarks ( ) => Setup ( OneMibibyte , HundredMibibytes ) ;
72+
73+ public IEnumerable < object [ ] > ReadWrite_MultipleBuffers_Arguments ( )
74+ {
75+ yield return new object [ ] { OneMibibyte , 4 } ; // medium size file, 4xpage size user buffers
76+ yield return new object [ ] { OneMibibyte , 16 } ; // medium size file, 16xpage size user buffers
77+ yield return new object [ ] { HundredMibibytes , 4 } ; // big file, 4xpage size user buffers
78+ yield return new object [ ] { HundredMibibytes , 16 } ; // big file, 16xpage size user buffers
79+ }
80+
81+ [ Benchmark ]
82+ [ ArgumentsSource ( nameof ( ReadWrite_MultipleBuffers_Arguments ) ) ]
83+ public async Task < long > ReadScatterAsync ( long fileSize , int count )
84+ {
85+ CancellationToken cancellationToken = CancellationToken . None ;
86+ System . Memory < byte > [ ] buffers = _countToMemory [ count ] ;
87+ long bytesRead = 0 ;
88+ using ( SafeFileHandle fileHandle = File . OpenHandle ( _sourceFilePaths [ fileSize ] , FileMode . Open , FileAccess . Read , FileShare . Read , AsyncNoBuffering ) )
89+ {
90+ while ( bytesRead < fileSize )
91+ {
92+ bytesRead += await RandomAccess . ReadAsync ( fileHandle , buffers , bytesRead , cancellationToken ) ;
93+ }
94+ }
95+
96+ return bytesRead ;
97+ }
98+
99+ [ Benchmark ]
100+ [ ArgumentsSource ( nameof ( ReadWrite_MultipleBuffers_Arguments ) ) ]
101+ public async Task WriteGatherAsync ( long fileSize , int count )
102+ {
103+ CancellationToken cancellationToken = CancellationToken . None ;
104+ System . ReadOnlyMemory < byte > [ ] buffers = _countToReadOnlyMemory [ count ] ;
105+ long buffersSize = count * PageSize ;
106+ using ( SafeFileHandle fileHandle = File . OpenHandle ( _destinationFilePaths [ fileSize ] , FileMode . Create , FileAccess . Write , FileShare . Read , AsyncNoBuffering ) )
107+ {
108+ long bytesWritten = 0 ;
109+ for ( int i = 0 ; i < fileSize / buffersSize ; i ++ )
110+ {
111+ await RandomAccess . WriteAsync ( fileHandle , buffers , bytesWritten , cancellationToken ) ;
112+ bytesWritten += buffersSize ;
113+ }
114+ }
115+ }
116+ }
117+ }
0 commit comments