Improve performance and safety

This commit is contained in:
2026-02-01 01:56:17 +09:00
parent 1fee890329
commit c36405645b
32 changed files with 2050 additions and 360 deletions

View File

@@ -41,16 +41,22 @@ namespace Misaki.HighPerformance.Analyzer
.OfType<ExpressionSyntax>() .OfType<ExpressionSyntax>()
.First(); .First();
// Register a code action that will invoke the fix.
context.RegisterCodeFix( context.RegisterCodeFix(
CodeAction.Create( CodeAction.Create(
title: "Share", "Share memory",
createChangedDocument: c => TransferOwnershipAsync(context.Document, expressionSyntax, c), c => ShareAsync(context.Document, expressionSyntax, c),
equivalenceKey: nameof(StructCopyCodeFixProvider)), "StructCopyCodeFixProvider_ShareMemory"),
diagnostic);
context.RegisterCodeFix(
CodeAction.Create(
"Transfer ownership",
c => TransferOwnershipAsync(context.Document, expressionSyntax, c),
"StructCopyCodeFixProvider_TransferOwnership"),
diagnostic); diagnostic);
} }
private async Task<Document> TransferOwnershipAsync(Document document, ExpressionSyntax expressionToFix, CancellationToken cancellationToken) private async Task<Document> ShareAsync(Document document, ExpressionSyntax expressionToFix, CancellationToken cancellationToken)
{ {
// 1. Get the semantic model to figure out exactly what type we are dealing with // 1. Get the semantic model to figure out exactly what type we are dealing with
var semanticModel = await document.GetSemanticModelAsync(cancellationToken); var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
@@ -70,6 +76,33 @@ namespace Misaki.HighPerformance.Analyzer
var detachInvocation = SyntaxFactory.InvocationExpression(detachMethod); var detachInvocation = SyntaxFactory.InvocationExpression(detachMethod);
// 4. Replace the old node with the new node in the Syntax Tree
var root = await document.GetSyntaxRootAsync(cancellationToken);
var newRoot = root.ReplaceNode(expressionToFix, detachInvocation);
return document.WithSyntaxRoot(newRoot);
}
private async Task<Document> TransferOwnershipAsync(Document document, ExpressionSyntax expressionToFix, CancellationToken cancellationToken)
{
// 1. Get the semantic model to figure out exactly what type we are dealing with
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var typeSymbol = semanticModel.GetTypeInfo(expressionToFix).Type;
// 2. Generate the type name string (e.g., "UniquePtr<ID3D12Device>")
// We use ToMinimalDisplayString so Roslyn handles namespaces/using directives automatically.
var typeName = typeSymbol.ToMinimalDisplayString(semanticModel, expressionToFix.SpanStart);
var typeSyntax = SyntaxFactory.ParseTypeName(typeName);
// 3. Create the "Detach()" invocation: expression.Detach()
var detachMethod = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
expressionToFix, // The 'a' in 'b = a'
SyntaxFactory.IdentifierName("Detach")
);
var detachInvocation = SyntaxFactory.InvocationExpression(detachMethod);
// 4. Create the "new UniquePtr<T>(...)" expression // 4. Create the "new UniquePtr<T>(...)" expression
var newObjectCreation = SyntaxFactory.ObjectCreationExpression(typeSyntax) var newObjectCreation = SyntaxFactory.ObjectCreationExpression(typeSyntax)
.WithArgumentList( .WithArgumentList(

View File

@@ -0,0 +1,99 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using System.Collections.Immutable;
namespace Misaki.HighPerformance.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class DefensiveCopyAnalyzer : DiagnosticAnalyzer
{
public const string DIAGNOSTIC_ID = "MHP002";
private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor(
DIAGNOSTIC_ID,
"Defensive copy detected",
"Calling non-readonly method '{0}' on readonly field or local '{1}' causes a silent defensive copy",
"Safety",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
}
private void AnalyzeInvocation(OperationAnalysisContext context)
{
var invocation = (IInvocationOperation)context.Operation;
var method = invocation.TargetMethod;
var instance = invocation.Instance;
// 1. Basic Filters: Must be an instance method on a Value Type (Struct)
if (method.IsStatic || instance == null)
return;
if (!instance.Type.IsValueType)
return; // Classes don't copy
if (instance.Type.IsReadOnly)
return; // Readonly structs are safe (compiler enforced)
// 2. If the method itself is 'readonly', it promises not to mutate, so no copy needed.
if (method.IsReadOnly)
return;
// 3. CHECK THE CONTEXT: Is the variable we are calling on "Read Only"?
if (IsReadOnlyContext(instance, out var variableName))
{
var diagnostic = Diagnostic.Create(
s_rule,
invocation.Syntax.GetLocation(),
method.Name,
variableName);
context.ReportDiagnostic(diagnostic);
}
}
private bool IsReadOnlyContext(IOperation instance, out string name)
{
name = "";
switch (instance)
{
// CASE 1: Readonly Field
case IFieldReferenceOperation fieldRef:
if (fieldRef.Field.IsReadOnly)
{
name = fieldRef.Field.Name;
return true;
}
break;
// CASE 2: Locals (ref readonly var x)
case ILocalReferenceOperation localRef:
// RefKind.In covers 'ref readonly' locals
if (localRef.Local.RefKind == RefKind.In)
{
name = localRef.Local.Name;
return true;
}
break;
// CASE 3: Parameters (in MyStruct x)
case IParameterReferenceOperation paramRef:
// RefKind.In covers 'in' parameters
if (paramRef.Parameter.RefKind == RefKind.In)
{
name = paramRef.Parameter.Name;
return true;
}
break;
}
return false;
}
}
}

View File

@@ -11,12 +11,13 @@ namespace Misaki.HighPerformance.Analyzer
public class StructCopyCodeAnalyzer : DiagnosticAnalyzer public class StructCopyCodeAnalyzer : DiagnosticAnalyzer
{ {
public const string DIAGNOSTIC_ID = "MHP001"; public const string DIAGNOSTIC_ID = "MHP001";
private const string _TITLE = "Struct marked as NonCopyable was copied";
private const string _MESSAGE_FORMAT = "The struct '{0}' is designed for unique ownership and cannot be copied. Use .Detach(), .Get(), .Share() or pass by reference.";
private const string _CATEGORY = "Safety";
private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor( private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor(
DIAGNOSTIC_ID, _TITLE, _MESSAGE_FORMAT, _CATEGORY, DiagnosticSeverity.Error, isEnabledByDefault: true); DIAGNOSTIC_ID,
"Struct marked as NonCopyable was copied",
"The struct '{0}' is designed for unique ownership and cannot be copied. Use .Detach(), .Get(), .Share() or pass by reference.",
"Safety",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule);

View File

@@ -6,7 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>1.2.2</AssemblyVersion> <AssemblyVersion>1.2.3</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>

View File

@@ -8,8 +8,10 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
{ {
private const int _FRAME_LATENCY = 4; private const int _FRAME_LATENCY = 4;
private const uint _ARENA_SIZE = 1024 * 1024; // 1 MB private const uint _ARENA_SIZE = 1024 * 1024; // 1 MB
private const int _MAGIC_ID = -559038737;
private DynamicArena* _pArena; private DynamicArena* _pArena;
private int _currentFrameCount;
private int _currentFrameIndex; private int _currentFrameIndex;
private fixed int _allocationsPerFrame[_FRAME_LATENCY]; private fixed int _allocationsPerFrame[_FRAME_LATENCY];
@@ -23,6 +25,7 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
var memoryHandle = default(MemoryHandle); var memoryHandle = default(MemoryHandle);
_pArena = (DynamicArena*)AllocationManager.HeapAlloc((nuint)(sizeof(DynamicArena) * _FRAME_LATENCY), MemoryUtility.AlignOf<DynamicArena>(), AllocationOption.Clear, &memoryHandle); _pArena = (DynamicArena*)AllocationManager.HeapAlloc((nuint)(sizeof(DynamicArena) * _FRAME_LATENCY), MemoryUtility.AlignOf<DynamicArena>(), AllocationOption.Clear, &memoryHandle);
_currentFrameCount = 0;
_currentFrameIndex = 0; _currentFrameIndex = 0;
_memoryHandle = memoryHandle; _memoryHandle = memoryHandle;
@@ -32,13 +35,20 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
_allocationsPerFrame[i] = 0; _allocationsPerFrame[i] = 0;
} }
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free); _handle = new AllocationHandle
{
State = Unsafe.AsPointer(ref this),
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free,
IsValid = &IsValid,
};
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{ {
var selfPtr = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
var pCurrentArena = selfPtr->_pArena + selfPtr->_currentFrameIndex; var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
var ptr = pCurrentArena->Allocate(size, alignment, allocationOption); var ptr = pCurrentArena->Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
@@ -46,8 +56,8 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
return null; return null;
} }
Interlocked.Increment(ref selfPtr->_allocationsPerFrame[selfPtr->_currentFrameIndex]); Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
*pHandle = AllocationManager.GetMagicHandle(); *pHandle = new MemoryHandle(_MAGIC_ID, pSelf->_currentFrameCount);
return ptr; return ptr;
} }
@@ -58,8 +68,8 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(instance, newSize, alignment, allocationOption, pHandle);
} }
var selfPtr = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
var pCurrentArena = selfPtr->_pArena + selfPtr->_currentFrameIndex; var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
var newPtr = pCurrentArena->Allocate(newSize, alignment, allocationOption); var newPtr = pCurrentArena->Allocate(newSize, alignment, allocationOption);
if (newPtr == null) if (newPtr == null)
{ {
@@ -71,20 +81,27 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
return newPtr; return newPtr;
} }
private static void Free(void* instance, void* ptr, MemoryHandle pHandle) private static void Free(void* instance, void* ptr, MemoryHandle handle)
{ {
// The arena allocator does not free individual blocks, as it manages memory in chunks. // The arena allocator does not free individual blocks, as it manages memory in chunks.
var selfPtr = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
Interlocked.Decrement(ref selfPtr->_allocationsPerFrame[selfPtr->_currentFrameIndex]); Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
}
private static bool IsValid(void* instance, MemoryHandle handle)
{
var pSelf = (TempJobAllocator*)instance;
return handle.id == _MAGIC_ID && handle.generation > pSelf->_currentFrameCount - _FRAME_LATENCY;
} }
public int AdvanceFrame() public int AdvanceFrame()
{ {
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0); var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
_currentFrameIndex = (_currentFrameIndex + 1) % _FRAME_LATENCY; _currentFrameCount++;
var pCurrentArena = _pArena + _currentFrameIndex; _currentFrameIndex = _currentFrameCount % _FRAME_LATENCY;
pCurrentArena->Reset();
(_pArena + _currentFrameIndex)->Reset();
return allocations; return allocations;
} }

