为什么 System.Collections.List<T>.Add 比手动内存管理更快?

4
嘿,我目前正在尝试使用非常底层的C#进行实验,主要是为了学习并将我的技能提升到下一个水平,以应对性能要求较高的项目。 我试图创建一个类似于NativeList的东西,即一个没有任何自动垃圾回收的列表。 我想使用结构体和手动指针分配来创建一个内存和缓存行对齐的列表实现。这对Unity开发者可能很熟悉。
我目前面临的问题是,在使用BenchmarkDotnet进行基准测试后,我得到了一些意外的结果。 读取操作的速度如预期地更快一些,主要是因为我直接使用指针(我猜测),而且在C#中数组访问速度本来就很快。 写入操作则快了很多,实际上快了约70%,这令人困惑,但可能是由于内存对齐引起的。 然而,添加操作却慢了约5%,我无法弄清楚原因。
请注意,基准测试中没有涉及任何分配,因为两个列表都保持其内部缓冲区的活动状态。剩下的部分只有与List内部非常相似的if条件。
您有什么建议可以使这个过程更快,或者可以告诉我为什么我的列表添加操作较慢吗?
基准测试结果如下:
Method Size Mean Error StdDev Ratio RatioSD CacheMisses/Op
Native_Add 100 934.59 ns 5.828 ns 4.866 ns 1.04 0.01 2
List_Add 100 894.44 ns 7.063 ns 6.607 ns 1.00 0.00 1
Native_Add 1000 9,463.38 ns 183.577 ns 162.736 ns 1.03 0.03 16
List_Add 1000 9,196.11 ns 87.191 ns 81.558 ns 1.00 0.00 17
Native_Add 10000 93,361.52 ns 876.444 ns 776.945 ns 1.05 0.01 112
List_Add 10000 88,501.43 ns 362.486 ns 302.692 ns 1.00 0.00 53
Native_Get 100 41.38 ns 0.371 ns 0.347 ns 0.94 0.02 0
List_Get 100 43.93 ns 0.849 ns 0.794 ns 1.00 0.00 0
Native_Get 1000 366.97 ns 4.445 ns 3.940 ns 0.98 0.02 0
List_Get 1000 374.97 ns 3.053 ns 2.550 ns 1.00 0.00 0
Native_Get 10000 3,674.04 ns 60.222 ns 56.331 ns 0.98 0.02 5
List_Get 10000 3,733.41 ns 35.220 ns 32.945 ns 1.00 0.00 4
Native_Set 100 56.57 ns 0.357 ns 0.279 ns 0.34 0.00 0
List_Set 100 164.29 ns 0.936 ns 0.875 ns 1.00 0.00 0
Native_Set 1000 493.52 ns 8.668 ns 8.108 ns 0.30 0.00 1
List_Set 1000 1,665.81 ns 10.073 ns 7.865 ns 1.00 0.00 2
Native_Set 10000 4,984.44 ns 94.866 ns 88.737 ns 0.30 0.01 10
List_Set 10000 16,699.32 ns 59.226 ns 49.456 ns 1.00 0.00 18

结构:

public unsafe struct NativeList<T> : IDisposable where T : unmanaged {
    private static readonly int MinCapacity = UnsafeUtils.CacheLineSize / sizeof(T);
    private T* _buffer;
    private int _capacity;
    private int _count;

    public int Capacity => _capacity;
    public int Count => _count;
    public T this[int index] {
        get {
            if((uint)index >= (uint)_count) throw new ArgumentOutOfRangeException(nameof(index));
            return _buffer[index];
        }
        set {
            if((uint)index >= (uint)_count) throw new ArgumentOutOfRangeException(nameof(index));
            _buffer[index] = value;
        }
    }

    public NativeList() {
    }

    public NativeList(int capacity) {
        if(capacity != 0) Resize(capacity);
    }

    public void Add(T value) {
        EnsureCapacity(1);
        _buffer[_count] = value;
        _count++;
    }

    public void AddRange(T* values, int count) {
        if(count == 0) return;
        EnsureCapacity(count);
        UnsafeUtils.Copy<T>(values, 0, _buffer, _count, count);
        _count += count;
    }

    public bool Remove(T value) {
        var index = IndexOf(value);
        if(index < 0) return false;
        RemoveAt(index);
        return true;
    }

