From 9b5dfec9d55f6fb378931c99229b0862c383c62c Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Wed, 11 Mar 2026 18:55:45 +0800 Subject: [PATCH] some chore --- StgSharp.Common/Collections/UnmanagedArray.cs | 176 ++++++++++++++++++ .../HighPerformance/Memory/L4.InternalOp.cs | 81 ++++++++ .../HighPerformance/Memory/L4.Prefetch.cs | 4 + .../HighPerformance/Memory/L4_design.md | 12 +- StgSharp.Common/Timing/StgSharpTime.cs | 15 +- StgSharp.Common/Timing/TimeSequence.cs | 28 ++- StgSharp.Common/Timing/TimeSpanProvider.cs | 29 ++- 7 files changed, 313 insertions(+), 32 deletions(-) create mode 100644 StgSharp.Common/Collections/UnmanagedArray.cs create mode 100644 StgSharp.Common/HighPerformance/Memory/L4.InternalOp.cs diff --git a/StgSharp.Common/Collections/UnmanagedArray.cs b/StgSharp.Common/Collections/UnmanagedArray.cs new file mode 100644 index 00000000..8df56eef --- /dev/null +++ b/StgSharp.Common/Collections/UnmanagedArray.cs @@ -0,0 +1,176 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="UnmanagedArray" +// Project: StgSharp +// AuthorGroup: Nitload +// Copyright (c) Nitload. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StgSharp.Common.Collections +{ + /// + /// Zero-overhead view over a contiguous region of unmanaged memory. Does not own the memory — + /// caller is responsible for lifetime management. + /// + /// + /// Element type. Must be unmanaged. + /// + public readonly unsafe struct UnmanagedArray where T : unmanaged + { + + private static readonly nint ElementSize = sizeof(T); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UnmanagedArray( + T* pointer, + int length + ) + { + Address = pointer; + Length = length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UnmanagedArray( + nuint address, + int length + ) + { + Address = (T*)address; + Length = length; + } + + /// + /// Indexed access. No bounds check in Release builds. + /// + public ref T this[ + int index + ] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Debug.Assert((uint)index < (uint)Length, + "UnmanagedArray index out of range"); + return ref Address[index]; + } + } + + /// + /// Raw pointer to the first element. + /// + public T* Address + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + init; + } + + /// + /// Element count. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + init; + } + + /// + /// Total byte size of the array (for allocation calculations). + /// + public int ByteSize => Length * sizeof(T); + + /// + /// Wraps the array as a . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() + { + return new Span(Address, Length); + } + + /// + /// Wraps a sub-range as a . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan( + int start, + int length + ) + { + Debug.Assert((uint)start + (uint)length <= (uint)Length, + "UnmanagedArray.AsSpan range out of bounds"); + return new Span(Address + start, length); + } + + /// + /// Zero-fill all elements. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + Unsafe.InitBlock(Address, 0, (uint)(Length * sizeof(T))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf( + T* ptr + ) + { + int offset = (int)(((nint)ptr - (nint)Address) / ElementSize); + ArgumentOutOfRangeException.ThrowIfNegative(offset, nameof(ptr)); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(offset, Length, nameof(ptr)); + return offset; + } + + /// + /// Returns a pointer to element at . Useful for feeding SIMD + /// load instructions (e.g. Sse2.LoadVector128). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T* PtrAt( + int index + ) + { + Debug.Assert((uint)index < (uint)Length, + "UnmanagedArray.PtrAt index out of range"); + return Address + index; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int UncheckedIndexOf( + T* ptr + ) + { + return (int)(((nint)ptr - (nint)Address) / ElementSize); + } + + } +} diff --git a/StgSharp.Common/HighPerformance/Memory/L4.InternalOp.cs b/StgSharp.Common/HighPerformance/Memory/L4.InternalOp.cs new file mode 100644 index 00000000..55f703f5 --- /dev/null +++ b/StgSharp.Common/HighPerformance/Memory/L4.InternalOp.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="L4.InternalOp" +// Project: StgSharp +// AuthorGroup: Nitload +// Copyright (c) Nitload. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Text; +using System.Threading.Tasks; + +namespace StgSharp.HighPerformance.Memory +{ + public partial class L4 + { + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void AgeAll() + { + Vector128 one = Vector128.Create((byte)0x01); + Vector128 max = Vector128.Create((byte)0x03); + byte* p = (byte*)_eviction; + for (int i = 0; i < CacheLineCount; i += 16) + { + Vector128 v = Sse2.LoadVector128(p + i); + v = Sse2.AddSaturate(v, one); + v = Sse2.Min(v, max); + Sse2.Store(p + i, v); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void Evict( + int count = 1 + ) + { + int remain = count; + Vector128 one = Vector128.Create((byte)0x01); + Vector128 three = Vector128.Create((byte)0x03); + byte* p = (byte*)_eviction; + for (int i = 0; i < CacheLineCount && remain > 0; i += 16) + { + Vector128 v = Sse2.LoadVector128(p + i); + Vector128 mask = Sse2.CompareEqual(v, three); + int evictMask = Sse2.MoveMask(mask); + while (evictMask != 0 && remain > 0) + { + int index = BitOperations.TrailingZeroCount(evictMask); + evictMask &= (evictMask - 1); + EvictLine(i + index); + remain--; + } + } + } + + } +} diff --git a/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs b/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs index 31817265..bb1838e5 100644 --- a/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs +++ b/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs @@ -45,6 +45,7 @@ namespace StgSharp.HighPerformance.Memory Span span = new(_prediction, PredictionCount); int actualCount = PrefetchPredict.Predict(span); int missIndex = 0; + AgeAll(); for (int i = 0; i < actualCount; i++) { ref CacheLinePrediction handle = ref span[i]; @@ -62,6 +63,9 @@ namespace StgSharp.HighPerformance.Memory { ref CacheLinePrediction handle = ref span[i]; nuint address = handle.Address; + nuint mapPtr = _cacheLineAllocator.Allocate(); + CacheLineHead* head = (CacheLineHead*)_entryAllocator.Allocate(); + // pre fetch them here } diff --git a/StgSharp.Common/HighPerformance/Memory/L4_design.md b/StgSharp.Common/HighPerformance/Memory/L4_design.md index 66a26414..5d24baf9 100644 --- a/StgSharp.Common/HighPerformance/Memory/L4_design.md +++ b/StgSharp.Common/HighPerformance/Memory/L4_design.md @@ -171,15 +171,7 @@ SoA 布局 — 平行数组 **逐出标志位布局**(`_evictionFlags[i]`): -``` - bit: 7 6 5 4 3 2 1 0 - ───────── ─ ───── ─ - 保留 C RRPV D - - D = bit 0 = dirty(0=只读副本, 1=有脏数据待写回) - RRPV = bit 1-2 = Re-Reference Prediction Value(0~3) - C = bit 3 = costClass(0=低成本, 1=高成本) -``` +RRPV数组使用byte存储每一个cacheline的使用情况,其低二位表示如下逻辑 **RRPV 语义**: @@ -203,7 +195,7 @@ SoA 布局 — 平行数组 | 对比项 | v0.1 环形链表 | v0.3 RRPV 掩码 + 回调 | |---------------|------------------------|------------------------------| | 数据结构 | 4 条双向链表 + next 指针 | byte 数组 + 函数指针数组 | -| 升温操作 | 摘节点 + 插节点(4 指针)| `&= 0xF9`(1 字节写入) | +| 升温操作 | 摘节点 + 插节点(4 指针)| `+= 1`(1 字节写入) | | 老化操作 | 逐节点移链表 | SIMD 批量 +1(16 个/周期) | | 逐出扫描 | 遍历链表头(指针追逐) | SIMD 模式匹配(16 个/周期) | | 缓存友好性 | 差(指针跳跃) | 好(连续内存扫描) | diff --git a/StgSharp.Common/Timing/StgSharpTime.cs b/StgSharp.Common/Timing/StgSharpTime.cs index 61308443..440da94a 100644 --- a/StgSharp.Common/Timing/StgSharpTime.cs +++ b/StgSharp.Common/Timing/StgSharpTime.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------- +//----------------------------------------------------------------------- // ----------------------------------------------------------------------- // file="StgSharpTime" // Project: StgSharp @@ -54,7 +54,10 @@ namespace StgSharp public static StgSharpTime OnlyInstance { get; } = new StgSharpTime(); - public override long GetCurrentTimeSpanTick() => mainProvider.ElapsedTicks; + public override long GetCurrentTimeSpanTick() + { + return mainProvider.ElapsedTicks; + } public sealed override void StartProvidingTime() { @@ -85,12 +88,16 @@ namespace StgSharp } } - protected sealed override void AddSubscriberUnsynced(TimeSpanProvider subscriber) + protected sealed override void AddSubscriberUnsynced( + TimeSpanProvider subscriber + ) { _subscribers.Add(subscriber); } - protected sealed override void RemoveSubscriberUnsynced(TimeSpanProvider subscriber) + protected sealed override void RemoveSubscriberUnsynced( + TimeSpanProvider subscriber + ) { _subscribers.Remove(subscriber); } diff --git a/StgSharp.Common/Timing/TimeSequence.cs b/StgSharp.Common/Timing/TimeSequence.cs index 95b06554..970b4c6c 100644 --- a/StgSharp.Common/Timing/TimeSequence.cs +++ b/StgSharp.Common/Timing/TimeSequence.cs @@ -63,8 +63,11 @@ namespace StgSharp.Common.Timing double totalSeconds, double growthMultiplier, double minSliceSeconds - ) => new LogTimeSequence(initialSliceSeconds, totalSeconds, growthMultiplier, - minSliceSeconds); + ) + { + return new LogTimeSequence(initialSliceSeconds, totalSeconds, growthMultiplier, + minSliceSeconds); + } public abstract bool EndsAt( long currentTick @@ -104,7 +107,10 @@ namespace StgSharp.Common.Timing [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool EndsAt( long currentTick - ) => currentTick >= EndingTick; + ) + { + return currentTick >= EndingTick; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool IsNextFrameReady( @@ -149,7 +155,7 @@ namespace StgSharp.Common.Timing private double _nextBoundarySecondsAdjusted; private readonly double _totalSeconds; private int _sliceIndex; - private long _freq; + private long _frequency; private long _nextBoundaryTick; private long _totalTicks; @@ -181,7 +187,7 @@ namespace StgSharp.Common.Timing long currentTick ) { - return currentTick >= EndingTick; + return currentTick >= EndingTick; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -239,7 +245,7 @@ namespace StgSharp.Common.Timing TimeSourceProviderBase source ) { - _freq = source?.Frequency ?? throw new ArgumentNullException(nameof(source)); + _frequency = source?.Frequency ?? throw new ArgumentNullException(nameof(source)); BeginningTick = source.GetCurrentTimeSpanTick(); _totalTicks = SecondsToTicks(_totalSeconds); EndingTick = BeginningTick + _totalTicks; @@ -254,7 +260,10 @@ namespace StgSharp.Common.Timing [MethodImpl(MethodImplOptions.AggressiveInlining)] private long SecondsToTicks( double seconds - ) => (long)Math.Max(1L, Math.Round(seconds * _freq, MidpointRounding.AwayFromZero)); + ) + { + return (long)Math.Max(1L, Math.Round(seconds * _frequency, MidpointRounding.AwayFromZero)); + } } @@ -278,7 +287,10 @@ namespace StgSharp.Common.Timing [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool EndsAt( long currentTick - ) => false; // never ends + ) + { + return false; // never ends + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool IsNextFrameReady( diff --git a/StgSharp.Common/Timing/TimeSpanProvider.cs b/StgSharp.Common/Timing/TimeSpanProvider.cs index f847b1f5..33d390ef 100644 --- a/StgSharp.Common/Timing/TimeSpanProvider.cs +++ b/StgSharp.Common/Timing/TimeSpanProvider.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------- +//----------------------------------------------------------------------- // ----------------------------------------------------------------------- // file="TimeSpanProvider" // Project: StgSharp @@ -40,7 +40,7 @@ namespace StgSharp.Timing { // private bool _subscribed; - private int _emittedSpanCount; // legacy counter + private readonly int _emittedSpanCount; // legacy counter // internal ended flag (0 = running, 1 = ended) private int _endedFlag; @@ -50,7 +50,8 @@ namespace StgSharp.Timing public TimeSpanProvider( TimeSequence sequence, TimeSourceProviderBase provider, - int maxWaitCount = 1) + int maxWaitCount = 1 + ) { ArgumentNullException.ThrowIfNull(provider); ArgumentNullException.ThrowIfNull(sequence); @@ -78,17 +79,25 @@ namespace StgSharp.Timing /// /// Current emitted time in seconds relative to the sequence beginning. /// - public double CurrentSecond - { - get => (double)(_sequence.PreviousFrameTick - _sequence.BeginningTick) / _timeSource.Frequency; - } + public double CurrentSecond => (double)(_sequence.PreviousFrameTick - _sequence.BeginningTick) / _timeSource.Frequency; public int CurrentSpan => _emittedSpanCount; public static TimeSourceProviderBase DefaultProvider => World.MainTimeProvider; - public static explicit operator int(TimeSpanProvider provider) => provider?._emittedSpanCount ?? - 0; + public static int ToInt32( + TimeSpanProvider provider + ) + { + return provider?._emittedSpanCount ?? 0; + } + + public static explicit operator int( + TimeSpanProvider provider + ) + { + return provider?._emittedSpanCount ?? 0; + } } -} \ No newline at end of file +}