View File

@@ -3,3 +3,4 @@ global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtility;
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>; global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>; global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
global using unsafe FreeFunc = delegate*<void*, void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, void>; global using unsafe FreeFunc = delegate*<void*, void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, void>;
global using unsafe IsValidFunc = delegate*<void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, bool>;

View File

@@ -1,57 +1,10 @@
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
{
public readonly int id;
public readonly int generation;
public readonly bool IsValid => AllocationManager.ContainsAllocation(this);
public readonly static MemoryHandle Invalid = new(-1, -1);
public MemoryHandle(int id, int generation)
{
this.id = id;
this.generation = generation;
}
public bool Equals(MemoryHandle other)
{
return id == other.id && generation == other.generation;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is MemoryHandle other && Equals(other);
}
public override int GetHashCode()
{
return id ^ generation;
}
public override string? ToString()
{
return $"MemoryHandle(Id: {id}, Generation: {generation})";
}
public static bool operator ==(MemoryHandle left, MemoryHandle right)
{
return left.Equals(right);
}
public static bool operator !=(MemoryHandle left, MemoryHandle right)
{
return !(left == right);
}
}
/// <summary> /// <summary>
/// Holds information about a memory allocation. /// Holds information about a memory allocation.
/// </summary> /// </summary>
@@ -93,15 +46,27 @@ public static unsafe class AllocationManager
private struct ArenaAllocator : IAllocator, IDisposable private struct ArenaAllocator : IAllocator, IDisposable
{ {
private const int _ARENA_MAGIC_ID = -3941029;
private DynamicArena _arena; private DynamicArena _arena;
private AllocationHandle _handle; private AllocationHandle _handle;
private int _currentTick;
public readonly AllocationHandle Handle => _handle; public readonly AllocationHandle Handle => _handle;
public readonly int CurrentTick => _currentTick;
public void Init(uint initialSize) public void Init(uint initialSize)
{ {
_arena = new DynamicArena(initialSize); _arena = new DynamicArena(initialSize);
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free); _handle = new AllocationHandle
{
State = Unsafe.AsPointer(ref this),
Alloc = &Allocate,
Realloc = &Reallocate,
Free = null,
IsValid = &IsValid
};
_currentTick = 0;
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
@@ -114,7 +79,7 @@ public static unsafe class AllocationManager
return null; return null;
} }
*pHandle = GetMagicHandle(); *pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
return ptr; return ptr;
} }
@@ -134,17 +99,20 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
return newPtr; return newPtr;
} }
private static void Free(void* instance, void* ptr, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
// The arena allocator does not free individual blocks, as it manages memory in chunks. var selfPtr = (ArenaAllocator*)instance;
return handle.id == _ARENA_MAGIC_ID && handle.generation == selfPtr->_currentTick;
} }
public void Reset() public void Reset()
{ {
_arena.Reset(); _arena.Reset();
_currentTick++;
} }
public void Dispose() public void Dispose()
@@ -161,27 +129,33 @@ public static unsafe class AllocationManager
public void Init() public void Init()
{ {
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free); _handle = new AllocationHandle
{
State = null,
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free,
IsValid = &IsValid
};
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{ {
return HeapAlloc(size, alignment, allocationOption, pHandle); return HeapAlloc(size, alignment, allocationOption, pHandle);
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{ {
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(null, newSize, alignment, allocationOption, pHandle);
} }
MemoryHandle newHandle; MemoryHandle newHandle;
var newPtr = HeapAlloc(newSize, alignment, allocationOption, &newHandle); var newPtr = HeapAlloc(newSize, alignment, allocationOption, &newHandle);
if (newPtr == null) if (newPtr == null)
{ {
// Allocation failed, return original pointer return null;
return ptr;
} }
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
@@ -191,15 +165,21 @@ public static unsafe class AllocationManager
return newPtr; return newPtr;
} }
private static void Free(void* instance, void* ptr, MemoryHandle handle) private static void Free(void* _, void* ptr, MemoryHandle handle)
{ {
HeapFree(ptr, handle); HeapFree(ptr, handle);
} }
private static bool IsValid(void* _, MemoryHandle handle)
{
return ContainsAllocation(handle);
}
} }
private struct StackAllocator : IAllocator private struct StackAllocator : IAllocator
{ {
// Thread-local stack for allocations. We does not track allocations across threads, which leads us to let system clean up the memory when thread exits. private const int _STACK_MAGIC_ID = -6843541;
[ThreadStatic] [ThreadStatic]
private static Stack s_stack; private static Stack s_stack;
private AllocationHandle _handle; private AllocationHandle _handle;
@@ -208,7 +188,14 @@ public static unsafe class AllocationManager
public void Init() public void Init()
{ {
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free); _handle = new AllocationHandle
{
State = Unsafe.AsPointer(ref this),
Alloc = &Allocate,
Realloc = &Reallocate,
Free = null,
IsValid = &IsValid
};
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
@@ -220,7 +207,7 @@ public static unsafe class AllocationManager
return null; return null;
} }
*pHandle = GetMagicHandle(); *pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
return ptr; return ptr;
} }
@@ -231,6 +218,24 @@ public static unsafe class AllocationManager
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(instance, newSize, alignment, allocationOption, pHandle);
} }
// Optimize for last allocation. Set offset directly.
var oldBase = s_stack.Buffer + s_stack.Offset - oldSize;
if (ptr == oldBase)
{
if (newSize > oldSize)
{
var diff = newSize - oldSize;
s_stack.Offset += diff;
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(s_stack.Buffer + s_stack.Offset - diff, diff);
}
}
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
return ptr;
}
var newPtr = s_stack.Allocate(newSize, alignment, allocationOption); var newPtr = s_stack.Allocate(newSize, alignment, allocationOption);
if (newPtr == null) if (newPtr == null)
{ {
@@ -239,11 +244,13 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
return newPtr; return newPtr;
} }
private static void Free(void* instance, void* ptr, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
return handle.id == _STACK_MAGIC_ID && handle.generation <= (int)s_stack.Offset;
} }
public static Stack.Scope CreateScope(StackAllocator* pSelf) public static Stack.Scope CreateScope(StackAllocator* pSelf)
@@ -264,7 +271,9 @@ public static unsafe class AllocationManager
private static AllocationHeader* s_pLiveHead; private static AllocationHeader* s_pLiveHead;
private static SpinLock s_liveLock; private static SpinLock s_liveLock;
private readonly static ConcurrentSlotMap<IntPtr> s_allocations; private static readonly ConcurrentSlotMap<IntPtr> s_allocations;
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
/// <summary> /// <summary>
/// Gets the number of live persistent heap allocations when the debug layer is disabled. /// Gets the number of live persistent heap allocations when the debug layer is disabled.
@@ -276,11 +285,14 @@ public static unsafe class AllocationManager
/// </summary> /// </summary>
public static bool IsDebugLayerEnabled => s_debugLayer; public static bool IsDebugLayerEnabled => s_debugLayer;
static AllocationManager() static AllocationManager()
{ {
s_pArenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator)); var allocatorTotalSize = (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator));
s_pHeapAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator)); var basePtr = NativeMemory.Alloc(allocatorTotalSize);
s_pStackAllocator = (StackAllocator*)NativeMemory.Alloc((nuint)sizeof(StackAllocator)); s_pArenaAllocator = (ArenaAllocator*)basePtr;
s_pHeapAllocator = (HeapAllocator*)((byte*)basePtr + (nuint)sizeof(ArenaAllocator));
s_pStackAllocator = (StackAllocator*)((byte*)basePtr + (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator)));
s_liveLock = new SpinLock(false); s_liveLock = new SpinLock(false);
@@ -429,7 +441,7 @@ public static unsafe class AllocationManager
MemCpy(newUser, userPtr, newSize); MemCpy(newUser, userPtr, newSize);
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize) if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
{ {
MemClear((byte*)newUser + oldSize, newSize - oldSize); MemClear(newUser + oldSize, newSize - oldSize);
} }
// Unlink and free the old block (without freeing the StackTrace pHandle again) // Unlink and free the old block (without freeing the StackTrace pHandle again)
@@ -465,21 +477,21 @@ public static unsafe class AllocationManager
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AllocationHandle GetAllocationHandle(Allocator allocator) public static AllocationHandle GetAllocationHandle(Allocator allocator)
{ {
switch (allocator) return allocator switch
{ {
case Allocator.Temp: Allocator.Temp => s_pArenaAllocator->Handle,
return s_pArenaAllocator->Handle; Allocator.Persistent => s_pHeapAllocator->Handle,
case Allocator.Persistent: _ => throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator)),
return s_pHeapAllocator->Handle; };
default:
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
}
} }
/// <summary> /// <summary>
/// Allocates a block of memory from the heap with the specified size and alignment, using the given allocation /// Allocates a block of memory from the heap with the specified size and alignment, using the given allocation options.
/// options.
/// </summary> /// </summary>
/// <remarks>
/// This will allocate memory from the heap. If the debug layer is enabled, additional tracking information will be recorded.
/// The memory handle is always tracked unless the <see cref="AllocationOption.Untrack"/> flag is specified.
/// </remarks>
/// <param name="size">The number of bytes to allocate. Must be greater than zero.</param> /// <param name="size">The number of bytes to allocate. Must be greater than zero.</param>
/// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two.</param> /// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two.</param>
/// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether the memory should be cleared or /// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether the memory should be cleared or
@@ -488,8 +500,10 @@ public static unsafe class AllocationManager
/// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception> /// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception>
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{ {
var isUntrack = allocationOption.HasFlag(AllocationOption.Untrack);
void* ptr; void* ptr;
if (s_debugLayer) if (s_debugLayer && !isUntrack)
{ {
ptr = DebugAllocate(size, alignment); ptr = DebugAllocate(size, alignment);
} }
@@ -509,7 +523,15 @@ public static unsafe class AllocationManager
MemClear(ptr, size); MemClear(ptr, size);
} }
if (isUntrack)
{
*pHandle = MagicHandle;
}
else
{
*pHandle = AddAllocation((IntPtr)ptr); *pHandle = AddAllocation((IntPtr)ptr);
}
return ptr; return ptr;
} }
@@ -521,7 +543,7 @@ public static unsafe class AllocationManager
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param> /// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
public static void HeapFree(void* ptr, MemoryHandle handle) public static void HeapFree(void* ptr, MemoryHandle handle)
{ {
if (s_debugLayer) if (s_debugLayer && handle != MagicHandle)
{ {
DebugFree(ptr); DebugFree(ptr);
} }
@@ -576,12 +598,6 @@ public static unsafe class AllocationManager
return new MemoryHandle(id, generation); return new MemoryHandle(id, generation);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle GetMagicHandle()
{
return new MemoryHandle(int.MinValue, int.MinValue);
}
/// <summary> /// <summary>
/// Removes the memory allocation associated with the specified handle. /// Removes the memory allocation associated with the specified handle.
/// </summary> /// </summary>
@@ -608,14 +624,17 @@ public static unsafe class AllocationManager
/// <summary> /// <summary>
/// Determines whether the specified memory handle refers to a currently tracked allocation. /// Determines whether the specified memory handle refers to a currently tracked allocation.
/// </summary> /// </summary>
/// <remarks>
/// This only validates the memory when you added the allocation via <see cref="AddAllocation(IntPtr)"/>.
/// For validating memory from <see cref="AllocationHandle"/>, use <see cref="AllocationHandle.IsValid"/> instead.
/// </remarks>
/// <param name="handle">The memory handle to check for an associated allocation.</param> /// <param name="handle">The memory handle to check for an associated allocation.</param>
/// <returns>true if the allocation corresponding to the handle exists; otherwise, false.</returns> /// <returns>true if the allocation corresponding to the handle exists; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsAllocation(MemoryHandle handle) public static bool ContainsAllocation(MemoryHandle handle)
{ {
if (handle.id == int.MinValue && handle.generation == int.MinValue) if (handle == MagicHandle)
{ {
// Magic handle always valid
return true; return true;
} }
@@ -679,22 +698,16 @@ public static unsafe class AllocationManager
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations."); throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations.");
} }
// NOTE: Arena allocator holds the base ptr for all allocators, heap and stack allocators do not own any memory themselves.
if (s_pArenaAllocator != null) if (s_pArenaAllocator != null)
{ {
s_pArenaAllocator->Dispose(); s_pArenaAllocator->Dispose();
Stack.DisposeAll();
NativeMemory.Free(s_pArenaAllocator); NativeMemory.Free(s_pArenaAllocator);
} }
if (s_pHeapAllocator != null)
{
NativeMemory.Free(s_pHeapAllocator);
}
if (s_pStackAllocator != null)
{
NativeMemory.Free(s_pStackAllocator);
}
s_disposed = true; s_disposed = true;
} }
} }