    public void RemoveAt(int index) {
        if(index >= _count) throw new ArgumentOutOfRangeException(nameof(index));

        _count--;
        if(_count != index) {
            UnsafeUtils.Copy<T>(_buffer, index + 1, _buffer, index, _count - index);
        }
    }
    public void RemoveRangeAt(int index, int count) {
        if(index < 0 || index >= _count) throw new ArgumentOutOfRangeException(nameof(index));
        if(count < 0 || index + count > _count) throw new ArgumentOutOfRangeException(nameof(count));
        if(count == 0) return;

        _count -= count;
        if(_count != index) {
            UnsafeUtils.Copy<T>(_buffer, index + count, _buffer, index, _count - index);
        }
        _count -= count;
    }

    public bool Contains(T value) {
        return IndexOf(value, EqualityComparer<T>.Default) >= 0;
    }
    public bool Contains(T value, IEqualityComparer<T> equalityComparer) {
        return IndexOf(value, equalityComparer) >= 0;
    }
    
    public int IndexOf(T value) {
        return IndexOf(value, EqualityComparer<T>.Default);
    }
    public int IndexOf(T value, IEqualityComparer<T> equalityComparer) {
        for(var i = 0; i < _count; i++) {
            if(equalityComparer.Equals(((T*)_buffer)[i], value)) return i;
        }
        return -1;
    }
    
    public void Clear() {
        _count = 0;
    }
    
    public Span<T> AsSpan() {
        return UnsafeUtils.CreateSpan<T>(_buffer, _count);
    }

    public Enumerator GetEnumerator() {
        return new Enumerator(_buffer, _count);
    }

    private void EnsureCapacity(int count) {
        var minCapacity = _count + count;
        if(minCapacity > _capacity) Resize(minCapacity);
    }

    private void Resize(int capacity) {
        capacity = Math.Max(MinCapacity, (int)BitOperations.RoundUpToPowerOf2((uint)capacity));
        _buffer = (T*)UnsafeUtils.AlignedRealloc<T>(_buffer, capacity);
        _capacity = capacity;
    }

    public void Dispose() {
        if(_buffer is null) return;
        UnsafeUtils.AlignedFree(_buffer);
        _capacity = 0;
        _buffer = null;
    }

    public struct Enumerator : IEnumerator<T> {
        private readonly void* _buffer;
        private readonly int _count;
        private int _index;
        
        public T Current { get; private set; }
        object IEnumerator.Current => Current;

        public Enumerator(void* buffer, int count) {
            _buffer = buffer;
            _count = count;
            _index = -1;
        }

        public bool MoveNext() {
            if(++_index >= _count) return false;
            Current = ((T*)_buffer)[_index];
            return true;
        }

        public void Reset() {
            _index = -1;
        }
        public void Dispose() {}
    }
}

基准:

[SimpleJob]
[MemoryDiagnoser]
[HardwareCounters(HardwareCounter.CacheMisses)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory, BenchmarkLogicalGroupRule.ByParams)]
public class NativeListBenchmark {
    private NativeList<int> _fullNativeList = new();
    private List<int> _fullList = new();
    private Random _random;
    
    private NativeList<int> _nativeList = new();
    private List<int> _list = new();

    [Params(100, 1000, 10_000)]
    public int Size { get; set; }

    [GlobalSetup]
    public void Setup() {
        _random = new Random(1337);
        for(var i = 0; i < Size; i++) {
            _fullNativeList.Add(i);
        }
        for(var i = 0; i < Size; i++) {
            _fullList.Add(i);
        }
    }

    [GlobalCleanup]
    public void Cleanup() {
        _nativeList.Dispose();
        _fullNativeList.Dispose();
    }

    [Benchmark]
    [BenchmarkCategory("Add")]
    public void Native_Add() {
        _nativeList.Clear();
        for(var i = 0; i < Size; i++) {
            _nativeList.Add(_random.Next());
        }
    }
    [Benchmark]
    [BenchmarkCategory("Get")]
    public void Native_Get() {
        for(var i = 0; i < Size; i++) {
            _ = _fullNativeList[i];
        }
    }

    [Benchmark]
    [BenchmarkCategory("Set")]
    public void Native_Set() {
        for(var i = 0; i < Size; i++) {
            _fullNativeList[i] = i;
        }
    }

