zoukankan      html  css  js  c++  java
  • .netcore的Buffer相关新类型包括BinaryPrimitives、Span<>,Memory<>,ArrayPool<>,Memorypool<>

    1 文章范围

    本文将.netcore新出现的与Buffer操作相关的类型进行简单分析与讲解,由于资料有限,一些见解为个人见解,可能不是很准确。这些新类型将包括BinaryPrimitives、Span<>,Memory<>,ArrayPool<>,Memorypool<>

    2 BinaryPrimitives

    在网络传输中,最小单位是byte,很多场景,我们需要将int long short等类型与byte[]相互转换。比如,将int转换为BigEndian的4个字节,在过去,我们很容易就想到BitConverter,但BitConverter设计得不够好友,BitConverter.GetBytes(int value)得到的byte[]的字节顺序永远与主机的字节顺序一样,我们不得不再根据BitConverter的IsLittleEndian属性判断是否需要对得到byte[]进行转换字节顺序,而BinaryPrimitives的Api设计为严格区分Endian,每个Api都指定了目标Endian。

    BitConverter

    var intValue = 1;
    var bytes = BitConverter.GetBytes(intValue);
    if (BitConverter.IsLittleEndian == true)
    {
        Array.Reverse(bytes);
    }

    BinaryPrimitives

    var intValue = 1;
    var bytes = new byte[sizeof(int)];
    BinaryPrimitives.WriteInt32BigEndian(bytes, intValue);

    3 Span<>

    Span是一个高效的连续内存范围操作值类型,我们知道Array是一个连接的内存范围的引用类型,那为什么还需要Span类型呢?可以简单这么认为:Span除了提供更高性能的Array的读写功能之外,还提供了比ArraySegment更易于理解和使用的内存局部视图,也就是说Span功能包含了Array+ArraySegment的功能,我可以使用BenchmarkDotNet对比Span、Array和指针读写一个连接内存的性能比较,测试结果为Span>Pointer>Array:

    读写代码

    public class DemoContext
    {
        private byte[] array = new byte[1024];
    
        [Benchmark]
        public void ByteArray()
        {            
            for (var i = 0; i < array.Length; i++)
            {
                array[i] = array[i];
            }
        }
    
        [Benchmark]
        public void ByteSpan()
        {
            var span = array.AsSpan();
            for (var i = 0; i < span.Length; i++)
            {
                span[i] = span[i];
            }
        }
    
        [Benchmark]
        unsafe public void BytePointer()
        {
            fixed (byte* pointer = &array[0])
            {
                for (var i = 0; i < array.Length; i++)
                {
                    *(pointer + i) = *(pointer + i);
                }
            }
        }
    }

    Benchmark报告

    |      Method |     Mean |   Error |  StdDev |
    |------------ |---------:|--------:|--------:|
    |   ByteArray | 577.4 ns | 9.07 ns | 8.48 ns |
    |    ByteSpan | 323.8 ns | 0.87 ns | 0.81 ns |
    | BytePointer | 499.4 ns | 4.09 ns | 3.82 ns |

    Memory<>

    如果尝试将Span<>作为全局变量,或在异步方法声明为变量,你会得到编译器的错误,原因不在本文讲解范围内,而Memory<>类型可以满足这些需求,Memory<>提供了用于数据读写的Span属性,这个Span属性是每将获取时都有一些计算,所以我们应该尽量避免多次获取它的Span属性。

    合理的获取Span

    var span = memory.Span;
    for (var i = 0; i < span.Length; i++)
    {
        span[i] = span[i];
    }

    不合理的获取Span

    for (var i = 0; i < memory.Length; i++)
    {
        memory.Span[i] = memory.Span[i];
    }

    Benchmark报告

    |      Method |       Mean |    Error |   StdDev |
    |------------ |-----------:|---------:|---------:|
    | ByteMemory1 |   325.8 ns |  1.03 ns |  0.97 ns |
    | ByteMemory2 | 3,344.9 ns | 11.91 ns | 11.14 ns |

    ArrayPool<>

    ArrayPool<>用于解决频繁申请内存和释放内存导致GC压力过大的场景,比如System.Text.Json在序列对象时为utf8的byte[]时,事先是无法计算最终byte[]的长度的,过程中可能要不断申请和调整缓冲区的大小。在没有ArrayPool加持的情况下,高频次的序列化,则会生产高频创建byte[]的过程,随之GC压力也会增大。ArrayPool的设计逻辑是,从pool申请一个指定最小长度的缓冲区,缓冲区在不需要的时候,将其返回到pool里,待以重复利用。

    var pool = ArrayPool<byte>.Shared;
    var buffer = pool.Rent(1024);
    // 开始利用buffer
    // ...
    // 使用结束
    pool.Return(buffer);

    Rent用于申请,实际上是租赁,Return是归还,返回到池中。我们可以使用IDisposable接口来包装Return功能,使用上更方便一些:

    /// <summary>
    /// 定义数组持有者的接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IArrayOwner<T> : IDisposable
    {
        /// <summary>
        /// 获取持有的数组
        /// </summary>
        T[] Array { get; }
    
        /// <summary>
        /// 获取数组的有效长度
        /// </summary>
        int Count { get; }
    }
    
    /// <summary>
    /// 表示共享的数组池
    /// </summary>
    public static class ArrayPool
    {
        /// <summary>
        /// 租赁数组
        /// </summary>
        /// <typeparam name="T">元素类型</typeparam>
        /// <param name="minLength">最小长度</param>
        /// <returns></returns>
        public static IArrayOwner<T> Rent<T>(int minLength)
        {
            return new ArrayOwner<T>(minLength);
        }
    
        /// <summary>
        /// 表示数组持有者
        /// </summary>
        /// <typeparam name="T"></typeparam>
        [DebuggerDisplay("Count = {Count}")]
        [DebuggerTypeProxy(typeof(ArrayOwnerDebugView<>))]
        private class ArrayOwner<T> :IDisposable, IArrayOwner<T>
        {
            /// <summary>
            /// 获取持有的数组
            /// </summary>
            public T[] Array { get; }
    
            /// <summary>
            /// 获取数组的有效长度
            /// </summary>
            public int Count { get; }
    
            /// <summary>
            /// 数组持有者
            /// </summary>
            /// <param name="minLength"></param> 
            public ArrayOwner(int minLength)
            {
                this.Array = ArrayPool<T>.Shared.Rent(minLength);
                this.Count = minLength;
            }
    
            /// <summary>
            /// 归还数组
            /// </summary>
            Public void Dispose()
            {
                ArrayPool<T>.Shared.Return(this.Array);
            }
        }
    
        /// <summary>
        /// 调试视图
        /// </summary>
        /// <typeparam name="T"></typeparam>
        private class ArrayOwnerDebugView<T>
        {
            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public T[] Items { get; }
    
            /// <summary>
            /// 调试视图
            /// </summary>
            /// <param name="owner"></param>
            public ArrayOwnerDebugView(IArrayOwner<T> owner)
            {
                this.Items = owner.Array.AsSpan(0, owner.Count).ToArray();
            }
        }
    }

    改造之后的使用

    using var buffer = ArrayPool.Rent<byte>(1024);
    // 尽情的使用buffer吧,自动回收

    Memorypool<>

    Memorypool<>本质上还是使用了ArrayPool<>,Memorypool只提供了Rent功能,返回一个IMomoryOwner<>,对其Dispose等同于Return过程,使用方式和我们上面改造过的ArrayPool静态类的使用方式是一样的。

    MemoryMarshal静态类

    MemoryMarshal是一个工具类,类似于我们指针操作时常常用到的Marshal类,它操作一些更底层的Span或Memory操作,比如提供将不同基元类型的Span相互转换等。

    获取Span的指针

    var span = new Span<byte>(new byte[] { 1, 2, 3, 4 });
    ref var p0 = ref MemoryMarshal.GetReference(span);
    fixed (byte* pointer = &p0)
    {
        Debug.Assert(span[0] == *pointer);
    }

    Span泛型参数类型转换

    Span<int> intSpan = new Span<int>(new int[] { 1024 });
    Span<byte> byteSpan = MemoryMarshal.AsBytes(intSpan);

    ReadonlyMemory<>转换为Memory

    // 相当于给ReadonlyMemory移除只读功能
    Memory<T> MemoryMarshal.AsMemory<T>(ReadonlyMemory<T> readonly)
  • 相关阅读:
    grunt in webstorm
    10+ Best Responsive HTML5 AngularJS Templates
    响应式布局
    responsive grid
    responsive layout
    js event bubble and capturing
    Understanding Service Types
    To add private variable to this Javascript literal object
    Centering HTML elements larger than their parents
    java5 新特性
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/12400449.html
Copyright © 2011-2022 走看看