View File

@@ -11,6 +11,10 @@ public enum AllocationOption : byte
/// Clear the memory to zero upon allocation. /// Clear the memory to zero upon allocation.
/// </summary> /// </summary>
Clear = 1 << 0, Clear = 1 << 0,
/// <summary>
/// Specify that this memory allocation should not been tracked competly, which <see cref="AllocationManager"/> will not perform any safty check like use after free and leack detection.
/// </summary>
Untrack = 1 << 1,
} }
public enum Allocator : byte public enum Allocator : byte

View File

@@ -1,16 +1,62 @@
using System.Diagnostics.CodeAnalysis;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
{
public readonly int id;
public readonly int generation;
public readonly static MemoryHandle Invalid = new(-1, -1);
public MemoryHandle(int id, int generation)
{
this.id = id;
this.generation = generation;
}
public bool Equals(MemoryHandle other)
{
return id == other.id && generation == other.generation;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is MemoryHandle other && Equals(other);
}
public override int GetHashCode()
{
return id ^ generation;
}
public override string? ToString()
{
return $"MemoryHandle(Id: {id}, Generation: {generation})";
}
public static bool operator ==(MemoryHandle left, MemoryHandle right)
{
return left.Equals(right);
}
public static bool operator !=(MemoryHandle left, MemoryHandle right)
{
return !(left == right);
}
}
/// <summary> /// <summary>
/// A structure that encapsulates function pointers for memory allocation operations. /// A structure that encapsulates function pointers for memory allocation operations.
/// </summary> /// </summary>
public readonly unsafe struct AllocationHandle public readonly unsafe struct AllocationHandle
{ {
/// <summary> /// <summary>
/// Gets a pointer to the allocator instance associated with this allocation handle. /// Gets a pointer to the state instance associated with this allocation handle.
/// </summary> /// </summary>
public void* pAllocator public void* State
{ {
get; get; init;
} }
/// <summary> /// <summary>
@@ -18,7 +64,7 @@ public readonly unsafe struct AllocationHandle
/// </summary> /// </summary>
public AllocFunc Alloc public AllocFunc Alloc
{ {
get; get; init;
} }
/// <summary> /// <summary>
@@ -26,7 +72,7 @@ public readonly unsafe struct AllocationHandle
/// </summary> /// </summary>
public ReallocFunc Realloc public ReallocFunc Realloc
{ {
get; get; init;
} }
/// <summary> /// <summary>
@@ -34,37 +80,29 @@ public readonly unsafe struct AllocationHandle
/// </summary> /// </summary>
public FreeFunc Free public FreeFunc Free
{ {
get; get; init;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AllocationHandle"/> struct with the specified allocator and memory /// Gets a function pointer for validating a memory handle.
/// management functions.
/// </summary> /// </summary>
/// <param name="allocator">A pointer to the allocator instance used for memory management.</param> public IsValidFunc IsValid
/// <param name="alloc">The function used to allocate memory.</param>
/// <param name="realloc">The function used to reallocate memory.</param>
/// <param name="free">The function used to free allocated memory.</param>
public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
{ {
pAllocator = allocator; get; init;
Alloc = alloc;
Realloc = realloc;
Free = free;
} }
} }
/// <summary> /// <summary>
/// Represents an allocator interface for managing memory allocations. /// Represents an state interface for managing memory allocations.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The allocator must be pined to a specific memory region. /// The state must be pined to a specific memory region.
/// Otherwise the reference of the <see cref="AllocationHandle.pAllocator"/>, may become invalid and lead to undefined behavior. /// Otherwise the reference of the <see cref="AllocationHandle.State"/>, may become invalid and lead to undefined behavior.
/// </remarks> /// </remarks>
public interface IAllocator public interface IAllocator
{ {
/// <summary> /// <summary>
/// Gets a reference to the allocation handle associated with this allocator. /// Gets a reference to the allocation handle associated with this state.
/// </summary> /// </summary>
AllocationHandle Handle AllocationHandle Handle
{ {

View File

@@ -2,12 +2,33 @@ using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;
public unsafe partial struct Stack
{
private static void** s_pStackBuffers = null;
private static int s_stackCount = 0;
private static int s_stackCapacity = 0;
private static readonly SpinLock s_locker = new SpinLock(false);
public static void DisposeAll()
{
if (s_pStackBuffers == null)
{
return;
}
for (var i = 0; i < s_stackCount; i++)
{
Free(s_pStackBuffers[i]);
}
}
}
/// <summary> /// <summary>
/// Provides a stack-based memory allocator for unmanaged memory, enabling fast allocation and deallocation of memory /// Provides a stack-based memory allocator for unmanaged memory, enabling fast allocation and deallocation of memory
/// blocks within a preallocated buffer. /// blocks within a preallocated buffer.
/// </summary> /// </summary>
/// <remarks>This is not a thread-safe implementation.</remarks> /// <remarks>This is not a thread-safe implementation.</remarks>
public unsafe struct Stack : IDisposable public unsafe partial struct Stack : IDisposable
{ {
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
@@ -42,6 +63,14 @@ public unsafe struct Stack : IDisposable
private nuint _offset; private nuint _offset;
private uint _activeScopeCount; private uint _activeScopeCount;
internal readonly byte* Buffer => _buffer;
public nuint Offset
{
readonly get => _offset;
internal set => _offset = value;
}
/// <summary> /// <summary>
/// Initializes a new instance of the StackAllocator class with a buffer of the specified size. /// Initializes a new instance of the StackAllocator class with a buffer of the specified size.
/// </summary> /// </summary>
@@ -67,6 +96,38 @@ public unsafe struct Stack : IDisposable
_size = size; _size = size;
_offset = 0; _offset = 0;
_activeScopeCount = 0; _activeScopeCount = 0;
var token = false;
try
{
s_locker.Enter(ref token);
if (s_pStackBuffers == null)
{
s_pStackBuffers = (void**)Malloc((nuint)sizeof(void*) * 4u);
s_stackCapacity = 4;
}
if (s_stackCount >= s_stackCapacity)
{
var pOld = s_pStackBuffers;
var newCapacity = s_stackCapacity * 2;
var pNew = (void**)Realloc(pOld, (nuint)sizeof(void*) * (nuint)newCapacity);
s_pStackBuffers = pNew;
s_stackCapacity = newCapacity;
}
s_pStackBuffers[s_stackCount] = _buffer;
s_stackCount++;
}
finally
{
if (token)
{
s_locker.Exit();
}
}
} }
private readonly void ThrowIfNoScope() private readonly void ThrowIfNoScope()

View File

@@ -0,0 +1,758 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 32 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 32 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString32"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedString32
{
public const int MAX_LENGTH = 15;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString32(string input)
: this(input.AsSpan())
{
}
public FixedString32(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString32(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 64 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 64 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString64"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedString64
{
public const int MAX_LENGTH = 31;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString64(string input)
: this(input.AsSpan())
{
}
public FixedString64(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString64(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 128 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 128 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString128"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedString128
{
public const int MAX_LENGTH = 63;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString128(string input)
: this(input.AsSpan())
{
}
public FixedString128(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString128(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 256 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 256 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString256"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedString256
{
public const int MAX_LENGTH = 127;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString256(string input)
: this(input.AsSpan())
{
}
public FixedString256(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString256(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 512 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 512 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString512"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedString512
{
public const int MAX_LENGTH = 255;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString512(string input)
: this(input.AsSpan())
{
}
public FixedString512(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString512(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 1024 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 1024 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString1024"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedString1024
{
public const int MAX_LENGTH = 511;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString1024(string input)
: this(input.AsSpan())
{
}
public FixedString1024(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString1024(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 2048 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 2048 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString2048"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedString2048
{
public const int MAX_LENGTH = 1023;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString2048(string input)
: this(input.AsSpan())
{
}
public FixedString2048(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString2048(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 4096 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 4096 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString4096"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedString4096
{
public const int MAX_LENGTH = 2047;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString4096(string input)
: this(input.AsSpan())
{
}
public FixedString4096(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString4096(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}

View File

@@ -0,0 +1,108 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
<# for (int i = 32; i <= 4096; i *= 2) { #>
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString<#= i #>"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedString<#= i #>
{
public const int MAX_LENGTH = <#= (i - 2) / 2 #>;
private ushort _length;
private fixed char _buffer[MAX_LENGTH];
public readonly ushort Length => _length;
public string Value
{
get
{
fixed (char* bufferPtr = _buffer)
{
return new string(bufferPtr, 0, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
if (value.Length > MAX_LENGTH)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)value.Length;
fixed (char* bufferPtr = _buffer)
fixed (char* valuePtr = value)
{
Unsafe.CopyBlockUnaligned(bufferPtr, valuePtr, (uint)(_length * sizeof(char)));
}
}
}
public FixedString<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedString<#= i #>(ReadOnlySpan<char> input)
{
if (input.Length > MAX_LENGTH)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)input.Length;
fixed (char* inputPtr = input)
fixed (char* bufferPtr = _buffer)
{
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, (uint)(_length * sizeof(char)));
}
}
public FixedString<#= i #>(char* input, ushort length)
: this(new ReadOnlySpan<char>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (char* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char* GetUnsafePtr()
{
return (char*)((ushort*)Unsafe.AsPointer(in this) + 1);
}
public override string ToString()
{
return Value;
}
}
<# } #>

View File

@@ -14,8 +14,10 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
[StructLayout(LayoutKind.Sequential, Size = 32)] [StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedText32 public unsafe struct FixedText32
{ {
public const int MAX_LENGTH = 30;
private ushort _length; private ushort _length;
private fixed byte _buffer[30]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -36,14 +38,14 @@ public unsafe struct FixedText32
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 30) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText32."); throw new ArgumentException("Input string is too long to fit in FixedText32.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 30)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -51,7 +53,7 @@ public unsafe struct FixedText32
public FixedText32(ReadOnlySpan<char> input) public FixedText32(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 30) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText32."); throw new ArgumentException("Input string is too long to fit in FixedText32.");
} }
@@ -59,7 +61,7 @@ public unsafe struct FixedText32
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 30); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -76,7 +78,7 @@ public unsafe struct FixedText32
public FixedText32(ReadOnlySpan<byte> input) public FixedText32(ReadOnlySpan<byte> input)
{ {
if (input.Length > 30) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText32."); throw new ArgumentException("Input byte array is too long to fit in FixedText32.");
} }
@@ -105,12 +107,9 @@ public unsafe struct FixedText32
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()
@@ -129,8 +128,10 @@ public unsafe struct FixedText32
[StructLayout(LayoutKind.Sequential, Size = 64)] [StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedText64 public unsafe struct FixedText64
{ {
public const int MAX_LENGTH = 62;
private ushort _length; private ushort _length;
private fixed byte _buffer[62]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -151,14 +152,14 @@ public unsafe struct FixedText64
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 62) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText64."); throw new ArgumentException("Input string is too long to fit in FixedText64.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 62)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -166,7 +167,7 @@ public unsafe struct FixedText64
public FixedText64(ReadOnlySpan<char> input) public FixedText64(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 62) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText64."); throw new ArgumentException("Input string is too long to fit in FixedText64.");
} }
@@ -174,7 +175,7 @@ public unsafe struct FixedText64
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 62); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -191,7 +192,7 @@ public unsafe struct FixedText64
public FixedText64(ReadOnlySpan<byte> input) public FixedText64(ReadOnlySpan<byte> input)
{ {
if (input.Length > 62) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText64."); throw new ArgumentException("Input byte array is too long to fit in FixedText64.");
} }
@@ -220,12 +221,9 @@ public unsafe struct FixedText64
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()
@@ -244,8 +242,10 @@ public unsafe struct FixedText64
[StructLayout(LayoutKind.Sequential, Size = 128)] [StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedText128 public unsafe struct FixedText128
{ {
public const int MAX_LENGTH = 126;
private ushort _length; private ushort _length;
private fixed byte _buffer[126]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -266,14 +266,14 @@ public unsafe struct FixedText128
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 126) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText128."); throw new ArgumentException("Input string is too long to fit in FixedText128.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 126)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -281,7 +281,7 @@ public unsafe struct FixedText128
public FixedText128(ReadOnlySpan<char> input) public FixedText128(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 126) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText128."); throw new ArgumentException("Input string is too long to fit in FixedText128.");
} }
@@ -289,7 +289,7 @@ public unsafe struct FixedText128
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 126); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -306,7 +306,7 @@ public unsafe struct FixedText128
public FixedText128(ReadOnlySpan<byte> input) public FixedText128(ReadOnlySpan<byte> input)
{ {
if (input.Length > 126) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText128."); throw new ArgumentException("Input byte array is too long to fit in FixedText128.");
} }
@@ -335,12 +335,9 @@ public unsafe struct FixedText128
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()
@@ -359,8 +356,10 @@ public unsafe struct FixedText128
[StructLayout(LayoutKind.Sequential, Size = 256)] [StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedText256 public unsafe struct FixedText256
{ {
public const int MAX_LENGTH = 254;
private ushort _length; private ushort _length;
private fixed byte _buffer[254]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -381,14 +380,14 @@ public unsafe struct FixedText256
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 254) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText256."); throw new ArgumentException("Input string is too long to fit in FixedText256.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 254)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -396,7 +395,7 @@ public unsafe struct FixedText256
public FixedText256(ReadOnlySpan<char> input) public FixedText256(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 254) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText256."); throw new ArgumentException("Input string is too long to fit in FixedText256.");
} }
@@ -404,7 +403,7 @@ public unsafe struct FixedText256
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 254); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -421,7 +420,7 @@ public unsafe struct FixedText256
public FixedText256(ReadOnlySpan<byte> input) public FixedText256(ReadOnlySpan<byte> input)
{ {
if (input.Length > 254) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText256."); throw new ArgumentException("Input byte array is too long to fit in FixedText256.");
} }
@@ -450,12 +449,9 @@ public unsafe struct FixedText256
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()
@@ -474,8 +470,10 @@ public unsafe struct FixedText256
[StructLayout(LayoutKind.Sequential, Size = 512)] [StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedText512 public unsafe struct FixedText512
{ {
public const int MAX_LENGTH = 510;
private ushort _length; private ushort _length;
private fixed byte _buffer[510]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -496,14 +494,14 @@ public unsafe struct FixedText512
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 510) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText512."); throw new ArgumentException("Input string is too long to fit in FixedText512.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 510)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -511,7 +509,7 @@ public unsafe struct FixedText512
public FixedText512(ReadOnlySpan<char> input) public FixedText512(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 510) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText512."); throw new ArgumentException("Input string is too long to fit in FixedText512.");
} }
@@ -519,7 +517,7 @@ public unsafe struct FixedText512
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 510); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -536,7 +534,7 @@ public unsafe struct FixedText512
public FixedText512(ReadOnlySpan<byte> input) public FixedText512(ReadOnlySpan<byte> input)
{ {
if (input.Length > 510) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText512."); throw new ArgumentException("Input byte array is too long to fit in FixedText512.");
} }
@@ -565,12 +563,9 @@ public unsafe struct FixedText512
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()
@@ -589,8 +584,10 @@ public unsafe struct FixedText512
[StructLayout(LayoutKind.Sequential, Size = 1024)] [StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedText1024 public unsafe struct FixedText1024
{ {
public const int MAX_LENGTH = 1022;
private ushort _length; private ushort _length;
private fixed byte _buffer[1022]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -611,14 +608,14 @@ public unsafe struct FixedText1024
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 1022) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText1024."); throw new ArgumentException("Input string is too long to fit in FixedText1024.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 1022)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -626,7 +623,7 @@ public unsafe struct FixedText1024
public FixedText1024(ReadOnlySpan<char> input) public FixedText1024(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 1022) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText1024."); throw new ArgumentException("Input string is too long to fit in FixedText1024.");
} }
@@ -634,7 +631,7 @@ public unsafe struct FixedText1024
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 1022); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -651,7 +648,7 @@ public unsafe struct FixedText1024
public FixedText1024(ReadOnlySpan<byte> input) public FixedText1024(ReadOnlySpan<byte> input)
{ {
if (input.Length > 1022) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText1024."); throw new ArgumentException("Input byte array is too long to fit in FixedText1024.");
} }
@@ -680,12 +677,9 @@ public unsafe struct FixedText1024
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()
@@ -704,8 +698,10 @@ public unsafe struct FixedText1024
[StructLayout(LayoutKind.Sequential, Size = 2048)] [StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedText2048 public unsafe struct FixedText2048
{ {
public const int MAX_LENGTH = 2046;
private ushort _length; private ushort _length;
private fixed byte _buffer[2046]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -726,14 +722,14 @@ public unsafe struct FixedText2048
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 2046) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText2048."); throw new ArgumentException("Input string is too long to fit in FixedText2048.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 2046)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -741,7 +737,7 @@ public unsafe struct FixedText2048
public FixedText2048(ReadOnlySpan<char> input) public FixedText2048(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 2046) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText2048."); throw new ArgumentException("Input string is too long to fit in FixedText2048.");
} }
@@ -749,7 +745,7 @@ public unsafe struct FixedText2048
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 2046); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -766,7 +762,7 @@ public unsafe struct FixedText2048
public FixedText2048(ReadOnlySpan<byte> input) public FixedText2048(ReadOnlySpan<byte> input)
{ {
if (input.Length > 2046) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText2048."); throw new ArgumentException("Input byte array is too long to fit in FixedText2048.");
} }
@@ -795,12 +791,9 @@ public unsafe struct FixedText2048
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()
@@ -819,8 +812,10 @@ public unsafe struct FixedText2048
[StructLayout(LayoutKind.Sequential, Size = 4096)] [StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedText4096 public unsafe struct FixedText4096
{ {
public const int MAX_LENGTH = 4094;
private ushort _length; private ushort _length;
private fixed byte _buffer[4094]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -841,14 +836,14 @@ public unsafe struct FixedText4096
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 4094) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText4096."); throw new ArgumentException("Input string is too long to fit in FixedText4096.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 4094)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -856,7 +851,7 @@ public unsafe struct FixedText4096
public FixedText4096(ReadOnlySpan<char> input) public FixedText4096(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 4094) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText4096."); throw new ArgumentException("Input string is too long to fit in FixedText4096.");
} }
@@ -864,7 +859,7 @@ public unsafe struct FixedText4096
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 4094); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -881,7 +876,7 @@ public unsafe struct FixedText4096
public FixedText4096(ReadOnlySpan<byte> input) public FixedText4096(ReadOnlySpan<byte> input)
{ {
if (input.Length > 4094) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText4096."); throw new ArgumentException("Input byte array is too long to fit in FixedText4096.");
} }
@@ -910,12 +905,9 @@ public unsafe struct FixedText4096
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()

View File

@@ -21,8 +21,10 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)] [StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedText<#= i #> public unsafe struct FixedText<#= i #>
{ {
public const int MAX_LENGTH = <#= i - 2 #>;
private ushort _length; private ushort _length;
private fixed byte _buffer[<#= i - 2 #>]; private fixed byte _buffer[MAX_LENGTH];
public readonly ushort Length => _length; public readonly ushort Length => _length;
public string Value public string Value
@@ -43,14 +45,14 @@ public unsafe struct FixedText<#= i #>
} }
var maxBytes = Encoding.UTF8.GetByteCount(value); var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > <#= i - 2 #>) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>."); throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
} }
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, <#= i - 2 #>)); _length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, MAX_LENGTH));
} }
} }
} }
@@ -58,7 +60,7 @@ public unsafe struct FixedText<#= i #>
public FixedText<#= i #>(ReadOnlySpan<char> input) public FixedText<#= i #>(ReadOnlySpan<char> input)
{ {
var maxBytes = Encoding.UTF8.GetByteCount(input); var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > <#= i - 2 #>) if (maxBytes > MAX_LENGTH)
{ {
throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>."); throw new ArgumentException("Input string is too long to fit in FixedText<#= i #>.");
} }
@@ -66,7 +68,7 @@ public unsafe struct FixedText<#= i #>
fixed (char* inputPtr = input) fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, <#= i - 2 #>); var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, MAX_LENGTH);
_length = (ushort)actualByteCount; _length = (ushort)actualByteCount;
} }
} }
@@ -83,7 +85,7 @@ public unsafe struct FixedText<#= i #>
public FixedText<#= i #>(ReadOnlySpan<byte> input) public FixedText<#= i #>(ReadOnlySpan<byte> input)
{ {
if (input.Length > <#= i - 2 #>) if (input.Length > MAX_LENGTH)
{ {
throw new ArgumentException("Input byte array is too long to fit in FixedText<#= i #>."); throw new ArgumentException("Input byte array is too long to fit in FixedText<#= i #>.");
} }
@@ -112,12 +114,9 @@ public unsafe struct FixedText<#= i #>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer() public readonly byte* GetUnsafePtr()
{ {
fixed (byte* ptr = _buffer) return (byte*)((ushort*)Unsafe.AsPointer(in this) + 1);
{
return ptr;
}
} }
public override string ToString() public override string ToString()

View File

@@ -1,5 +1,6 @@
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections; using System.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections; namespace Misaki.HighPerformance.LowLevel.Collections;
@@ -50,18 +51,26 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
private readonly T* _buffer; private readonly T* _buffer;
private readonly int _count; private readonly int _count;
public readonly int Count => _count; public int Count => _count;
public readonly T this[int index] public ref readonly T this[int index]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index); get
{
CheckIndexBounds(index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
} }
public readonly T this[uint index] public ref readonly T this[uint index]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UnsafeUtility.ReadArrayElement<T>(_buffer, index); get
{
CheckIndexBounds((int)index);
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
}
} }
public Enumerator GetEnumerator() => new Enumerator(in this); public Enumerator GetEnumerator() => new Enumerator(in this);
@@ -74,6 +83,16 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
_count = count; _count = count;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void CheckIndexBounds(int index)
{
if (index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
}
/// <summary> /// <summary>
/// Returns a read-only span that represents the valid elements in the underlying buffer. /// Returns a read-only span that represents the valid elements in the underlying buffer.
/// </summary> /// </summary>

View File

@@ -17,7 +17,25 @@ public unsafe struct UnTypedArray : IUnTypedCollection
public readonly nuint Size => _size; public readonly nuint Size => _size;
public readonly nuint Alignment => _alignment; public readonly nuint Alignment => _alignment;
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid; public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
/// <summary> /// <summary>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator. /// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
@@ -34,8 +52,13 @@ public unsafe struct UnTypedArray : IUnTypedCollection
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero."); throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
} }
if (handle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle; MemoryHandle memHandle;
_buffer = handle.Alloc(_allocationHandle.pAllocator, size, alignment, allocationOption, &memHandle); _buffer = handle.Alloc(_allocationHandle.State, size, alignment, allocationOption, &memHandle);
_size = size; _size = size;
_alignment = alignment; _alignment = alignment;
@@ -87,7 +110,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
} }
MemoryHandle memHandle = _memoryHandle; MemoryHandle memHandle = _memoryHandle;
_buffer = _allocationHandle.Realloc(_allocationHandle.pAllocator, _buffer, _size, newSize, _alignment, option, &memHandle); _buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option, &memHandle);
_size = newSize; _size = newSize;
_memoryHandle = memHandle; _memoryHandle = memHandle;
} }
@@ -232,9 +255,9 @@ public unsafe struct UnTypedArray : IUnTypedCollection
return; return;
} }
if (_allocationHandle.pAllocator != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle); _allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
} }
_buffer = null; _buffer = null;

