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

  1. var intValue = 1;
  2. var bytes = BitConverter.GetBytes(intValue);
  3. if (BitConverter.IsLittleEndian == true)
  4. {
  5. Array.Reverse(bytes);
  6. }

BinaryPrimitives

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

3 Span<>

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

读写代码

  1. public class DemoContext
  2. {
  3. private byte[] array = new byte[1024];
  4. [Benchmark]
  5. public void ByteArray()
  6. {
  7. for (var i = 0; i < array.Length; i++)
  8. {
  9. array[i] = array[i];
  10. }
  11. }
  12. [Benchmark]
  13. public void ByteSpan()
  14. {
  15. var span = array.AsSpan();
  16. for (var i = 0; i < span.Length; i++)
  17. {
  18. span[i] = span[i];
  19. }
  20. }
  21. [Benchmark]
  22. unsafe public void BytePointer()
  23. {
  24. fixed (byte* pointer = &array[0])
  25. {
  26. for (var i = 0; i < array.Length; i++)
  27. {
  28. *(pointer + i) = *(pointer + i);
  29. }
  30. }
  31. }
  32. }

Benchmark报告

  1. | Method | Mean | Error | StdDev |
  2. |------------ |---------:|--------:|--------:|
  3. | ByteArray | 577.4 ns | 9.07 ns | 8.48 ns |
  4. | ByteSpan | 323.8 ns | 0.87 ns | 0.81 ns |
  5. | BytePointer | 499.4 ns | 4.09 ns | 3.82 ns |

Memory<>

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

合理的获取Span

  1. var span = memory.Span;
  2. for (var i = 0; i < span.Length; i++)
  3. {
  4. span[i] = span[i];
  5. }

不合理的获取Span

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