    [Benchmark(Baseline = true)]
    [BenchmarkCategory("Add")]
    public void List_Add() {
        _list.Clear();
        for(var i = 0; i < Size; i++) {
            _list.Add(_random.Next());
        }
    }

    [Benchmark(Baseline = true)]
    [BenchmarkCategory("Get")]
    public void List_Get() {
        for(var i = 0; i < Size; i++) {
            _ = _fullList[i];
        }
    }

    [Benchmark(Baseline = true)]
    [BenchmarkCategory("Set")]
    public void List_Set() {
        for(var i = 0; i < Size; i++) {
            _fullList[i] = i;
        }
    }
}

1
“一个没有自动垃圾回收的列表” -- 只要您的类型是值类型,List<> 中也没有自动垃圾回收。 - Blindy
1
List<>在没有自动垃圾回收的情况下,也不会进行任何垃圾回收,只要您的类型是值类型。 - Blindy
1
顺便提一下:与C++不同,C#中很难跟踪原始指针的所有权...我强烈建议除非你是该代码的唯一用户并且手动审查列表上的每个复制操作,否则避免使用结构体作为你的列表。 - Alexei Levenkov
1
顺便提一下:与C++不同,C#中很难跟踪原始指针的所有权...我强烈建议在列表中避免使用结构体,除非你是该代码的唯一用户,并且手动审查列表上的每个复制操作。 - Alexei Levenkov
1
@Dotlogix 如果你真的担心的是列表本身,那就是为什么存在列表池的原因。与其从头开始重写整个类,并且至少与原始版本一样好,不如将列表进行池化,这样效率会更高。至于针对你实际情况的建议,这里有两个:在64位上运行测试,并使用现代的.Net。事实上,你应该永远不需要使用实际指针,在.Net 7中,跨度(spans)可以给你带来相同的好处。 - Blindy
显示剩余28条评论
2个回答