View File

@@ -102,7 +102,25 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
} }
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid; public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
@@ -130,8 +148,13 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero."); throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero.");
} }
if (handle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle; MemoryHandle memHandle;
var buff = handle.Alloc(handle.pAllocator, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle); var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle);
_buffer = (T*)buff; _buffer = (T*)buff;
_memoryHandle = memHandle; _memoryHandle = memHandle;
@@ -168,6 +191,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void ThrowIfNotCreated() private readonly void ThrowIfNotCreated()
{ {
if (!IsCreated) if (!IsCreated)
@@ -202,6 +226,11 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();
if (_allocationHandle.Realloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support reallocation.");
}
if (newSize == Count) if (newSize == Count)
{ {
return; return;
@@ -209,7 +238,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
MemoryHandle memHandle = _memoryHandle; MemoryHandle memHandle = _memoryHandle;
var elemSize = SizeOf<T>(); var elemSize = SizeOf<T>();
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.pAllocator, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle); _buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle);
_memoryHandle = memHandle; _memoryHandle = memHandle;
_count = newSize; _count = newSize;
} }
@@ -273,9 +302,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return; return;
} }
if (_allocationHandle.pAllocator != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle); _allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
} }
_buffer = null; _buffer = null;

View File

@@ -55,7 +55,6 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
return result; return result;
} }
set set
{ {
var idx = _helper.Find(key); var idx = _helper.Find(key);
@@ -98,7 +97,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// <param name="key">The key to add.</param> /// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param> /// <param name="item">The value to add.</param>
/// <returns>True if the key-value pair was added.</returns> /// <returns>True if the key-value pair was added.</returns>
public bool TryAdd(TKey key, TValue item) public bool TryAdd(in TKey key, TValue item)
{ {
var idx = _helper.TryAdd(key); var idx = _helper.TryAdd(key);
if (idx != -1) if (idx != -1)
@@ -117,7 +116,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// <param name="key">The key to add.</param> /// <param name="key">The key to add.</param>
/// <param name="item">The value to add.</param> /// <param name="item">The value to add.</param>
/// <exception cref="ArgumentException">Thrown if the key was already present.</exception> /// <exception cref="ArgumentException">Thrown if the key was already present.</exception>
public void Add(TKey key, TValue item) public void Add(in TKey key, TValue item)
{ {
var result = TryAdd(key, item); var result = TryAdd(key, item);
if (!result) if (!result)
@@ -131,7 +130,7 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// </summary> /// </summary>
/// <param name="item">The value to remove.</param> /// <param name="item">The value to remove.</param>
/// <returns>True if the value was present.</returns> /// <returns>True if the value was present.</returns>
public bool Remove(TKey key) public bool Remove(in TKey key)
{ {
return -1 != _helper.TryRemove(key); return -1 != _helper.TryRemove(key);
} }
@@ -142,17 +141,38 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// <param name="key">The key to look up.</param> /// <param name="key">The key to look up.</param>
/// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param> /// <param name="item">Outputs the value associated with the key. Outputs default if the key was not present.</param>
/// <returns>True if the key was present.</returns> /// <returns>True if the key was present.</returns>
public bool TryGetValue(TKey key, out TValue item) public bool TryGetValue(in TKey key, out TValue item)
{ {
return _helper.TryGetValue(key, out item); return _helper.TryGetValue(key, out item);
} }
/// <summary>
/// Retrieves the value associated with the specified key, or returns a default value if the key is not found.
/// </summary>
/// <param name="key">The key whose value to retrieve.</param>
/// <param name="defaultValue">The value to return if the specified key does not exist. If not specified, the default value for the type is used.</param>
/// <returns>The value associated with the specified key if the key is found; otherwise, the specified default value.</returns>
public TValue GetValueOrDefault(in TKey key, TValue defaultValue = default)
{
if (_helper.TryGetValue<TValue>(key, out var value))
{
return value;
}
return defaultValue;
}
public ref TValue GetValueRef(in TKey key, out bool exists)
{
return ref _helper.GetValueRef<TValue>(key, out exists);
}
/// <summary> /// <summary>
/// Returns true if a given key is present in this hash map. /// Returns true if a given key is present in this hash map.
/// </summary> /// </summary>
/// <param name="key">The key to look up.</param> /// <param name="key">The key to look up.</param>
/// <returns>True if the key was present.</returns> /// <returns>True if the key was present.</returns>
public bool ContainsKey(TKey key) public bool ContainsKey(in TKey key)
{ {
return -1 != _helper.Find(key); return -1 != _helper.Find(key);
} }

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.3.3</AssemblyVersion> <AssemblyVersion>1.3.5</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
@@ -29,6 +29,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="Collections\FixedString.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedString.gen.cs</LastGenOutput>
</None>
<None Update="Collections\FixedText.tt"> <None Update="Collections\FixedText.tt">
<Generator>TextTemplatingFileGenerator</Generator> <Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedText.gen.cs</LastGenOutput> <LastGenOutput>FixedText.gen.cs</LastGenOutput>
@@ -40,6 +44,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Collections\FixedString.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FixedString.tt</DependentUpon>
</Compile>
<Compile Update="Collections\FixedText.gen.cs"> <Compile Update="Collections\FixedText.gen.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>

View File

@@ -136,7 +136,7 @@ public unsafe struct UniquePtr<T> : IEquatable<UniquePtr<T>>
} }
} }
public ref struct Ref<T> public ref struct Ref<T> : IEquatable<Ref<T>>
{ {
private ref T _value; private ref T _value;
@@ -150,6 +150,11 @@ public ref struct Ref<T>
return ref _value; return ref _value;
} }
public bool Equals(Ref<T> other)
{
return Unsafe.AreSame(ref _value, ref other._value);
}
[Obsolete("Equals() on Ref will always throw an exception. Use the equality operator instead.")] [Obsolete("Equals() on Ref will always throw an exception. Use the equality operator instead.")]
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
public override bool Equals(object? obj) public override bool Equals(object? obj)
@@ -166,7 +171,7 @@ public ref struct Ref<T>
public static bool operator ==(Ref<T> left, Ref<T> right) public static bool operator ==(Ref<T> left, Ref<T> right)
{ {
return Unsafe.AreSame(ref left._value, ref right._value); return left.Equals(right);
} }
public static bool operator !=(Ref<T> left, Ref<T> right) public static bool operator !=(Ref<T> left, Ref<T> right)

View File

@@ -1,5 +1,6 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -89,7 +90,25 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
public readonly bool IsEmpty => !IsCreated || _count == 0; public readonly bool IsEmpty => !IsCreated || _count == 0;
public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid; public readonly bool IsCreated
{
get
{
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
}
return false;
}
}
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset) private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
{ {
@@ -142,6 +161,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_COLLECTION_CHECKS")]
private readonly void ThrowIfNotCreated() private readonly void ThrowIfNotCreated()
{ {
if (!IsCreated) if (!IsCreated)
@@ -191,8 +211,13 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption) private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
{ {
if (_allocationHandle.Alloc == null)
{
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
MemoryHandle memHandle; MemoryHandle memHandle;
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.pAllocator, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle); var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
_buffer = buf; _buffer = buf;
_keys = (TKey*)(_buffer + keyOffset); _keys = (TKey*)(_buffer + keyOffset);
@@ -228,7 +253,10 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
} }
} }
_allocationHandle.Free(_allocationHandle.pAllocator, oldBuffer, oldMemoryHandle); if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, oldBuffer, oldMemoryHandle);
}
} }
public void Resize(int newCapacity) public void Resize(int newCapacity)
@@ -399,6 +427,21 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return false; return false;
} }
public ref TValue GetValueRef<TValue>(in TKey key, out bool exists)
where TValue : unmanaged
{
ThrowIfNotCreated();
var idx = Find(key);
if (idx != -1)
{
exists = true;
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
}
exists = false;
return ref Unsafe.NullRef<TValue>();
}
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index) public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();
@@ -522,9 +565,9 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return; return;
} }
if (_allocationHandle.pAllocator != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle); _allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle);
} }
_buffer = null; _buffer = null;

