杂谈.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)
杂谈.netcore的Buffer相关新类型的更多相关文章
- Android 虚拟机Dalvik、Android各种java包功能、Android相关文件类型、应用程序结构分析、ADB
Android虚拟机Dalvik Dalvik冲击 随着Google 的AndroidSDK 的发布,关于它的API 以及在移动电话领域所带来的预期影响这些方面的讨论不胜枚举.不过,其中的一个话题在J ...
- Innodb buffer 相关参数
buffer相关参数: show GLOBAL VARIABLES LIKE 'innodb_buffer_pool_instances'; show GLOBAL VARIABLES LIKE 'i ...
- IIS新类型文件500错误
时间过得太久,就真的会忘掉.在这里记录一下吧. 不得已重新安装系统,然后以前的配置都忘掉了.对新类型的文件.geojson 文件报错. 500错误. 首先反复调试MIME类型是否有问题, 再看映射是否 ...
- python定义一种新类型的元组
# 定义一种新类型的元组,只保留int类型,切只大于0的元素 # 例如:IntTuple([1,-1,"abc",6,['x','y'],3])==>(1,6,3) # 解决 ...
- 八十三、SAP中的ALV创建之二,ALV相关的类型池定义
一.与ALV相关的类型都是在TYPE-POOLS:SLIS中.我们来到SE11 二.常用的定义有fieldca和layout等,用于显示字段,和控制信息数据等. 三.我们以VBAK表为例,用ALV输出 ...
- 跟我一起学.NetCore之选项(Options)核心类型简介
前言 .NetCore中提供的选项框架,我把其理解为配置组,主要是将服务中可供配置的项提取出来,封装成一个类型:从而服务可根据应用场景进行相关配置项的设置来满足需求,其中使用了依赖注入的形式,使得更加 ...
- MySQL 5.7 Replication 相关新功能说明
背景: MySQL5.7在主从复制上面相对之前版本多了一些新特性,包括多源复制.基于组提交的并行复制.在线修改Replication Filter.GTID增强.半同步复制增强等.因为都是和复制相关, ...
- ES6相关新特性介绍
你可能已经听说过 ECMAScript 6 (简称 ES6)了.ES6 是 Javascript 的下一个版本,它有很多很棒的新特性.这些特性复杂程度各不相同,但对于简单的脚本和复杂的应用都很有用.在 ...
- MySQL 5.7 Replication 相关新功能说明 (转)
背景: MySQL5.7在主从复制上面相对之前版本多了一些新特性,包括多源复制.基于组提交的并行复制.在线修改Replication Filter.GTID增强.半同步复制增强等.因为都是和复制相关, ...
随机推荐
- GPU选型
1-基本概念 显存带宽 是指显示芯片与显存之间的数据传输速率,它以字节/秒为单位.显存带宽是决定显卡性能和速度最重要的因素之一. 2-常见GPU性能参数 GPU型号 单卡显存 cuda 计算能 ...
- PHP redis安装扩展
命令: 查看php版本:PHP -v 查看php安装的扩展:PHP -m php扩展开发包(包括phpize,php -config):yum install php-devel which phpi ...
- Python--day41--递归锁Rlock
1,递归锁Rlock:递归锁是为了解决死锁问题,且递归锁的特点是在同一个线程中可以被acquire()多次 多个acquire()在递归锁中不会阻塞,而互斥锁Lock就会阻塞 代码示例: from t ...
- 原生js实现响应式轮播图,支持电脑端点击切图,手机端滑动切图
轮播图的实现原理并不难,但是步骤有些繁琐.最近练习了一个轮播图,大部分是跟着网上的教程写的,然后自己做了一点兼容ie8的修改,加了点击切换图片的特效和手机端的滑动特效,让这个轮播图可以在响应式的网站中 ...
- P1046 阶乘
题目描述 给你一个数N,求 \(N!\) (即:N的阶乘).\(N! = N \times (N-1) \times \dots \times 2 \times 1\) 输入格式 输入一个整数 \(N ...
- linux 系统挂起
尽管内核代码的大部分 bug 以 oops 消息结束, 有时候它们可能完全挂起系统. 如果系 统挂起, 没有消息打印. 例如, 如果代码进入一个无限循环, 内核停止调度,[15]15 并且系 统不会响 ...
- MySQL面试(二)
1.为什么索引遵循最左匹配原则? 当B+树的数据项是符合的数据结构,比如(name,age,sex)的时候,B+树是按照从左到右的顺序建立搜索树的.比如当(张三,20,F)这样的数据来检索的时候,b+ ...
- 【t050】方程求解
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 要求Xi(i = 1,2,3,4)是一个[-T..T]中的整数,满足方程AX1 + BX2 + CX3 ...
- Simple Robot Gym - 101102I (思维)
SaMer is building a simple robot that can move in the four directions: up (^), down (v), left (<) ...
- codeforces 677D(分层图dp)
Codeforces 677D 传送门:https://codeforces.com/contest/677/problem/D 题意: 给你一个n*m的方格图,每个点有一个权值val,现在要求你从坐 ...