4
根据List<T>源代码的判断,通过谨慎使用AggressiveInliningNoInlining,您可能会获得显著的加速效果。我没有具体的信息告诉您在哪里使用它,在哪里不使用它,但似乎它被用于内联常见路径,同时将不常见的路径保留在单独的函数中。
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int count) {

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T value) {

[MethodImpl(MethodImplOptions.NoInlining)]
private void Resize(int capacity) {

说了这么多,我赞同其他人的观点,你是

  • 可能优化了错误的东西。你的代码中几乎肯定存在比List上的getter/setter更糟糕的低效率问题。
  • 可能会冒着内存泄漏(忘记使用using)、缓冲区溢出和其他问题的风险,而获得的收益很少。
  • 至少,你可以通过使用Span<T>来存储_buffer来改进现有的代码,性能开销很小(在正确放置边界检查的情况下,Jitter可以省略它们)。特别是在使用spans时,Copy具有非常好的优化,因为它展开并使用SIMD指令(如果可能)。
  • 你应该重新考虑为什么你想要重写这个,而不是使用某种形式的ListPool,这是改进游戏中内存处理的经典方式。
  • 我不知道你的UnsafeUtils是做什么的,但已经有功能可以用于分配释放对齐内存。另请参阅在C#中创建内存对齐数组

只是补充一些小事情:Span<T> 是一个 ref struct,既不能用作类字段,也不能在异步环境中使用。List Pool 在调整列表大小时仍然进行垃圾回收,并且即使不再需要,它仍然保留了大量的内存分配。UnsafeUtils 内部封装了一些烦人的缺失方法,如指针相加,并确保泛型指针算术正确处理 void* 在 dotnet 中与 T* 处理方式不同。我完全同意你的观点,这可能是危险的,只适用于非常有限的用例。 - Dotlogix
只是补充一些小事情:Span<T> 是一个 ref struct,既不能用作类字段,也不能在异步环境中使用。列表池在调整列表大小时仍然进行垃圾回收,并且即使不再需要,它仍然保留了大量的内存分配。UnsafeUtils 内部封装了一些烦人的缺失方法,如指针相加,并确保泛型指针算术在 dotnet 中正确处理 void* 与 T* 的区别。我完全同意你的观点,这可能是危险的,只适用于非常有限的用例。 - Dotlogix
只是为了补充一些小事情:Span<T> 是一个 ref 结构体,既不能用作类字段,也不能在异步环境中使用。List Pool 在调整列表大小时仍然进行垃圾回收,并且即使不再需要,仍然保留了大量的内存分配。UnsafeUtils 内部封装了一些烦人的缺失方法,如指针相加,并确保泛型指针算术在 dotnet 中正确处理 void* 与 T* 的区别。我完全同意你的观点,这可能是危险的,只适用于非常有限的用例。 - undefined
可以使用Memory<T>。如果你想的话,你也可以自己创建ListPool。重点在于它会保留内存,这样你就不需要不断重新分配内存了,这就是它的作用所在。 - Charlieface
可以使用Memory<T>。如果你想的话,你总是可以创建自己的ListPool。整个重点是它会保留内存,这样你就不需要不断重新分配内存,这就是它的作用所在。 - Charlieface
可以使用Memory<T>。如果你想的话,你也可以自己创建ListPool。整个重点是它会保留内存,这样你就不需要不断重新分配内存,这就是它的作用。 - undefined

0

为了完整起见,这是我得出的结果。正如其他人提到的那样,请自行承担风险。它可能会导致内存泄漏和其他问题。

这对教育和研究来说是一个不错的小东西,在我看来,把自己关在墙后并忽视在C#中可以进行底层操作的事实是错误的方法。C#中许多优化都是基于更底层的操作。

确实需要小心,但并不比传统的C/C++更危险。 它可以提高性能,超越经典CLR的可能性。 对于我需要处理数百万次读写的用例来说,5%至10%的速度提升是很大的,并且我无法通过其他任何可用的方法实现这一点。 许多SIMD指令也受益于对齐内存。

无论如何,感谢大家的帮助和建议。

基准测试结果:

方法 大小 平均值 误差 标准偏差 比率 缓存未命中/操作
Native_Add 100 928.46 ns 9.469 ns 8.857 ns 1.05 1
List_Add 100 881.83 ns 2.588 ns 2.161 ns 1.00 0
Native_Add 1000 8,853.17 ns 25.682 ns 24.023 ns 0.98 2
List_Add 1000 9,047.41 ns 74.998 ns 70.153 ns 1.00 7
Native_Add 10000 86,743.27 ns 365.873 ns 324.337 ns 0.91 48
List_Add 10000 94,958.34 ns 632.483 ns 591.625 ns 1.00 85
Native_Get 100 41.18 ns 0.233 ns 0.218 ns 0.95 0
List_Get 100 43.12 ns 0.416 ns 0.389 ns public unsafe struct NativeList<T> : IDisposable where T : unmanaged { private static readonly int MinCapacity = UnsafeUtils.CacheLineSize / sizeof(T); private T* _buffer; private int _capacity; private int _count; public int Capacity => _capacity; public int Count => _count; public T this[int index] { get { if((uint)index >= (uint)_count) throw new ArgumentOutOfRangeException(nameof(index)); return _buffer[index]; } set { if((uint)index >= (uint)_count) throw new ArgumentOutOfRangeException(nameof(index)); _buffer[index] = value; } } public NativeList() { } public NativeList(int capacity) { if(capacity != 0) Resize(capacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T value) { var buffer = _buffer; var count = _count; var capacity = _capacity; if ((uint)count < (uint)capacity) { _count = count + 1; buffer[count] = value; } else { AddWithResize(value); } } public void AddRange(T* values, int count) { if(count == 0) return; EnsureCapacity(count); UnsafeUtils.Copy<T>(values, 0, _buffer, _count, count); _count += count; } public bool Remove(T value) { var index = IndexOf(value); if(index < 0) return false; RemoveAt(index); return true; } public void RemoveAt(int index) { if(index >= _count) throw new ArgumentOutOfRangeException(nameof(index)); _count--; if(_count != index) { UnsafeUtils.Copy<T>(_buffer, index + 1, _buffer, index, _count - index); } } public void RemoveRangeAt(int index, int count) { if(index < 0 || index >= _count) throw new ArgumentOutOfRangeException(nameof(index)); if(count < 0 || index + count > _count) throw new ArgumentOutOfRangeException(nameof(count)); if(count == 0) return; _count -= count; if(_count != index) { UnsafeUtils.Copy<T>(_buffer, index + count, _buffer, index, _count - index); } _count -= count; } public bool Contains(T value) { return IndexOf(value, EqualityComparer<T>.Default) >= 0; } public bool Contains(T value, IEqualityComparer<T> equalityComparer) { return IndexOf(value, equalityComparer) >= 0; } public int IndexOf(T value) { return IndexOf(value, EqualityComparer<T>.Default); } public int IndexOf(T value, IEqualityComparer<T> equalityComparer) { for(var i = 0; i < _count; i++) { if(equalityComparer.Equals(_buffer[i], value)) return i; } return -1; } public void Clear() { _count = 0; } public Span<T> AsSpan() { return UnsafeUtils.CreateSpan<T>(_buffer, _count); } public Enumerator GetEnumerator() { return new Enumerator(_buffer, _count); } private void EnsureCapacity(int count) { var minCapacity = _count + count; if(minCapacity > _capacity) Resize(minCapacity); } [MethodImpl(MethodImplOptions.NoInlining)] private void AddWithResize(T value) { var count = _count; Resize(count + 1); _count = count + 1; _buffer[_count] = value; } private void Resize(int capacity) { capacity = Math.Max(MinCapacity, (int)BitOperations.RoundUpToPowerOf2((uint)capacity)); _buffer = (T*)UnsafeUtils.AlignedRealloc<T>(_buffer, capacity); _capacity = capacity; } public void Dispose() { if(_buffer is null) return; UnsafeUtils.AlignedFree(_buffer); _capacity = 0; _buffer = null; } public struct Enumerator : IEnumerator<T> { private readonly void* _buffer; private readonly int _count; private int _index; public T Current { get; private set; } object IEnumerator.Current => Current; public Enumerator(void* buffer, int count) { _buffer = buffer; _count = count; _index = -1; } public bool MoveNext() { if(++_index >= _count) return false; Current = ((T*)_buffer)[_index]; return true; } public void Reset() { _index = -1; } public void Dispose() {} } }
public static unsafe class UnsafeUtils {
    public const int CacheLineSize = 64;
    public static readonly int PointerSize = Unsafe.SizeOf<nuint>();

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Span<T> CreateSpan<T>(void* pointer, int count) where T : unmanaged {
        return new Span<T>(pointer, count);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Span<T> CreateSpan<T>(void* pointer, int index, int count) where T : unmanaged {
        return new Span<T>(Unsafe.Add<T>(pointer, index), count);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int SizeOf<T>() where T : unmanaged {
        return sizeof(T);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int AlignmentOf<T>() where T : unmanaged {
        return sizeof(AlignmentHelper<T>) - sizeof(T);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int ByteCount<T>(int count) where T : unmanaged {
        return count * sizeof(T);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void* AlignedAlloc<T>(int count) where T : unmanaged {
        if(count <= 0) throw new ArgumentOutOfRangeException(nameof(count));
        return NativeMemory.AlignedAlloc((nuint)ByteCount<T>(count), (nuint)AlignmentOf<T>());
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void* AlignedRealloc<T>(void* source, int count) where T : unmanaged {
        if(count <= 0) throw new ArgumentOutOfRangeException(nameof(count));
        return NativeMemory.AlignedRealloc(source, (nuint)ByteCount<T>(count), (nuint)AlignmentOf<T>());
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void AlignedFree(void* pointer) {
        NativeMemory.AlignedFree(pointer);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void* Alloc<T>(int count) where T : unmanaged {
        if(count <= 0) throw new ArgumentOutOfRangeException(nameof(count));
        return NativeMemory.Alloc((nuint)ByteCount<T>(count));
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void* Realloc<T>(void* source, int count) where T : unmanaged {
        if(count <= 0) throw new ArgumentOutOfRangeException(nameof(count));
        return NativeMemory.Realloc(source, (nuint)ByteCount<T>(count));
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Free(void* pointer) {
        NativeMemory.Free(pointer);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int IndexOf<T>(void* source, int count, T value) where T : unmanaged, IEquatable<T> {
        return CreateSpan<T>(source, count).IndexOf(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int IndexOf<T>(void* source, int index, int count, T value) where T : unmanaged, IEquatable<T> {
        return CreateSpan<T>(source, index, count).IndexOf(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int LastIndexOf<T>(void* source, int count, T value) where T : unmanaged, IEquatable<T> {
        return CreateSpan<T>(source, count).LastIndexOf(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int LastIndexOf<T>(void* source, int index, int count, T value) where T : unmanaged, IEquatable<T> {
        return CreateSpan<T>(source, index, count).LastIndexOf(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Contains<T>(void* source, int count, T value) where T : unmanaged, IEquatable<T> {
        return CreateSpan<T>(source, count).Contains(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Contains<T>(void* source, int index, int count, T value) where T : unmanaged, IEquatable<T> {
        return CreateSpan<T>(source, index, count).Contains(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Clear(void* destination, int byteCount) {
        NativeMemory.Clear(destination, (nuint)byteCount);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Clear(void* destination, int index, int byteCount) {
        NativeMemory.Clear((byte*)destination + index, (nuint)byteCount);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Clear<T>(void* destination, int count) where T : unmanaged {
        CreateSpan<T>(destination, count).Clear();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Clear<T>(void* destination, int index, int count) where T : unmanaged {
        CreateSpan<T>(destination, index, count).Clear();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Copy(void* source, void* destination, int byteCount) {
        NativeMemory.Copy(source, destination, (nuint)byteCount);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Copy(void* source, int sourceByteIndex, void* destination, int destinationByteIndex, int byteCount) {
        NativeMemory.Copy((byte*)source + sourceByteIndex, (byte*)destination + destinationByteIndex, (nuint)byteCount);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Copy<T>(void* source, void* destination, int count) where T : unmanaged {
        var sourceSpan = CreateSpan<T>(source, count);
        var destinationSpan = CreateSpan<T>(destination, count);
        sourceSpan.CopyTo(destinationSpan);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Copy<T>(void* source, int sourceIndex, void* destination, int destinationIndex, int count) where T : unmanaged {
        var sourceSpan = CreateSpan<T>(source, sourceIndex, count);
        var destinationSpan = CreateSpan<T>(destination, destinationIndex, count);
        sourceSpan.CopyTo(destinationSpan);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Fill(void* destination, int byteCount, byte value) {
        NativeMemory.Fill(destination, (nuint)byteCount, value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Fill(void* destination, int index, int byteCount, byte value) {
        NativeMemory.Fill((byte*)destination + index, (nuint)byteCount, value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Fill<T>(void* destination, int count, T value) where T : unmanaged {
        var destinationSpan = CreateSpan<T>(destination, count);
        destinationSpan.Fill(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Fill<T>(void* destination, int index, int count, T value) where T : unmanaged {
        var destinationSpan = CreateSpan<T>(destination, index, count);
        destinationSpan.Fill(value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ref T AsRef<T>(void* source) {
        return ref Unsafe.AsRef<T>(source);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ref T AsRef<T>(void* source, int index) {
        return ref Unsafe.AsRef<T>(Unsafe.Add<T>(source, index));
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Write<T>(void* destination, T value) {
        Unsafe.Write(destination, value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Write<T>(void* destination, int index, T value) {
        Unsafe.Write(Unsafe.Add<T>(destination, index), value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void WriteUnaligned<T>(void* destination, T value) {
        Unsafe.WriteUnaligned(destination, value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void WriteUnaligned<T>(void* destination, int index, T value) {
        Unsafe.WriteUnaligned(Unsafe.Add<T>(destination, index), value);
    }

    public static T Read<T>(void* source) {
        return Unsafe.Read<T>(source);
    }

    public static T Read<T>(void* source, int index) {
        return Unsafe.Read<T>(Unsafe.Add<T>(source, index));
    }

    public static T ReadUnaligned<T>(void* source) {
        return Unsafe.ReadUnaligned<T>(source);
    }

    public static T ReadUnaligned<T>(void* source, int index) {
        return Unsafe.ReadUnaligned<T>(Unsafe.Add<T>(source, index));
    }

    public static void* Add<T>(void* source, int count) where T : unmanaged {
        return Unsafe.Add<T>(source, count);
    }

    public static void* Subtract<T>(void* source, int count) where T : unmanaged {
        return Unsafe.Subtract<T>(source, count);
    }

    [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local")]
    private readonly record struct AlignmentHelper<T>(byte Dummy, T Value) where T : unmanaged;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接