View File

@@ -1,5 +1,7 @@
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.LowLevel.Utilities; namespace Misaki.HighPerformance.LowLevel.Utilities;
@@ -20,9 +22,16 @@ public static unsafe partial class MemoryUtility
/// <returns>Returns a pointer to the allocated memory block.</returns> /// <returns>Returns a pointer to the allocated memory block.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Malloc(nuint size) public static void* Malloc(nuint size)
{
try
{ {
return NativeMemory.Alloc(size); return NativeMemory.Alloc(size);
} }
catch (Exception)
{
return null;
}
}
/// <summary> /// <summary>
/// Allocates a block of memory of the specified size in bytes and initializes it to zero. /// Allocates a block of memory of the specified size in bytes and initializes it to zero.
@@ -31,9 +40,16 @@ public static unsafe partial class MemoryUtility
/// <returns>Returns a pointer to the allocated and zero-initialized memory block.</returns> /// <returns>Returns a pointer to the allocated and zero-initialized memory block.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Calloc(nuint size) public static void* Calloc(nuint size)
{
try
{ {
return NativeMemory.AllocZeroed(size); return NativeMemory.AllocZeroed(size);
} }
catch (Exception)
{
return null;
}
}
/// <summary> /// <summary>
/// Allocates a block of memory with a specified size and alignment. /// Allocates a block of memory with a specified size and alignment.
@@ -43,9 +59,16 @@ public static unsafe partial class MemoryUtility
/// <returns>Returns a pointer to the allocated memory block.</returns> /// <returns>Returns a pointer to the allocated memory block.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedAlloc(nuint size, nuint alignment) public static void* AlignedAlloc(nuint size, nuint alignment)
{
try
{ {
return NativeMemory.AlignedAlloc(size, alignment); return NativeMemory.AlignedAlloc(size, alignment);
} }
catch (Exception)
{
return null;
}
}
/// <summary> /// <summary>
/// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory. /// Resizes a previously allocated memory block to a new size. It returns a pointer to the reallocated memory.
@@ -55,9 +78,16 @@ public static unsafe partial class MemoryUtility
/// <returns>A pointer to the reallocated memory block, or null if the operation fails.</returns> /// <returns>A pointer to the reallocated memory block, or null if the operation fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Realloc(void* ptr, nuint size) public static void* Realloc(void* ptr, nuint size)
{
try
{ {
return NativeMemory.Realloc(ptr, size); return NativeMemory.Realloc(ptr, size);
} }
catch (Exception)
{
return null;
}
}
/// <summary> /// <summary>
/// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated /// Reallocates memory to a specified size with a given alignment. It returns a pointer to the newly allocated
@@ -69,9 +99,16 @@ public static unsafe partial class MemoryUtility
/// <returns>A pointer to the reallocated memory block, or null if the allocation fails.</returns> /// <returns>A pointer to the reallocated memory block, or null if the allocation fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment) public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
{
try
{ {
return NativeMemory.AlignedRealloc(ptr, size, alignment); return NativeMemory.AlignedRealloc(ptr, size, alignment);
} }
catch (Exception)
{
return null;
}
}
/// <summary> /// <summary>
/// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively. /// Releases the allocated memory pointed to by the given pointer. This helps in managing memory usage effectively.
@@ -130,6 +167,42 @@ public static unsafe partial class MemoryUtility
NativeMemory.Copy(source, destination, size); NativeMemory.Copy(source, destination, size);
} }
/// <summary>
/// Moves a block of memory from a source location to a destination location, handling overlapping regions correctly.
/// </summary>
/// <param name="destination">Indicates the memory address where the data will be moved to.</param>
/// <param name="source">Specifies the memory address from which data will be moved.</param>
/// <param name="size">Defines the number of bytes to be moved from the source to the destination.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemMove(void* destination, void* source, nuint size)
{
// NativeMemory.Copy use memmove internally.
NativeMemory.Copy(source, destination, size);
}
/// <summary>
/// Compares two blocks of memory byte by byte for a specified length.
/// </summary>
/// <param name="ptr1">A pointer to the first block of memory to compare.</param>
/// <param name="ptr2">A pointer to the second block of memory to compare.</param>
/// <param name="size">The number of bytes to compare. Must not exceed the length of either memory block.</param>
/// <returns>A signed integer that indicates the relative order of the memory blocks: less than zero if the first differing
/// byte in ptr1 is less than the corresponding byte in ptr2; zero if all compared bytes are equal; greater than
/// zero if the first differing byte in ptr1 is greater than the corresponding byte in ptr2.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MemCmp(void* ptr1, void* ptr2, nuint size)
{
if (ptr1 == ptr2)
{
return 0;
}
var span1 = new ReadOnlySpan<byte>(ptr1, (int)size);
var span2 = new ReadOnlySpan<byte>(ptr2, (int)size);
return span1.SequenceCompareTo(span2);
}
/// <summary> /// <summary>
/// Calculates the size in bytes of a specified unmanaged type. /// Calculates the size in bytes of a specified unmanaged type.
/// </summary> /// </summary>

