杂谈.netcore的Buffer相关新类型

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)

原文地址:https://www.cnblogs.com/kewei/p/12183258.html

时间: 2024-10-17 20:58:08

杂谈.netcore的Buffer相关新类型的相关文章

C++中的数组array和vector,lambda表达式,C字符串加操作,C++中新类型数组(数组缓存),多元数组,new缓冲

 使用C++风格的数组,不需要管理内存. array要注意不要溢出,因为它是栈上开辟内存. array适用于任何类型 #include<iostream> #include<array> #include<vector>   //C++的标准库 #include<string>   //C++字符串 #include <stdlib.h> using  std::array; //静态数组,栈上 using std::vector; //动态

如何在caffe中添加新类型的layer

如何在caffe中添加新类型的layer 参考:https://github.com/BVLC/caffe/issues/684 Add a class declaration for your layer to the appropriate one of common_layers.hpp,data_layers.hpp, loss_layers.hpp, neuron_layers.hpp, or vision_layers.hpp. Include an inline implement

IIS新类型文件500错误

时间过得太久,就真的会忘掉.在这里记录一下吧. 不得已重新安装系统,然后以前的配置都忘掉了.对新类型的文件.geojson 文件报错. 500错误. 首先反复调试MIME类型是否有问题, 再看映射是否有问题. 再把错误信息发到浏览器上. 最后定位在响应缓冲限制上.

C++ 声明新类型 typedef

在C++中,除了可以声明结构体.共用体.枚举等类型外,还可以用typedef声明一个新的类型名来代替已有的类型如: typedef int INTEGER;  //指定用标识符INTEGER代表int类型 typedef float REAL;  //指定用REAL代表float类型 这样,以下两行等价: int i,j; float a,b; INTEGER i,j; REAL a,b; 这样可以使熟悉FORTRAN的人能用INTEGER和REAL定义变量,以适应他们的习惯. 如果在一个程序中

Innodb buffer相关参数及查看方式(待续)

buffer相关参数: innodb_buffer_pool_size                      innodb_buffer 大小 innodb_buffer_pool_instances             innodb_buffer instance 个数 innodb_old_blocks_pct                        LRU端mid点位置 innodb_old_blocks_time                      LRU在Old端保

关于typedef声明新类型名

typedef作用是用简单的类型名代替复杂的类型名,这样易于理解,方便表示.如 typedef int Integer 指定Integer为类型名,作用于int 相同 Integer i 与 int i 等价. 复杂点的情况是定义函数指针,如 typedef void (*fun)(void) 表示用户自己定义了一个函数指针数据类型,fun ptr,即定义了一个函数指针ptr,可将函数地址赋给该指针,如 ptr=File; (File为一函数,函数名即地址,File()的入口地址赋给ptr,以后

通过案例学调优之--和 LOG BUFFER 相关的主要 Latch

通过案例学调优之--和 LOG BUFFER 相关的主要 Latch  4.1.和 LOG BUFFER 相关的主要 Latch  有:  Latch:Redo Copy      Latch:Redo Allocation Latch 4.2 当一个进程在修改数据时候将会产生 Redo,这个 Redo 首先在 PGA 中保存. 然后进程需要 获取Redo Copy Latch(这个Latch的个数由隐含参数_log_simultaneous_copies决定),当获 得 Redo Copy L

5.19 找到被指的新类型字符

[题目]: 新类型字符的定义如下: 1.新类型字符是长度为1或者2的字符串 2.表现形式可以仅是小写字母,例如,"e":也可以是大写字母+小写字母,例如,"Ab":还可以是大写字母+大写字母,例如,"DC" 现在给定一个字符串str,str一定是若干新类型字符正确组合的结果.比如"eaCCBi",由新类型字符"e","a","CC"和"Bi"拼成.

BUG02 -【环境冲突,还是看官方文档】 mp版本从3.1.0及以下版本升级到高版本,JDK8日期新类型LocalDateTime等无法映射(报错)

mp版本从3.1.0及以下版本升级到高版本,JDK8日期新类型LocalDateTime等无法映射(报错) mp官网说明, 以下来自搬运 Error attempting to get column 'create_time' from result set. Cause: java.sql.SQLFeatureNotSupportedException 3.1.0之前版本没问题,针对3.1.1以及后续版本出现上述问题 现象: 集成druid数据源,使用3.1.0之前版本没问题,升级mp到3.1