Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/benchmarks/micro/MicroBenchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@

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

<ItemGroup>
Expand Down
86 changes: 86 additions & 0 deletions src/benchmarks/micro/libraries/Common/AlignedMemory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers;
using System.Memory;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

namespace System
{
internal sealed class AlignedMemory : MemoryManager<byte>
{
private bool _disposed;
private int _refCount;
private IntPtr _memory;
private int _length;

private unsafe AlignedMemory(void* memory, int length)
{
_memory = (IntPtr)memory;
_length = length;
}

public static unsafe AlignedMemory Allocate(nuint length, nuint alignment)
=> new AlignedMemory(NativeMemory.AlignedAlloc(length, alignment), (int)length);

public bool IsDisposed => _disposed;

public unsafe override System.Span<byte> GetSpan() => new System.Span<byte>((void*)_memory, _length);

public override MemoryHandle Pin(int elementIndex = 0)
{
unsafe
{
Retain();
if ((uint)elementIndex > _length) throw new ArgumentOutOfRangeException(nameof(elementIndex));
void* pointer = Unsafe.Add<byte>((void*)_memory, elementIndex);
return new MemoryHandle(pointer, default, this);
}
}

private bool Release()
{
int newRefCount = Interlocked.Decrement(ref _refCount);

if (newRefCount < 0)
{
throw new InvalidOperationException("Unmatched Release/Retain");
}

return newRefCount != 0;
}

private void Retain()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(AlignedMemory));
}

Interlocked.Increment(ref _refCount);
}

protected override unsafe void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

NativeMemory.AlignedFree(_memory.ToPointer());
_disposed = true;
}

protected override bool TryGetArray(out ArraySegment<byte> arraySegment)
{
// cannot expose managed array
arraySegment = default;
return false;
}

public override void Unpin() => Release();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Extensions;
using MicroBenchmarks;
using Microsoft.Win32.SafeHandles;

namespace System.IO.Tests
{
[BenchmarkCategory(Categories.Libraries, Categories.NoWASM)]
[OperatingSystemsFilter(allowed: true, platforms: OS.Windows)] // NO_BUFFERING is supported only on Windows: https://github.com/dotnet/runtime/issues/27408
public class Perf_RandomAccess_NoBuffering
{
private const int OneKibibyte = 1 << 10; // 1024
private const int OneMibibyte = OneKibibyte << 10;
private const int HundredMibibytes = OneMibibyte * 100;
private const FileOptions NoBuffering = (FileOptions)0x20000000;
private const FileOptions AsyncNoBuffering = FileOptions.Asynchronous | NoBuffering;

static int PageSize = Environment.SystemPageSize;

private Dictionary<long, string> _sourceFilePaths, _destinationFilePaths;
private Dictionary<int, AlignedMemory[]> _countToAlignedMemory;
private Dictionary<int, System.Memory<byte>[]> _countToMemory;
private Dictionary<int, System.ReadOnlyMemory<byte>[]> _countToReadOnlyMemory;

private void Setup(params long[] fileSizes)
{
_countToAlignedMemory = new Dictionary<int, AlignedMemory[]>()
{
{ 4, Enumerable.Range(0, 4).Select(_ => AlignedMemory.Allocate((nuint)PageSize, (nuint)PageSize)).ToArray() },
{ 16, Enumerable.Range(0, 16).Select(_ => AlignedMemory.Allocate((nuint)PageSize, (nuint)PageSize)).ToArray() },
};
_countToMemory = _countToAlignedMemory.ToDictionary(pair => pair.Key, pair => pair.Value.Select(aligned => aligned.Memory).ToArray());
_countToReadOnlyMemory = _countToAlignedMemory.ToDictionary(pair => pair.Key, pair => pair.Value.Select(aligned => (System.ReadOnlyMemory<byte>)aligned.Memory).ToArray());

_sourceFilePaths = fileSizes.ToDictionary(size => size, size => CreateFileWithRandomContent(size));
_destinationFilePaths = fileSizes.ToDictionary(size => size, size => CreateFileWithRandomContent(size));

static string CreateFileWithRandomContent(long fileSize)
{
string filePath = FileUtils.GetTestFilePath();
File.WriteAllBytes(filePath, ValuesGenerator.Array<byte>((int)fileSize));
return filePath;
}
}

[GlobalCleanup]
public void Cleanup()
{
foreach (string filePath in _sourceFilePaths.Values.Concat(_destinationFilePaths.Values))
{
File.Delete(filePath);
}
foreach (IDisposable alignedMemory in _countToAlignedMemory.Values.SelectMany(buffers => buffers))
{
alignedMemory.Dispose();
}
}

[GlobalSetup(Targets = new[] { nameof(ReadScatterAsync), nameof(WriteGatherAsync) })]
public void SetupBigFileBenchmarks() => Setup(OneMibibyte, HundredMibibytes);

public IEnumerable<object[]> ReadWrite_MultipleBuffers_Arguments()
{
yield return new object[] { OneMibibyte, 4 }; // medium size file, 4xpage size user buffers
yield return new object[] { OneMibibyte, 16 }; // medium size file, 16xpage size user buffers
yield return new object[] { HundredMibibytes, 4 }; // big file, 4xpage size user buffers
yield return new object[] { HundredMibibytes, 16 }; // big file, 16xpage size user buffers
}

[Benchmark]
[ArgumentsSource(nameof(ReadWrite_MultipleBuffers_Arguments))]
public async Task<long> ReadScatterAsync(long fileSize, int count)
{
CancellationToken cancellationToken = CancellationToken.None;
System.Memory<byte>[] buffers = _countToMemory[count];
long bytesRead = 0;
using (SafeFileHandle fileHandle = File.OpenHandle(_sourceFilePaths[fileSize], FileMode.Open, FileAccess.Read, FileShare.Read, AsyncNoBuffering))
{
while (bytesRead < fileSize)
{
bytesRead += await RandomAccess.ReadAsync(fileHandle, buffers, bytesRead, cancellationToken);
}
}

return bytesRead;
}

[Benchmark]
[ArgumentsSource(nameof(ReadWrite_MultipleBuffers_Arguments))]
public async Task WriteGatherAsync(long fileSize, int count)
{
CancellationToken cancellationToken = CancellationToken.None;
System.ReadOnlyMemory<byte>[] buffers = _countToReadOnlyMemory[count];
long buffersSize = count * PageSize;
using (SafeFileHandle fileHandle = File.OpenHandle(_destinationFilePaths[fileSize], FileMode.Create, FileAccess.Write, FileShare.Read, AsyncNoBuffering))
{
long bytesWritten = 0;
for (int i = 0; i < fileSize / buffersSize; i++)
{
await RandomAccess.WriteAsync(fileHandle, buffers, bytesWritten, cancellationToken);
bytesWritten += buffersSize;
}
}
}
}
}