redesign matrix design

This commit is contained in:
Youmu Liu
2025-12-23 18:13:05 +08:00
parent b32c1cb4eb
commit b60ce56f1c
12 changed files with 493 additions and 225 deletions
@@ -44,6 +44,8 @@ namespace StgSharp.HighPerformance
public unsafe struct MatrixKernel<T> where T : unmanaged, INumber<T>
{
public static readonly int Size = 16 * sizeof(T);
private T c0r0;
public MatrixKernel()
@@ -58,10 +60,10 @@ namespace StgSharp.HighPerformance
}
}
public ref T this[int column, int row]
public ref T this[int row, int column]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref Unsafe.Add(ref c0r0, (column * 4) + row);
get => ref Unsafe.Add(ref c0r0, (row * 4) + column);
}
}
@@ -0,0 +1,88 @@
# Matrix Coordinate & Naming Conventions (StgSharp)
This document defines the coordinate terminology and naming rules used across `Matrix<T>`, `MatrixKernel<T>`, and packed/triangular storage layouts.
Goal: eliminate ambiguity around `column`/`row` names (e.g., “index **of** a column” vs “index **along** a column”).
---
## 1) The key rule: encode prepositions into identifiers (`Of` vs `In`)
### `...OfX` — “which X” (object identity / ordinal)
Use **`Of`** when the value identifies **which object** (which column, which row, which kernel, etc.).
Examples:
- `columnOfElement`: the element is located in **which column**
- `rowOfKernel`: the kernel block is located in **which row**
### `...InX` / `...WithinX` — “inside X” (local coordinate)
Use **`In`** (or `Within`) when the value is a **local coordinate inside** another object.
Examples:
- `columnInKernel`: local column coordinate **inside** a kernel block
- `rowInKernel`: local row coordinate **inside** a kernel block
> Quick mental model: **`Of` = identity**, **`In` = inside**.
---
## 2) Coordinate levels: `Element`, `KernelGrid`, `InKernel`
StgSharp commonly uses three coordinate levels:
1. **Element-level**: final matrix element coordinates (`Matrix<T>[col,row]`)
2. **Kernel-grid level**: coordinates of a kernel block in the kernel grid
3. **In-kernel level**: local element coordinates within a kernel (e.g., 4×4)
Recommended names:
| Level | Column name | Row name | Meaning |
|---|---|---|---|
| Element | `columnOfElement` | `rowOfElement` | element located in which column/row (0-based) |
| Kernel grid | `columnOfKernel` | `rowOfKernel` | kernel block located in which kernel-column/kernel-row (0-based) |
| In-kernel | `columnInKernel` | `rowInKernel` | local coordinate inside the kernel (0..KernelSize-1) |
---
## 3) Index base: 0-based (mandatory)
Unless explicitly documented otherwise, all coordinates are **0-based**:
- `columnOfElement ∈ [0, ColumnLength-1]`
- `rowOfElement ∈ [0, RowLength-1]`
- `columnOfKernel ∈ [0, KernelColumnLength-1]`
- `rowOfKernel ∈ [0, KernelRowLength-1]`
- `columnInKernel ∈ [0, KernelSize-1]`
- `rowInKernel ∈ [0, KernelSize-1]`
---
## 4) Mapping element coordinates to kernel coordinates (KernelSize = 4 example)
For a fixed kernel size `KernelSize = 4`:
```csharp
int columnOfKernel = columnOfElement / 4;
int rowOfKernel = rowOfElement / 4;
int columnInKernel = columnOfElement % 4;
int rowInKernel = rowOfElement % 4;
```
If `columnOfElement` and `rowOfElement` are guaranteed non-negative, bit operations can be used:
```csharp
int columnOfKernel = columnOfElement >> 2;
int rowOfKernel = rowOfElement >> 2;
int columnInKernel = columnOfElement & 3;
int rowInKernel = rowOfElement & 3;
```
---
## 5) Packed storage: distinguish `Index` from `Offset`
Packed layouts do **not** store “empty blocks”; stored blocks are placed contiguously in a packed buffer.
### Use the correct terms
- **`packedKernelIndex`**: “the N-th kernel
@@ -25,10 +25,12 @@
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.HighPerformance;
using StgSharp.HighPerformance.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
@@ -37,21 +39,72 @@ namespace StgSharp.Mathematics.Numeric
public unsafe partial class Matrix<T>
{
private const int KernelSideLength = 4;
public static Matrix<T> Create(
int columnLength,
int rowLength,
MatrixLayout layout,
HybridLayerSegregatedFitAllocator allocator)
{
Matrix<T> matrix = new Matrix<T>();
int tSize = sizeof(T);
int totalSize = columnLength * rowLength * tSize;
HybridLayerSegregatedFitAllocationHandle handle = allocator.Alloc((uint)totalSize);
MatrixStorageHLSF<T> storage = new MatrixStorageHLSF<T>(handle);
storage._handle = handle;
ArgumentNullException.ThrowIfNull(allocator);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(columnLength);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(rowLength);
int kernelColumns = DivideRoundUp(columnLength, KernelSideLength);
int kernelRows = DivideRoundUp(rowLength, KernelSideLength);
if (layout is MatrixLayout.UpperTriangle or MatrixLayout.LowerTriangle)
{
if (kernelColumns != kernelRows) {
throw new ArgumentException("Triangular layouts require square matrices.");
}
}
long kernelCount = layout switch
{
MatrixLayout.DenseSquare => (long)kernelColumns * kernelRows,
MatrixLayout.UpperTriangle => TriangularKernelCount(kernelColumns),
MatrixLayout.LowerTriangle => TriangularKernelCount(kernelRows),
_ => throw new ArgumentOutOfRangeException(nameof(layout))
};
long requiredBytes = kernelCount * MatrixKernel<T>.Size;
if (requiredBytes <= 0 || requiredBytes > uint.MaxValue) {
throw new ArgumentOutOfRangeException(nameof(requiredBytes), "Requested matrix exceeds allocator capacity.");
}
HybridLayerSegregatedFitAllocationHandle handle = allocator.Alloc((uint)requiredBytes);
MatrixStorageHLSF<T> storage = new MatrixStorageHLSF<T>(allocator, handle);
Matrix<T> matrix = new Matrix<T>
{
Layout = layout,
KernelColumnLength = kernelColumns,
KernelRowLength = kernelRows,
};
matrix._buffer = storage;
matrix._enumeration = new MatrixMetadata(columnLength, rowLength);
matrix._boarder = MatrixKernelBoarder.Create(layout);
matrix._size = new MatrixSize(columnLength, rowLength, kernelColumns, kernelRows);
return matrix;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int DivideRoundUp(int value, int divisor)
{
return (value + (divisor - 1)) / divisor;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static long TriangularKernelCount(int order)
{
long n = order;
return (n * (n + 1L)) / 2L;
}
}
}
+62 -20
View File
@@ -30,7 +30,6 @@ using StgSharp.Mathematics.Numeric;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// using hlsfHandle = global::StgSharp.HighPerformance.Memory.HybridLayerSegregatedFitAllocationHandle;
@@ -42,17 +41,61 @@ namespace StgSharp.Mathematics.Numeric
private protected static readonly int t_count = 16 / sizeof(T);
private protected static readonly int t_size = sizeof(T);
private MatrixMetadata _enumeration;
private MatrixKernelBoarder _boarder;
private MatrixSize _size;
private MatrixStorage<T> _buffer;
private protected Matrix() { }
public ref T this[int column, int row]
public T this[int x, int y]
{
get => throw new NotImplementedException();
get
{
if (y < 0 || y > _size.ElementColumnLength || x < 0 || x > _size.ElementRowLength) {
throw new ArgumentOutOfRangeException($"Index [{x},{y}] is out of matrix index range.");
}
int
k_c = y / 4,
k_r = x / 4,
c = y % 4,
r = x % 4;
ref MatrixKernel<T> kernel = ref GetMatrixKernel(k_c, k_r);
return Unsafe.IsNullRef(ref kernel) ? T.Zero : kernel[c, r];
}
set
{
if (y < 0 || y > _size.ElementColumnLength || x < 0 || x > _size.ElementRowLength) {
throw new ArgumentOutOfRangeException($"Index [{x},{y}] is out of matrix index range.");
}
int
k_c = y / 4,
k_r = x / 4,
c = y % 4,
r = x % 4;
ref MatrixKernel<T> kernel = ref GetMatrixKernel(k_c, k_r);
if (!Unsafe.IsNullRef(ref kernel)) {
kernel[c, r] = value;
}
}
}
/// <summary>
/// Matlab style indexer to generate a new matrix
/// </summary>
/// <param name="column">
///
/// </param>
/// <param name="row">
///
/// </param>
/// <returns>
///
/// </returns>
/// <exception cref="NotImplementedException">
///
/// </exception>
public unsafe Matrix<T> this[
ReadOnlySpan<MatrixIndex> column,
ReadOnlySpan<MatrixIndex> row]
@@ -64,34 +107,33 @@ namespace StgSharp.Mathematics.Numeric
/// <summary>
/// Length of the matrix in columns.
/// </summary>
public int ColumnLength { get; init; }
public int ColumnLength => _size.ElementColumnLength;
/// <summary>
/// Length of the matrix in rows.
/// </summary>
public int RowLength { get; init; }
public int RowLength => _size.ElementRowLength;
public MatrixLayout Layout { get; init; }
internal MatrixKernel<T>* Buffer { get; }
internal MatrixKernel<T>* Buffer
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (MatrixKernel<T>*)_buffer.BufferPointer;
}
protected internal int KernelColumnLength { get; init; }
protected internal int KernelRowLength { get; init; }
public ref MatrixKernel<T> GetMatrixKernel(int column, int row)
public unsafe ref MatrixKernel<T> GetMatrixKernel(int x, int y)
{
if (_enumeration.GetCurrentKernelIndex) { } else { }
}
private protected void AssertIndex(int column, int row)
{
if (column < 0 || column >= ColumnLength) {
throw new ArgumentOutOfRangeException(nameof(column));
}
if (row < 0 || row >= RowLength) {
throw new ArgumentOutOfRangeException(nameof(row));
fixed (MatrixSize *msPtr = &_size)
{
MatrixKernelEnumerator* e = (MatrixKernelEnumerator*)msPtr;
return ref _boarder.IsInBuffer(x, y, e) ?
ref _buffer[_boarder.GetIndexOffset(x, y, e)] :
ref Unsafe.NullRef<MatrixKernel<T>>();
}
}
@@ -46,7 +46,7 @@ namespace StgSharp.Mathematics.Numeric
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct MatrixKernelEnumeration
public unsafe struct MatrixKernelBoarder
{
public readonly delegate* unmanaged[Cdecl]
@@ -55,9 +55,9 @@ namespace StgSharp.Mathematics.Numeric
public readonly delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool> IsInBuffer;
[Obsolete("Do not use constructor, use static method instead", true)]
public MatrixKernelEnumeration() { }
public MatrixKernelBoarder() { }
public MatrixKernelEnumeration(
public MatrixKernelBoarder(
delegate* unmanaged[Cdecl]
<int, int, MatrixKernelEnumerator*, long> getOffset,
delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool> inBuffer)
@@ -66,24 +66,128 @@ namespace StgSharp.Mathematics.Numeric
IsInBuffer = inBuffer;
}
private static delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool> IsInBufferDenseSquarePtr { get; } = (delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool>)&IsInBufferDenseSquare;
private static delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, long> GetIndexOffsetDenseSquarePtr { get; } =
(delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, long>)&GetIndexOffsetDenseSquare;
public static MatrixKernelEnumeration Create(MatrixLayout layout)
private static delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, long> GetIndexOffsetUpperTrianglePtr { get; } =
(delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, long>)&GetIndexOffsetUpperTriangle;
private static delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, long> GetIndexOffsetLowerTrianglePtr { get; } =
(delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, long>)&GetIndexOffsetLowerTriangle;
private static delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool> IsInBufferDenseSquarePtr { get; } =
(delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool>)&IsInBufferDenseSquare;
private static delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool> IsInBufferUpperTrianglePtr { get; } =
(delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool>)&IsInBufferUpperTriangle;
private static delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool> IsInBufferLowerTrianglePtr { get; } =
(delegate* unmanaged[Cdecl]<int, int, MatrixKernelEnumerator*, bool>)&IsInBufferLowerTriangle;
public static MatrixKernelBoarder Create(MatrixLayout layout)
{
return layout switch
{
MatrixLayout.DenseSquare => new MatrixKernelEnumeration(null, IsInBufferDenseSquarePtr),
MatrixLayout.DenseSquare => new MatrixKernelBoarder(GetIndexOffsetDenseSquarePtr, IsInBufferDenseSquarePtr),
MatrixLayout.UpperTriangle => throw new NotImplementedException(),
MatrixLayout.UpperTriangle => new MatrixKernelBoarder(GetIndexOffsetUpperTrianglePtr, IsInBufferUpperTrianglePtr),
MatrixLayout.LowerTriangle => new MatrixKernelBoarder(GetIndexOffsetLowerTrianglePtr, IsInBufferLowerTrianglePtr),
_ => throw new ArgumentOutOfRangeException(nameof(layout)),
};
}
private static bool IsInBufferDenseSquare(
int col,
int row,
[UnmanagedCallersOnly(CallConvs =[ typeof(CallConvCdecl) ])]
private static long GetIndexOffsetDenseSquare(
int x_kernel,
int y_kernel,
MatrixKernelEnumerator* enumerator)
{
return true;
long x = x_kernel;
long y = y_kernel;
long rows = enumerator->KernelRowLength;
return x * rows + y;
}
[UnmanagedCallersOnly(CallConvs =[ typeof(CallConvCdecl) ])]
private static long GetIndexOffsetLowerTriangle(
int x_kernel,
int y_kernel,
MatrixKernelEnumerator* enumerator)
{
long order = GetTriangularOrder(enumerator);
long x = x_kernel;
long y = y_kernel;
long prefixColumns = x * order - (x * (x - 1L)) / 2L;
return prefixColumns + (y - x);
}
[UnmanagedCallersOnly(CallConvs =[ typeof(CallConvCdecl) ])]
private static long GetIndexOffsetUpperTriangle(
int x_kernel,
int y_kernel,
MatrixKernelEnumerator* enumerator)
{
long x = x_kernel;
long y = y_kernel;
long prefixColumns = (x * (x + 1L)) / 2L;
return prefixColumns + y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static long GetTriangularOrder(MatrixKernelEnumerator* enumerator)
{
return Math.Min(enumerator->KernelColumnLength, enumerator->KernelRowLength);
}
[UnmanagedCallersOnly(CallConvs =[ typeof(CallConvCdecl) ])]
private static bool IsInBufferDenseSquare(
int x_kernel,
int y_kernel,
MatrixKernelEnumerator* enumerator)
{
return IsWithinKernelDomain(enumerator, x_kernel, y_kernel);
}
[UnmanagedCallersOnly(CallConvs =[ typeof(CallConvCdecl) ])]
private static bool IsInBufferLowerTriangle(
int x_kernel,
int y_kernel,
MatrixKernelEnumerator* enumerator)
{
return IsWithinTriangularDomain(enumerator, x_kernel, y_kernel) && y_kernel >= x_kernel;
}
[UnmanagedCallersOnly(CallConvs =[ typeof(CallConvCdecl) ])]
private static bool IsInBufferUpperTriangle(
int x_kernel,
int y_kernel,
MatrixKernelEnumerator* enumerator)
{
return IsWithinTriangularDomain(enumerator, x_kernel, y_kernel) && y_kernel <= x_kernel;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsWithinKernelDomain(
MatrixKernelEnumerator* enumerator,
int x_kernel,
int y_kernel)
{
return x_kernel >= 0 &&
y_kernel >= 0 &&
x_kernel < enumerator->KernelColumnLength &&
y_kernel < enumerator->KernelRowLength;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsWithinTriangularDomain(
MatrixKernelEnumerator* enumerator,
int x_kernel,
int y_kernel)
{
long order = GetTriangularOrder(enumerator);
return x_kernel >= 0 && y_kernel >= 0 && x_kernel < order && y_kernel < order;
}
}
@@ -98,6 +202,18 @@ namespace StgSharp.Mathematics.Numeric
[FieldOffset(8)] public readonly int KernelColumnLength;
[FieldOffset(12)] public readonly int KernelRowLength;
public MatrixSize(
int elementColumnLength,
int elementRowLength,
int kernelColumnLength,
int kernelRowLength)
{
ElementColumnLength = elementColumnLength;
ElementRowLength = elementRowLength;
KernelColumnLength = kernelColumnLength;
KernelRowLength = kernelRowLength;
}
}
[StructLayout(LayoutKind.Explicit)]
@@ -25,11 +25,13 @@
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.HighPerformance;
using StgSharp.HighPerformance.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
@@ -41,6 +43,11 @@ namespace StgSharp.Mathematics.Numeric
public abstract class MatrixStorage<T> : IDisposable where T : unmanaged, INumber<T>
{
internal unsafe ref MatrixKernel<T> this[long index]
{
get { return ref Unsafe.AsRef<MatrixKernel<T>>(BufferPointer + MatrixKernel<T>.Size * index); }
}
protected internal abstract unsafe T* BufferPointer { get; }
public abstract void Dispose();