Skip to content

Commit b9994e9

Browse files
authored
NO_BUFFERING benchmarks (#1967)
1 parent 55bda82 commit b9994e9

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed

src/benchmarks/micro/MicroBenchmarks.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@
111111

112112
<ItemGroup Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' Or ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And '$(_TargetFrameworkVersionWithoutV)' &lt; '6.0')">
113113
<Compile Remove="libraries\System.Collections\PriorityQueue\Perf_PriorityQueue.cs" />
114+
<Compile Remove="libraries\Common\AlignedMemory.cs" />
114115
<Compile Remove="libraries\System.IO.FileSystem\Perf.RandomAccess.cs" />
116+
<Compile Remove="libraries\System.IO.FileSystem\Perf.RandomAccess.NoBuffering.cs" />
115117
</ItemGroup>
116118

117119
<ItemGroup>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.Memory;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using System.Threading;
10+
11+
namespace System
12+
{
13+
internal sealed class AlignedMemory : MemoryManager<byte>
14+
{
15+
private bool _disposed;
16+
private int _refCount;
17+
private IntPtr _memory;
18+
private int _length;
19+
20+
private unsafe AlignedMemory(void* memory, int length)
21+
{
22+
_memory = (IntPtr)memory;
23+
_length = length;
24+
}
25+
26+
public static unsafe AlignedMemory Allocate(nuint length, nuint alignment)
27+
=> new AlignedMemory(NativeMemory.AlignedAlloc(length, alignment), (int)length);
28+
29+
public bool IsDisposed => _disposed;
30+
31+
public unsafe override System.Span<byte> GetSpan() => new System.Span<byte>((void*)_memory, _length);
32+
33+
public override MemoryHandle Pin(int elementIndex = 0)
34+
{
35+
unsafe
36+
{
37+
Retain();
38+
if ((uint)elementIndex > _length) throw new ArgumentOutOfRangeException(nameof(elementIndex));
39+
void* pointer = Unsafe.Add<byte>((void*)_memory, elementIndex);
40+
return new MemoryHandle(pointer, default, this);
41+
}
42+
}
43+
44+
private bool Release()
45+
{
46+
int newRefCount = Interlocked.Decrement(ref _refCount);
47+
48+
if (newRefCount < 0)
49+
{
50+
throw new InvalidOperationException("Unmatched Release/Retain");
51+
}
52+
53+
return newRefCount != 0;
54+
}
55+
56+
private void Retain()
57+
{
58+
if (_disposed)
59+
{
60+
throw new ObjectDisposedException(nameof(AlignedMemory));
61+
}
62+
63+
Interlocked.Increment(ref _refCount);
64+
}
65+
66+
protected override unsafe void Dispose(bool disposing)
67+
{
68+
if (_disposed)
69+
{
70+
return;
71+
}
72+
73+
NativeMemory.AlignedFree(_memory.ToPointer());
74+
_disposed = true;
75+
}
76+
77+
protected override bool TryGetArray(out ArraySegment<byte> arraySegment)
78+
{
79+
// cannot expose managed array
80+
arraySegment = default;
81+
return false;
82+
}
83+
84+
public override void Unpin() => Release();
85+
}
86+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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

Comments
 (0)