.net core在新增的System.Buffers中引入了一大堆高效内存管理的类,如span和memory内存池。本文今天这里介绍一个高效动态内存访问方案。

ReadOnlySequenceSegment<T>

在我们读取数据的过程,很多时候会出现如下场景:

  1. 不知道数据实际大小
  2. 一次性申请大量内存开销太大

此时我们往往会使用动态内存的方案,通过链表的方式串联起来,从而形成逻辑意义上的数据流。如下图所示:

ReadOnlySequenceSegment<T>就是这样一个表示数据流节点的内存模型,它是一个抽象类,包含如下三个元素:

  • Memory        指向所包含的内存
  • Next        指向下一个节点
  • RunningIndex        标志当前节点在整个流的位置

其中Memory和Next还比较容易理解,典型的链表结构。主要难理解的是RunningIndex,他表示该节点在数据流中的Memory起始索引。

一般的来讲,某节点的RunningIndex为其上一个节点的RunningIndex + Memory.Length。加上RunningIndex估计主要是为了快速索引的。

例如:对于如下3快内存 100byte, 200byte, 300byte组成的链表,其RunningIndex分别是0, 100, 200。

另外,在实际的使用过程中,往往是不停的释放链表头部的节点,并且在尾部添加新节点。 RunningIndex表示的索引一般是逻辑意义上的索引,在释放头节点时,一般不用更新其子节点以及后续节点的RunningIndex。

ReadOnlySequence<T>

ReadOnlySequenceSegment<T>虽然能解决我们的动态内存的申请和释放问题,但它往往并不好用,因为很容易出现一段连续的数据被分割在多个节点的情况,在这段不连续的数据里进行查询是非常不便的。

为了解决这个问题,.net core中推出了一个视图类ReadOnlySequence<T>

ReadOnlySequence<T>由两个属性标记:

  • Start: 起始SequenceSegment以及起始索引
  • End: 结尾SequenceSegment以及结尾索引

可以通过foreach遍历各节点的Memory

  var seq = new ReadOnlySequence<byte>();
  foreach (ReadOnlyMemory<byte> memory in seq)
  {
  }

ReadOnlySequence的主要优势在于,它可以看成一段逻辑意义上的连续内存,常用的函数有:

  • Slice: 对视图数据切片
  • PositionOf: 查询元素的缩影
  • ToArray: 转换成数组

其中的ToArray涉及到大量的数据拷贝,需要谨慎使用。

另外.net core 3.0中还内置了一个SequenceReader,用起来是十分方便的:

private static ReadOnlySpan<byte> CRLF => new byte[] { (byte)'\r', (byte)'\n' };

public static void ReadLines(ReadOnlySequence<byte> sequence)
{
SequenceReader<byte> reader = new SequenceReader<byte>(sequence); while (!reader.End)
{
if (!reader.TryReadToAny(out ReadOnlySpan<byte> line, CRLF, advancePastDelimiter: false))
{
// Couldn't find another delimiter
// ...
} if (!reader.IsNext(CRLF, advancePast: true))
{
// Not a good CR/LF pair
// ...
} // line is valid, process
ProcessLine(line);
}
}

如何使用

用过System.IO.Pipelines的朋友就知道,ReadOnlySequence在该库中是非常好用的。但如果我们想创建一个ReadOnlySequence,发现并不是那么容易,因为:

  1. ReadOnlySequence依赖于ReadOnlySequenceSegment
  2. ReadOnlySequenceSegment是抽象类,需要自己继承

也就是说我们需要自己实现ReadOnlySequenceSegment<T>,然后再将其封装到ReadOnlySequence中,目前.net core中并没有内置实现可能是因为在高效内存管理的方案中并没有什么通用的解决方案吧。

如果我们要自己实现ReadOnlySequence,一般需要如下几个步骤:

  1. 继承ReadOnlySequenceSegment类,实现自己的SequenceSegment
  2. 在申请内存过程中,创建SequenceSegment,并将其挂成链表
  3. 使用数据时,在该链表中创建ReadOnlySequence
  4. 当SequenceSegment节点的内存使用完成的时候,从链表中接触该节点,并释放内存。

简单来说就是如下几种操作:

  • 数据读取: 创建SequenceSegment
  • 数据使用: 在SequenceSegment链表上创建ReadOnlySequence
  • 使用完成: 释放SequenceSegment

如果要更进一步优化,在SequenceSegment中的内存申请和释放可以使用内存池。