View File

@@ -81,7 +81,6 @@ namespace {typeInfo.TypeSymbol.ContainingNamespace.ToDisplayString()}
protected virtual void GenerateTypeStart() protected virtual void GenerateTypeStart()
{ {
sourceBuilder.Append($@" sourceBuilder.Append($@"
//[global::System.Runtime.CompilerServices.SkipLocalsInit]
public partial struct {typeInfo.TypeSymbol.Name} : global::System.IEquatable<{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}> public partial struct {typeInfo.TypeSymbol.Name} : global::System.IEquatable<{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>
{{"); {{");
} }

View File

@@ -61,7 +61,8 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
} }
sourceBuilder.AppendLine(); sourceBuilder.AppendLine();
sourceBuilder.AppendLine($@"
sourceBuilder.AppendLine(@$"
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index] public unsafe ref {typeInfo.ComponentTypeFullName} this[int index]
{{ {{
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
@@ -72,7 +73,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
}} }}
}}"); }}");
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine(@$"
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")] [global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
private void RangeCheck(int index) private void RangeCheck(int index)
{{ {{

View File

@@ -9,6 +9,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
private int _vectorBitsSize; private int _vectorBitsSize;
private int _missingComponentsCount; private int _missingComponentsCount;
private string _componentTypePrefix = null!; private string _componentTypePrefix = null!;
private bool CanUseVectorStorage => _missingComponentsCount == 0;
private readonly List<(string signature, List<string> assignment)> _constructorSignatures = new(); private readonly List<(string signature, List<string> assignment)> _constructorSignatures = new();
@@ -42,6 +43,20 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
}; };
} }
protected override void GenerateTypeStart()
{
if (CanUseVectorStorage)
{
sourceBuilder.Append($@"
[global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit)]
public partial struct {typeInfo.TypeSymbol.Name} : global::System.IEquatable<{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>
{{");
return;
}
base.GenerateTypeStart();
}
protected override void GenerateBody() protected override void GenerateBody()
{ {
GenerateField(); GenerateField();
@@ -81,14 +96,32 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
private void GenerateField() private void GenerateField()
{ {
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
if (CanUseVectorStorage)
{
sourceBuilder.AppendLine($@"
[global::System.Runtime.InteropServices.FieldOffset(0)]
internal global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{typeInfo.ComponentTypeFullName}> __v;");
for (var i = 0; i < typeInfo.Row; i++) for (var i = 0; i < typeInfo.Row; i++)
{ {
sourceBuilder.Append($@" sourceBuilder.AppendLine($@"
public {typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {s_vectorComponents[i]};"); [global::System.Runtime.InteropServices.FieldOffset({i * typeInfo.ComponentSize})]
public {componentType} {s_vectorComponents[i]};");
}
}
else
{
for (var i = 0; i < typeInfo.Row; i++)
{
sourceBuilder.AppendLine($@"
public {componentType} {s_vectorComponents[i]};");
}
} }
sourceBuilder.AppendLine(); sourceBuilder.AppendLine();
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine(@$"
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index] public unsafe ref {typeInfo.ComponentTypeFullName} this[int index]
{{ {{
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
@@ -99,7 +132,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
}} }}
}}"); }}");
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine(@$"
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")] [global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
private void RangeCheck(int index) private void RangeCheck(int index)
{{ {{
@@ -387,16 +420,59 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var asResult = $"As{typeSimpleName}()"; var asResult = $"As{typeSimpleName}()";
var canVectorizeBinaryArithmetic = CanUseVectorStorage;
var canVectorizeDivide = CanUseVectorStorage && (typeInfo.ComponentTypeSymbol.SpecialType == SpecialType.System_Single || typeInfo.ComponentTypeSymbol.SpecialType == SpecialType.System_Double);
StartRegion("Arithmetic Operators"); StartRegion("Arithmetic Operators");
// Add // Add
sourceBuilder.Append($@" if (canVectorizeBinaryArithmetic)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({typeName} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(lhs.__v, rhs.__v)).{asResult};
}}");
}
else
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({typeName} lhs, {typeName} rhs) public static {typeName} operator +({typeName} lhs, {typeName} rhs)
{{ {{
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} + rhs.{c}"))}); return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} + rhs.{c}"))});
}}");
}
if (canVectorizeBinaryArithmetic)
{
sourceBuilder.Append($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({typeName} lhs, {componentType} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
}} }}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({componentType} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
}}
#if NET10_0_OR_GREATER
{INLINE_METHOD_ATTRIBUTE}
public void operator +=({typeName} other)
{{
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Add(this.__v, other.__v);
}}
#endif");
}
else
{
sourceBuilder.Append($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({typeName} lhs, {componentType} rhs) public static {typeName} operator +({typeName} lhs, {componentType} rhs)
{{ {{
@@ -421,15 +497,56 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
}} }}
#endif"); #endif");
}
// Subtract // Subtract
if (canVectorizeBinaryArithmetic)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({typeName} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(lhs.__v, rhs.__v)).{asResult};
}}");
}
else
{
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({typeName} lhs, {typeName} rhs) public static {typeName} operator -({typeName} lhs, {typeName} rhs)
{{ {{
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} - rhs.{c}"))}); return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} - rhs.{c}"))});
}}");
}
if (canVectorizeBinaryArithmetic)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({typeName} lhs, {componentType} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
}} }}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({componentType} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
}}
#if NET10_0_OR_GREATER
{INLINE_METHOD_ATTRIBUTE}
public void operator -=({typeName} other)
{{
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Subtract(this.__v, other.__v);
}}
#endif");
}
else
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({typeName} lhs, {componentType} rhs) public static {typeName} operator -({typeName} lhs, {componentType} rhs)
{{ {{
@@ -455,15 +572,57 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
}} }}
#endif"); #endif");
}
// Multiply // Multiply
if (canVectorizeBinaryArithmetic)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({typeName} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(lhs.__v, rhs.__v)).{asResult};
}}");
}
else
{
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({typeName} lhs, {typeName} rhs) public static {typeName} operator *({typeName} lhs, {typeName} rhs)
{{ {{
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} * rhs.{c}"))}); return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} * rhs.{c}"))});
}}");
}
if (canVectorizeBinaryArithmetic)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({typeName} lhs, {componentType} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
}} }}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({componentType} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
}}
#if NET10_0_OR_GREATER
// Use scaler here to let JIT handle the simd optimization since we can not do a in-place vectorlization manually.
{INLINE_METHOD_ATTRIBUTE}
public void operator *=({typeName} other)
{{
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Multiply(this.__v, other.__v);
}}
#endif");
}
else
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({typeName} lhs, {componentType} rhs) public static {typeName} operator *({typeName} lhs, {componentType} rhs)
{{ {{
@@ -490,15 +649,57 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
}} }}
#endif"); #endif");
}
// Divide // Divide
if (canVectorizeDivide)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({typeName} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(lhs.__v, rhs.__v)).{asResult};
}}");
}
else
{
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({typeName} lhs, {typeName} rhs) public static {typeName} operator /({typeName} lhs, {typeName} rhs)
{{ {{
return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} / rhs.{c}"))}); return new {typeName}({string.Join(", ", s_vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} / rhs.{c}"))});
}}");
}
if (canVectorizeDivide)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({typeName} lhs, {componentType} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(lhs.__v, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(rhs))).{asResult};
}} }}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({componentType} lhs, {typeName} rhs)
{{
return (global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(lhs), rhs.__v)).{asResult};
}}
#if NET10_0_OR_GREATER
// Use scaler here to let JIT handle the simd optimization since we can not do a in-place vectorlization manually.
{INLINE_METHOD_ATTRIBUTE}
public void operator /=({typeName} other)
{{
this.__v = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Divide(this.__v, other.__v);
}}
#endif");
}
else
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({typeName} lhs, {componentType} rhs) public static {typeName} operator /({typeName} lhs, {componentType} rhs)
{{ {{
@@ -525,6 +726,7 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
}} }}
#endif"); #endif");
}
// Modulus // Modulus
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
@@ -735,13 +937,14 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
public static partial class VectorInterop public static partial class VectorInterop
{{ {{
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public unsafe static global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> AsVector{_vectorBitsSize}(this {typeName} value) public unsafe static global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> AsVector{_vectorBitsSize}(this in {typeName} value)
{{"); {{");
if (typeInfo.Row == 4) if (CanUseVectorStorage)
{ {
sourceBuilder.Append($@" sourceBuilder.Append($@"
return global::System.Runtime.CompilerServices.Unsafe.BitCast<{typeName}, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>>(value);"); ref var v = ref global::System.Runtime.CompilerServices.Unsafe.AsRef(in value);
return global::System.Runtime.CompilerServices.Unsafe.As<{typeName}, global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>>(ref v);");
} }
else else
{ {
@@ -754,12 +957,24 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
public unsafe static {typeName} As{typeSimpleName}(this global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> value) public unsafe static {typeName} As{typeSimpleName}(this global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> value)
{{"); {{");
if (CanUseVectorStorage)
{
sourceBuilder.AppendLine($@"
var result = default({typeName});
result.__v = value;
return result;
}}
}}");
}
else
{
sourceBuilder.AppendLine($@" sourceBuilder.AppendLine($@"
ref var address = ref global::System.Runtime.CompilerServices.Unsafe.As<global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>, byte>(ref value); ref var address = ref global::System.Runtime.CompilerServices.Unsafe.As<global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>, byte>(ref value);
return global::System.Runtime.CompilerServices.Unsafe.ReadUnaligned<{typeName}>(ref address); return global::System.Runtime.CompilerServices.Unsafe.ReadUnaligned<{typeName}>(ref address);
}} }}
}}"); }}");
} }
}
private void GenerateMathMethod() private void GenerateMathMethod()
{ {

View File

@@ -1,44 +1,41 @@
#define VECTOR_BENCHMARK #define NOISE_BENCHMARK
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Numerics; using System.Numerics;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.Test.Benchmark; namespace Misaki.HighPerformance.Test.Benchmark;
public class MathematicsBenchmark public class MathematicsBenchmark
{ {
#if VECTOR_BENCHMARK #if VECTOR_BENCHMARK
private Vector2 _v2a = new Vector2(1, 2); private Vector4 _va = new Vector4(1, 2, 1, 2);
private Vector2 _v2b = new Vector2(3, 4); private Vector4 _vb = new Vector4(3, 4, 3, 4);
private float2 _f2a = new float2(1, 2); private float4 _fa = new float4(1, 2, 1, 2);
private float2 _f2b = new float2(3, 4); private float4 _fb = new float4(3, 4, 3, 4);
[Benchmark] [Benchmark]
public Vector2 VectorAdd() public Vector4 VectorAdd()
{ {
var v = new Vector2(0, 0);
for (var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
v = _v2a + _v2b; _va += _vb;
} }
return v; return _va;
} }
[Benchmark] [Benchmark]
public float2 float2Add() public float4 floatAdd()
{ {
var v = new float2(0, 0);
for (var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
v = _f2a + _f2b; _fa += _fb;
} }
return v; return _fa;
} }
#endif #endif
@@ -47,7 +44,7 @@ public class MathematicsBenchmark
private const int _SIZE = 32; private const int _SIZE = 32;
[Benchmark] [Benchmark]
public void VectorNoise() public unsafe void VectorNoise()
{ {
var buf = stackalloc float[_SIZE * _SIZE]; var buf = stackalloc float[_SIZE * _SIZE];
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobVector var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobVector
@@ -64,7 +61,7 @@ public class MathematicsBenchmark
} }
[Benchmark] [Benchmark]
public void MathNoise() public unsafe void MathNoise()
{ {
var buf = stackalloc float[_SIZE * _SIZE]; var buf = stackalloc float[_SIZE * _SIZE];
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobMath var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobMath
@@ -79,6 +76,24 @@ public class MathematicsBenchmark
job.Execute(i, 0); job.Execute(i, 0);
} }
} }
[Benchmark]
// This is 10x faster than VectorNoise and MathNoise, but writing a burst like compiler to compile MathNoise into this is incredibly hard.
public unsafe void MathVNoise()
{
var buf = stackalloc float[_SIZE * _SIZE];
var job = new Misaki.HighPerformance.Test.Jobs.NoiseJobMathV
{
buffers = buf,
width = _SIZE,
height = _SIZE,
};
for (var i = 0; i < _SIZE * _SIZE / 8; i++)
{
job.Execute(i, 0);
}
}
#endif #endif
#if MATRIX_BENCHMARK #if MATRIX_BENCHMARK

