关于.net core高性能编程中的Span
如果对Span
内存片段ReadOnlySequenceSegment
假设你已经了解了Memory
这个我理解得不是很准确,但总体来说就是我们一个完整的数据分成了多个内存片段,每个内存片段用Memory
ReadOnlySequenceSegment
1 public ReadOnlyMemoryMemory { get; protected set; } 2 public ReadOnlySequenceSegment ? Next { get; protected set; } 3 public long RunningIndex { get; protected set; }
- Memory:表示这个链表节点下的内存数据,也就是上面的Memory1、2、3
- Next:就是指向的下一个节点
- RunningIndex:指当前节点之前的节点的数据之和,比如Memory1里有1个字节、Memeory2里有2个字节,那么Memory3对应节点的RunningIndex就是3
这玩意是个抽象类,不过暂时可以不关心,因为我们通常开发时都可以从某个方法的参数获得ReadOnlySequenceSegment
这里重点记住:
- ReadOnlySequenceSegment里面存储的ReadOnlyMemory
(理解上约等于byte[]) - 多个ReadOnlySequenceSegment可以组成一个链表,从逻辑上表示一个完整的数据,ReadOnlySequenceSegment只是其中一个节点
内存片段容器ReadOnlySequence
上面说的这个内存片段链表其实已经可以从逻辑上表示一段完整的数据了,但是ReadOnlySequenceSegment
这里说的容器不是很准确,因为ReadOnlySequence只是存储了整个链表的首位节点,但是由于是链表,其实只要知道首节点,就可以通过Next递归获得整个链表的所有节点,因此我这里把它称为容器
下面引用官方文档的一张图
绿色框中有3段蓝色块,我们可以理解为是链表中的一个节点(ReadOnlySequenceSegment),由于这个节点内部重要的就是保存着具体的数据Memory
根据绿色部分的3个不连续的内存片段,可以生成一个表示逻辑上连续的内存片段集合ReadOnlySequence,这个ReadOnlySequence包含3个Memory
注:上面简写的16进制,A=0x0A
连续内存片段中的索引SequencePosition
只要知道一个数据在哪个片段中,并且知道它在这个片段中的哪个位置,就能表示一个具体的索引了。
但特别注意这个索引是针对原始链表来说的,也就是上面绿色快的部分,比如图片中的“4”在第1段的索引3的位置;“A”,在第2段的索引2处。这种情况没有办法用单个数字来表示索引,因此单独定义了SequencePosition来表示索引。
ReadOnlySequence的api
- 构造函数ReadOnlySequence(ReadOnlySequenceSegment
startSegment, int startIndex, ReadOnlySequenceSegment endSegment, int endIndex) - startSegment:链表的首个节点
- startIndex:首个节点不一定完全加入到ReadOnlySequence,此参数表示从第几个值开始
- endSegment:链表的尾节点
- endIndex:尾节点也不一定完全加入ReadOnlySequence,此参数表示要加入的索引+1
- 按上图所示,代码应该这样:new ReadOnlySequence(片段1,3,片段3,1); 注意最后一个参数是1,可以简单理解为在尾节点取前几个值加入到ReadOnlySequence
- End:就是最后一个片段的最后一个数据的索引对象,就是图片中的片段3索引1
- Start:第一个片段的索引,片段1,索引2
- Length:ReadOnlySequence包含的值的长度,按图中就是4 5 6 ....D F 2 长度为10
- GetPosition(int index):获取第几个值的索引对象,比如GetPosition(0),那就是黄色块的0为4,它所处于绿色块的索引为:片段1,索引2;GetPosition(4),那就是黄色块的2,所处绿色快的片段2,索引1
- PositionOf(T value):查早某个值在这个序列中所处的索引,比如PositionOf(4),那就是在黄色块的片段1的索引0处,最终结果就是绿色块片段1的索引3处
- Slice():从这个连续内存片段集合中指定索引处开始,取一段数据,返回的是一个新的ReadOnlySequence。有几个重载,比较容易猜到它的意义
-
bool TryGet(ref SequencePosition position, out ReadOnlyMemory
memory, bool advance = true) 尝试从指定索引处开始读取,所指定的索引处所在片段还有剩余数据,则本次读取这些剩余数据,否则读取下一个片段的数据。最终若读取成功,则返回true,且将读取到的数据赋值给memory参数。advance为true时,position将被直接赋值为下一个片段的索引0处。理解这个再看官方文档那个循环就容易了。
主要api就这几个。
后续
即使自己造轮子时不在乎性能,在使用一些第三方库时也可能会遇到此对象,对它有些了解的话不至于太迷茫。.net core中提供了System.Buffers命名空间,里面包含好几个跟字节数组处理相关的类,后面学到哪里就纪录到哪里。它是System.IO.Pipelines的基础。而System.IO.Pipelines又是编写高性能程序必不可少的玩意。
下一篇学完SequenceReader