.net core中的高效动态内存管理方案的更多相关文章

  1. FreeRTOS 动态内存管理

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面 ...

  2. C++中对C的扩展学习新增语法——动态内存管理

    1.C语言动态内存管理的缺点: 1.malloc对象的大小需要自己计算. 2.需要手动转换指针类型. 3.C++的对象不适合使用malloc和free. 2.C++中new/delete基本使用: 3 ...

  3. C语言中储存类别和内存管理

    C语言中储存类别和内存管理 储存类别 C语言提供了多种储存类别供我们使用,并且对应的有对应的内存管理策略,在了解C中的储存类型前,我们先了解一下与储存类型相关的一些概念. 1. 基础概念 对象:不同于 ...

  4. C++动态内存管理之shared_ptr、unique_ptr

    C++中的动态内存管理是通过new和delete两个操作符来完成的.new操作符,为对象分配内存并调用对象所属类的构造函数,返回一个指向该对象的指针.delete调用时,销毁对象,并释放对象所在的内存 ...

  5. uCGUI动态内存管理

    动态内存的堆区 /* 堆区共用体定义 */ typedef union { /* 可以以4字节来访问堆区,也可以以1个字节来访问 */ ]; /* required for proper aligne ...

  6. 从Profile中窥探Unity的内存管理

    刨根问底U3D---从Profile中窥探Unity的内存管理 这篇文章包含哪些内容 这篇文章从Unity的Profile组件入手,来探讨一下Unity在开发环境和正式环境中的内存使用发面的一些区别, ...

  7. Keil C动态内存管理机制分析及改进(转)

    源:Keil C动态内存管理机制分析及改进 Keil C是常用的嵌入式系统编程工具,它通过init_mempool.mallloe.free等函数,提供了动态存储管理等功能.本文通过对init_mem ...

  8. 深入理解C++ new/delete, new []/delete[]动态内存管理

    在C语言中,我们写程序时,总是会有动态开辟内存的需求,每到这个时候我们就会想到用malloc/free 去从堆里面动态申请出来一段内存给我们用.但对这一块申请出来的内存,往往还需要我们对它进行稍许的“ ...

  9. C++程序设计入门 引用和动态内存管理学习

    引用: 引用就是另一个变量的别名,通过引用所做的读写操作实际上是作用于原变量上. 由于引用是绑定在一个对象上的,所以定义引用的时候必须初始化. 函数参数:引用传递 1.引用可做函数参数,但调用时只需 ...

随机推荐

  1. 解决tomcat下面部署多个项目log4j的日志输出会集中输出到一个项目中的问题

    在一次项目上线后,发现了一个奇怪的问题,经过对源码的阅读调试终于解决,具体经过是这样的: 问题描述:tomcat7下面部署多个项目,log4j的日志输出会集中输出到一个项目中,就算配置了日志文件的绝对 ...

  2. java Concurrent 中的数据结构

    一:阻塞数据结构(线程安全) ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列. PriorityBl ...

  3. 【ORACLE】oracl基本操作笔记

    1.用命令导入导出表 C:\Users\xiang>imp bjlims/bjlims@orcl file="c:\tjlims.dmp" full=y C:\Users\x ...

  4. imperva—waf 敏感字段显现

    imperva WAF中看到的日志内容信息有些都是敏感的  比如登录登出的信息 如何调整敏感信息的现实方式,并可以自定义敏感字段? 这里添加字段就可以了 这样就将******转变为明文了

  5. 【Python】使用Python将Shellcode转换成汇编

    1.介绍 需要多少行代码转换hex成反汇编呢? 多亏了Python的Capstone库,做这件事只需要五行. 在二进制分析中,进行Exploit开发或逆向工程时,需要快速将十六进制的Shellcode ...

  6. sync 解释

    sync命令用于强制被改变的内容立刻写入磁盘,更新超块信息. 在Linux/Unix系统中,在文件或数据处理过程中一般先放到内存缓冲区中,等到适当的时候再写入磁盘,以提高系统的运行效率.sync命令则 ...

  7. linux压缩日志并删除原始文件

    下面的脚本可以压缩日志并删除原始文件 #!/bin/bash yesterday=`date -d '1days ago' +%Y_%m_%d` cd $ find . -name "*$y ...

  8. mybatis generator 双击创建失败

    https://coding.imooc.com/learn/questiondetail/20460.html 用的下面这个方法

  9. .NetCore中如何实现权限控制 基于Claim角色、策略、基于Claim功能点处理

    .NetCore中如果实现权限控制的问题,当我们访问到一个Action操作的时候,我们需要进行权限控制 基于claims 角色控制 基于角色控制总觉得范围有点过大,而且控制起来感觉也不是太好,举一个例 ...

  10. Vuejs 高仿饿了么外卖APP 百度云视频教程下载

    Vuejs 高仿饿了么外卖APP 百度云视频教程下载 链接:https://pan.baidu.com/s/1KPbKog0qJqXI-2ztQ19o7w 提取码: 关注公众号[GitHubCN]回复 ...