|
using System; |
|
using System.Runtime.CompilerServices; |
|
using System.Runtime.InteropServices; |
|
using NUnit.Framework; |
|
using Unity.Collections.LowLevel.Unsafe; |
|
using Unity.PerformanceTesting; |
|
|
|
public unsafe class UnsafeBenchmark |
|
{ |
|
private const int Count = 1_000_000; |
|
|
|
private int* arrayPtr; |
|
private NB<int> nb; |
|
|
|
private static int sink; |
|
|
|
[StructLayout(LayoutKind.Sequential, Size = 1)] |
|
public struct TestThreadSafety : IDisposable |
|
{ |
|
public void Dispose() |
|
{ |
|
} |
|
} |
|
|
|
[StructLayout(LayoutKind.Sequential, Size = 1)] |
|
public readonly struct Sentinel |
|
{ |
|
public TestThreadSafety TestThreadSafety() => new TestThreadSafety(); |
|
} |
|
|
|
public struct NB<T> where T : unmanaged |
|
{ |
|
private readonly uint _capacity; |
|
private readonly IntPtr _ptr; |
|
private readonly Sentinel _threadSentinel; |
|
private static readonly int SIZE = Unsafe.SizeOf<T>(); |
|
|
|
public NB(IntPtr array, uint capacity) |
|
: this() |
|
{ |
|
this._ptr = array; |
|
this._capacity = capacity; |
|
this._threadSentinel = new(); |
|
} |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
public ref T SystemUnsafe(int index) |
|
{ |
|
// using (this._threadSentinel.TestThreadSafety()) |
|
return ref Unsafe.AsRef<T>((void*)(this._ptr + (int)index * Unsafe.SizeOf<T>())); |
|
} |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
public ref T UnityUnsafe(int index) |
|
{ |
|
return ref UnsafeUtility.AsRef<T>((void*)(this._ptr + (int)index * UnsafeUtility.SizeOf<T>())); |
|
} |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
public ref T WithSentinelAndSize(int index) |
|
{ |
|
using (this._threadSentinel.TestThreadSafety()) |
|
return ref Unsafe.AsRef<T>((void*)(this._ptr + (int)index * SIZE)); |
|
} |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
public ref T WithSize(int index) |
|
{ |
|
return ref Unsafe.AsRef<T>((void*)(this._ptr + (int)index * SIZE)); |
|
} |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
public ref T WithSentinel(int index) |
|
{ |
|
using (this._threadSentinel.TestThreadSafety()) |
|
return ref Unsafe.AsRef<T>((void*)(this._ptr + (int)index * Unsafe.SizeOf<T>())); |
|
} |
|
} |
|
|
|
[OneTimeSetUp] |
|
public void GlobalSetup() |
|
{ |
|
arrayPtr = (int*)Marshal.AllocHGlobal(Count * sizeof(int)); |
|
|
|
for (int i = 0; i < Count; ++i) |
|
arrayPtr[i] = i; |
|
|
|
nb = new((IntPtr)arrayPtr, Count); |
|
} |
|
|
|
[OneTimeTearDown] |
|
public void GlobalCleanup() |
|
{ |
|
Marshal.FreeHGlobal((IntPtr)arrayPtr); |
|
} |
|
|
|
[Test, Performance] |
|
public void AccessPointerDirectly() |
|
{ |
|
Measure.Method(() => |
|
{ |
|
int result = 0; |
|
|
|
for (int i = 0; i < Count; ++i) |
|
{ |
|
result += arrayPtr[i]; |
|
} |
|
|
|
sink = result; |
|
}).MeasurementCount(1000).Run(); |
|
} |
|
|
|
[Test, Performance] |
|
public void AccessWithSystemUnsafe() |
|
{ |
|
Measure.Method(() => |
|
{ |
|
int result = 0; |
|
|
|
for (int i = 0; i < Count; ++i) |
|
{ |
|
result += nb.SystemUnsafe(i); |
|
} |
|
|
|
sink = result; |
|
}).MeasurementCount(1000).Run(); |
|
} |
|
|
|
[Test, Performance] |
|
public void AccessWithUnityUnsafe() |
|
{ |
|
Measure.Method(() => |
|
{ |
|
int result = 0; |
|
|
|
for (int i = 0; i < Count; ++i) |
|
{ |
|
result += nb.UnityUnsafe(i); |
|
} |
|
|
|
sink = result; |
|
}).MeasurementCount(1000).Run(); |
|
} |
|
|
|
[Test, Performance] |
|
public void AccessWithSentinelAndSize() |
|
{ |
|
Measure.Method(() => |
|
{ |
|
int result = 0; |
|
|
|
for (int i = 0; i < Count; ++i) |
|
{ |
|
result += nb.WithSentinelAndSize(i); |
|
} |
|
|
|
sink = result; |
|
}).MeasurementCount(1000).Run(); |
|
} |
|
|
|
[Test, Performance] |
|
public void AccessWithSentinel() |
|
{ |
|
Measure.Method(() => |
|
{ |
|
int result = 0; |
|
|
|
for (int i = 0; i < Count; ++i) |
|
{ |
|
result += nb.WithSentinel(i); |
|
} |
|
|
|
sink = result; |
|
}).MeasurementCount(1000).Run(); |
|
} |
|
|
|
[Test, Performance] |
|
public void AccessWithSize() |
|
{ |
|
Measure.Method(() => |
|
{ |
|
int result = 0; |
|
|
|
for (int i = 0; i < Count; ++i) |
|
{ |
|
result += nb.WithSize(i); |
|
} |
|
|
|
sink = result; |
|
}).MeasurementCount(1000).Run(); |
|
} |
|
} |