View File

@@ -2,6 +2,8 @@ using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Misaki.HighPerformance.Test.Jobs; namespace Misaki.HighPerformance.Test.Jobs;
@@ -12,7 +14,7 @@ internal unsafe struct NoiseJobVector : IJobParallelFor
public int height; public int height;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float Frac(float x) public static float Frac(float x)
{ {
return x - MathF.Truncate(x); return x - MathF.Truncate(x);
} }
@@ -88,3 +90,101 @@ internal unsafe struct NoiseJobMath : IJobParallelFor
buffers[loopIndex] = GradientNoise(uv); buffers[loopIndex] = GradientNoise(uv);
} }
} }
internal unsafe struct NoiseJobMathV : IJobParallelFor
{
public float* buffers;
public int width;
public int height;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Mod289(Vector256<float> x)
{
var div = x / Vector256.Create(289.0f);
var flr = Vector256.Floor(div);
return x - (flr * Vector256.Create(289.0f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> Fade(Vector256<float> t)
{
return t * t * t * (t * (t * Vector256.Create(6.0f) - Vector256.Create(15.0f)) + Vector256.Create(10.0f));
}
// HELPER: Calculate gradients for 8 pixels at once
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<float> GradDot(Vector256<float> ix, Vector256<float> iy, Vector256<float> fx, Vector256<float> fy)
{
var hx = Mod289(ix);
var hy = Mod289(iy);
var p = hx * Vector256.Create(34.0f) + Vector256.Create(1.0f);
p = Mod289(p * hx + hy);
p = p * Vector256.Create(34.0f) + Vector256.Create(1.0f);
p = Mod289(p * hx);
var r = (p / 41.0f);
r = (r - Vector256.Floor(r)) * 2.0f - Vector256<float>.One;
var gx = r - Vector256.Floor(r + Vector256.Create(0.5f));
var gy = Vector256.Abs(r) - Vector256.Create(0.5f);
// Normalize
var len = Vector256.Sqrt(gx * gx + gy * gy);
gx /= len;
gy /= len;
return gx * fx + gy * fy;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> GradientNoiseAVX(Vector256<float> uvX, Vector256<float> uvY)
{
var ipX = Vector256.Floor(uvX);
var ipY = Vector256.Floor(uvY);
var fpX = uvX - ipX;
var fpY = uvY - ipY;
var uX = Fade(fpX);
var uY = Fade(fpY);
var d00 = GradDot(ipX, ipY, fpX, fpY);
var d01 = GradDot(ipX, ipY + Vector256<float>.One, fpX, fpY - Vector256<float>.One);
var d10 = GradDot(ipX + Vector256<float>.One, ipY, fpX - Vector256<float>.One, fpY);
var d11 = GradDot(ipX + Vector256<float>.One, ipY + Vector256<float>.One, fpX - Vector256<float>.One, fpY - Vector256<float>.One);
var lerpX1 = d00 + (d10 - d00) * uX;
var lerpX2 = d01 + (d11 - d01) * uX;
return lerpX1 + (lerpX2 - lerpX1) * uY;
}
public void Execute(int loopIndex, int threadIndex)
{
// ---------------------------------------------------------
// IMPORTANT: Loop Stride is now 8!
// ---------------------------------------------------------
int baseIndex = loopIndex * 8;
// Safety check
if (baseIndex + 7 >= width * height)
return;
// Calculate Coords
int y = baseIndex / width;
int x = baseIndex % width;
// Sequence: 0, 1, 2, 3, 4, 5, 6, 7
var vSeqX = Vector256.Create(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f);
var vBaseX = Vector256.Create((float)x) + vSeqX;
var vBaseY = Vector256.Create((float)y);
var vWidth = Vector256.Create((float)width);
var vHeight = Vector256.Create((float)height);
var result = GradientNoiseAVX(vBaseX / vWidth, vBaseY / vHeight);
// Store 8 floats (32 bytes)
Avx.Store(buffers + baseIndex, result);
}
}

View File

@@ -1,6 +1,8 @@
using Misaki.HighPerformance.Image; using Misaki.HighPerformance;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Utilities;
//BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmark.ParallelNoiseBenchmark>(); BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmark.MathematicsBenchmark>();
//using Misaki.HighPerformance.Collections; //using Misaki.HighPerformance.Collections;
//using Misaki.HighPerformance.LowLevel.Buffer; //using Misaki.HighPerformance.LowLevel.Buffer;
@@ -38,11 +40,14 @@ using Misaki.HighPerformance.Image;
// 4, 3, 2, 1); // 4, 3, 2, 1);
//Console.WriteLine(Matrix4x4.Multiply(ma, mb)); //Console.WriteLine(Matrix4x4.Multiply(ma, mb));
//int[] arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
//int[] arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const string _IMAGE_PATH = "C:/Users/Misaki/Downloads/Im/119683453_p2.jpg"; //unsafe
//{
using var stream = File.OpenRead(_IMAGE_PATH); // fixed (int* p1 = arr1)
var imageInfo = ImageInfo.FromStream(stream); // fixed (int* p2 = arr2)
using var image = ImageResult.FromStream(stream); // {
// Console.WriteLine(MemoryUtility.MemCmp(p1, p2, (nuint)(arr1.Length * sizeof(int))));
Console.WriteLine($"{imageInfo.Width}x{imageInfo.Height} {imageInfo.ColorComponents}"); // }
//}

View File

@@ -20,6 +20,12 @@ public class TestUnsafeArray
_arr.Dispose(); _arr.Dispose();
} }
[GlobalTestCleanup]
public static void GlobalCleanup(TestContext ctx)
{
AllocationManager.Dispose();
}
[TestMethod] [TestMethod]
public void TestIndexAccess() public void TestIndexAccess()
{ {

View File

@@ -7,28 +7,24 @@ namespace Misaki.HighPerformance.Buffer
where T : class where T : class
{ {
private readonly Func<T> _factory; private readonly Func<T> _factory;
private readonly Action<T>? _resetAction;
private readonly ConcurrentQueue<T> _pool = new(); private readonly ConcurrentQueue<T> _pool = new();
private bool _disposed; private bool _disposed;
public uint InitialSize public int InitialSize
{ {
get; get;
} }
public uint MaxSize public ObjectPool(Func<T> factory, Action<T>? resetAction, int initialSize = 0)
{
get;
}
public ObjectPool(Func<T> factory, uint initialSize = uint.MinValue, uint maxSize = uint.MaxValue)
{ {
_factory = factory; _factory = factory;
_resetAction = resetAction;
InitialSize = initialSize; InitialSize = initialSize;
MaxSize = maxSize;
if (initialSize != uint.MinValue) if (initialSize > 0)
{ {
for (var i = 0; i < initialSize; i++) for (var i = 0; i < initialSize; i++)
{ {
@@ -51,8 +47,6 @@ namespace Misaki.HighPerformance.Buffer
} }
var newInstance = _factory(); var newInstance = _factory();
_pool.Enqueue(newInstance);
return newInstance; return newInstance;
} }
@@ -73,20 +67,15 @@ namespace Misaki.HighPerformance.Buffer
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
if (_pool.Count < MaxSize) _resetAction?.Invoke(obj);
{
_pool.Enqueue(obj); _pool.Enqueue(obj);
} }
}
public void Reset() public void Reset()
{ {
foreach (var obj in _pool) foreach (var obj in _pool)
{ {
if (obj is IDisposable disposable) _resetAction?.Invoke(obj);
{
disposable.Dispose();
}
} }
_pool.Clear(); _pool.Clear();

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.0.3</AssemblyVersion> <AssemblyVersion>1.0.4</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>

View File

@@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance;
[StructLayout(LayoutKind.Explicit)]
public struct Union<T0, T1>
where T0 : unmanaged
where T1 : unmanaged
{
[FieldOffset(0)]
public T0 v0;
[FieldOffset(0)]
public T1 v1;
}