diff --git a/src/benchmarks/micro/MicroBenchmarks.csproj b/src/benchmarks/micro/MicroBenchmarks.csproj
index 56be2af97cc..86dc94605ba 100644
--- a/src/benchmarks/micro/MicroBenchmarks.csproj
+++ b/src/benchmarks/micro/MicroBenchmarks.csproj
@@ -111,7 +111,9 @@
+
+
diff --git a/src/benchmarks/micro/libraries/Common/AlignedMemory.cs b/src/benchmarks/micro/libraries/Common/AlignedMemory.cs
new file mode 100644
index 00000000000..a7188984785
--- /dev/null
+++ b/src/benchmarks/micro/libraries/Common/AlignedMemory.cs
@@ -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
+ {
+ 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 GetSpan() => new System.Span((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((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 arraySegment)
+ {
+ // cannot expose managed array
+ arraySegment = default;
+ return false;
+ }
+
+ public override void Unpin() => Release();
+ }
+}
\ No newline at end of file
diff --git a/src/benchmarks/micro/libraries/System.IO.FileSystem/Perf.RandomAccess.NoBuffering.cs b/src/benchmarks/micro/libraries/System.IO.FileSystem/Perf.RandomAccess.NoBuffering.cs
new file mode 100644
index 00000000000..f3d9a2c0961
--- /dev/null
+++ b/src/benchmarks/micro/libraries/System.IO.FileSystem/Perf.RandomAccess.NoBuffering.cs
@@ -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 _sourceFilePaths, _destinationFilePaths;
+ private Dictionary _countToAlignedMemory;
+ private Dictionary[]> _countToMemory;
+ private Dictionary[]> _countToReadOnlyMemory;
+
+ private void Setup(params long[] fileSizes)
+ {
+ _countToAlignedMemory = new Dictionary()
+ {
+ { 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)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((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