Benchmark报告

  1. | Method | Mean | Error | StdDev |
  2. |------------ |-----------:|---------:|---------:|
  3. | ByteMemory1 | 325.8 ns | 1.03 ns | 0.97 ns |
  4. | 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里,待以重复利用。

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

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

  1. /// <summary>
  2. /// 定义数组持有者的接口
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. public interface IArrayOwner<T> : IDisposable
  6. {
  7. /// <summary>
  8. /// 获取持有的数组
  9. /// </summary>
  10. T[] Array { get; }
  11. /// <summary>
  12. /// 获取数组的有效长度
  13. /// </summary>
  14. int Count { get; }
  15. }
  16. /// <summary>
  17. /// 表示共享的数组池
  18. /// </summary>
  19. public static class ArrayPool
  20. {
  21. /// <summary>
  22. /// 租赁数组
  23. /// </summary>
  24. /// <typeparam name="T">元素类型</typeparam>
  25. /// <param name="minLength">最小长度</param>
  26. /// <returns></returns>
  27. public static IArrayOwner<T> Rent<T>(int minLength)
  28. {
  29. return new ArrayOwner<T>(minLength);
  30. }
  31. /// <summary>
  32. /// 表示数组持有者
  33. /// </summary>
  34. /// <typeparam name="T"></typeparam>
  35. [DebuggerDisplay("Count = {Count}")]
  36. [DebuggerTypeProxy(typeof(ArrayOwnerDebugView<>))]
  37. private class ArrayOwner<T> :IDisposable, IArrayOwner<T>
  38. {
  39. /// <summary>
  40. /// 获取持有的数组
  41. /// </summary>
  42. public T[] Array { get; }
  43. /// <summary>
  44. /// 获取数组的有效长度
  45. /// </summary>
  46. public int Count { get; }
  47. /// <summary>
  48. /// 数组持有者
  49. /// </summary>
  50. /// <param name="minLength"></param>
  51. public ArrayOwner(int minLength)
  52. {
  53. this.Array = ArrayPool<T>.Shared.Rent(minLength);
  54. this.Count = minLength;
  55. }
  56. /// <summary>
  57. /// 归还数组
  58. /// </summary>
  59. Public void Dispose()
  60. {
  61. ArrayPool<T>.Shared.Return(this.Array);
  62. }
  63. }
  64. /// <summary>
  65. /// 调试视图
  66. /// </summary>
  67. /// <typeparam name="T"></typeparam>
  68. private class ArrayOwnerDebugView<T>
  69. {
  70. [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
  71. public T[] Items { get; }
  72. /// <summary>
  73. /// 调试视图
  74. /// </summary>
  75. /// <param name="owner"></param>
  76. public ArrayOwnerDebugView(IArrayOwner<T> owner)
  77. {
  78. this.Items = owner.Array.AsSpan(0, owner.Count).ToArray();
  79. }
  80. }
  81. }

改造之后的使用

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

Memorypool<>

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

MemoryMarshal静态类

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

获取Span的指针

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

Span泛型参数类型转换

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

ReadonlyMemory<>转换为Memory

  1. // 相当于给ReadonlyMemory移除只读功能
  2. Memory<T> MemoryMarshal.AsMemory<T>(ReadonlyMemory<T> readonly)

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

  1. Android 虚拟机Dalvik、Android各种java包功能、Android相关文件类型、应用程序结构分析、ADB

    Android虚拟机Dalvik Dalvik冲击 随着Google 的AndroidSDK 的发布,关于它的API 以及在移动电话领域所带来的预期影响这些方面的讨论不胜枚举.不过,其中的一个话题在J ...

  2. Innodb buffer 相关参数

    buffer相关参数: show GLOBAL VARIABLES LIKE 'innodb_buffer_pool_instances'; show GLOBAL VARIABLES LIKE 'i ...

  3. IIS新类型文件500错误

    时间过得太久,就真的会忘掉.在这里记录一下吧. 不得已重新安装系统,然后以前的配置都忘掉了.对新类型的文件.geojson 文件报错. 500错误. 首先反复调试MIME类型是否有问题, 再看映射是否 ...

  4. python定义一种新类型的元组

    # 定义一种新类型的元组,只保留int类型,切只大于0的元素 # 例如:IntTuple([1,-1,"abc",6,['x','y'],3])==>(1,6,3) # 解决 ...

  5. 八十三、SAP中的ALV创建之二,ALV相关的类型池定义

    一.与ALV相关的类型都是在TYPE-POOLS:SLIS中.我们来到SE11 二.常用的定义有fieldca和layout等,用于显示字段,和控制信息数据等. 三.我们以VBAK表为例,用ALV输出 ...

  6. 跟我一起学.NetCore之选项(Options)核心类型简介

    前言 .NetCore中提供的选项框架,我把其理解为配置组,主要是将服务中可供配置的项提取出来,封装成一个类型:从而服务可根据应用场景进行相关配置项的设置来满足需求,其中使用了依赖注入的形式,使得更加 ...

  7. MySQL 5.7 Replication 相关新功能说明

    背景: MySQL5.7在主从复制上面相对之前版本多了一些新特性,包括多源复制.基于组提交的并行复制.在线修改Replication Filter.GTID增强.半同步复制增强等.因为都是和复制相关, ...

  8. ES6相关新特性介绍

    你可能已经听说过 ECMAScript 6 (简称 ES6)了.ES6 是 Javascript 的下一个版本,它有很多很棒的新特性.这些特性复杂程度各不相同,但对于简单的脚本和复杂的应用都很有用.在 ...

  9. MySQL 5.7 Replication 相关新功能说明 (转)

    背景: MySQL5.7在主从复制上面相对之前版本多了一些新特性,包括多源复制.基于组提交的并行复制.在线修改Replication Filter.GTID增强.半同步复制增强等.因为都是和复制相关, ...

随机推荐

  1. github下载慢,轻松提速教程

    获取github的IP地址访问:https://www.ipaddress.com/ 网址 依次获取以下三个网址的IP github.comgithub.global.ssl.fastly.netco ...

  2. Group_concat介绍与例子

    进公司做的第一个项目就是做一个订单追踪查询,里里外外连接了十一个表,作为公司菜鸡的我麻了爪. 其中有一个需求就是对于多行的数据在一行显示,原谅我才疏学浅 无奈下找到了项目组长  在那学来了这个利器 ( ...

  3. 2018-8-10-WPF-判断调用方法堆栈

    title author date CreateTime categories WPF 判断调用方法堆栈 lindexi 2018-08-10 19:16:53 +0800 2018-2-13 17: ...

  4. js基础——基本包装类型

    1.基本包装类型String   var bz = new String("Li.Linda"); //引用类型(object)         bz.name= bz.subst ...

  5. H3C 三种生成树协议的端口状态对比

  6. Linux 内核/sbin/hotplug 工具

    如同本章中前面提过的, 无论何时一个设备从系统中增删, 都产生一个"热插拔事件". 这 意味着内核调用用户空间程序 /sbin/hotplug. 这个程序典型地是一个非常小的 ba ...

  7. 2018-5-19-创建不带BOM-的UTF8

    title author date CreateTime categories 创建不带BOM 的UTF8 lindexi 2018-05-19 14:11:33 +0800 2018-2-13 17 ...

  8. 在eclipse动态网页项目中,编写web.xml时,servlet标签报错.

    cvc-complex-type.2.4.b: The content of element 'servlet' is not complete. One of '{"http:// jav ...

  9. LuoguP3521 [POI2011]ROT-Tree Rotations

    P3521 [POI2011]ROT-Tree Rotations 题目大意: 给一棵\((1≤n≤200000)\)个叶子的二叉树,可以交换每个点的左右子树,要求前序遍历叶子的逆序对最少. 我们发现 ...

  10. 2018-2-13-win10-uwp-判断文件存在

    title author date CreateTime categories win10 uwp 判断文件存在 lindexi 2018-2-13 17:23:3 +0800 2018-2-13 1 ...