Microsoft.IO.RecyclableMemoryStream原始碼解讀

攀攀發表於2022-12-03

一、RecyclableMemoryStreamManager

原始碼地址:https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream

小物件池和大物件池管理、RecyclableMemoryStream建立、各場景的ETW訊息\事件鉤子;執行緒安全

 

Microsoft.IO.RecyclableMemoryStream原始碼解讀

備註:官方這張圖,只是池塊增長策略闡述,不能很好理解其內部池具體實現。小物件還好理解,大物件組織分配並不像畫的這樣。

1.1、建構函式引數

blockSize:小物件池,塊大小;預設128KB。

largeBufferMultiple:大物件池,策略被乘數大小;預設1M。

maximumBufferSize:大物件池,塊最大大小;預設128M。

useExponentialLargeBuffer:大物件池策略,true:指數、false:線性;一個類實體只能對應一種策略,如果想使用2種策略,就要定義2個類實體;預設線性。

maximumSmallPoolFreeBytes:byte[] 歸還小物件池,小物件池最大大小;超過GC回收,否則歸還小物件池;預設不限制。

maximumLargePoolFreeBytes:byte[] 歸還大物件池,大物件池最大大小;超過GC回收,否則歸還大物件池;預設不限制。

1.2、小物件池

private readonly ConcurrentStack<byte[]> smallPool; //小物件池

private long smallPoolFreeSize; //小物件池歸還(空閒)byte大小
private long smallPoolInUseSize; //小物件借出(使用)byte大小

smallPool 使用執行緒安全堆疊,每個元素bye[] 大小一樣,對應blockSize
如:blockSize = 128K,則bye[] 大小都是 128K

maximumSmallPoolFreeBytes = 128M,則代表smallPool 所有塊大小總和最大值為128M,超出則歸還時不儲存到最小池中

1.3、大物件池

private readonly ConcurrentStack<byte[]>[] largePools; //大物件池

private readonly long[] largeBufferFreeSize; //大物件每個層級,歸還(空閒)byte大小
private readonly long[] largeBufferInUseSize; //大物件每個層級,借出(使用)byte大小

largePools 使用執行緒安全堆疊的陣列,陣列每個索引對應指數/線性一個層級大小,每個層級bye[] 大小一樣。

如:largeBufferMultiple = 1M、maximumBufferSize = 128M
則,指數 1M、2M、4M、8M、16M、32M、64M、128M,largePools.Length = 8;largePools[2] 下面bye[] 大小都是4M
  線性 1M、2M、3M、4M、5M、6M、7M、......、128M,largePools.Length = 128;largePools[2] 下面bye[] 大小都是3M


largeBufferFreeSize.Length == largePools.Length
largeBufferInUseSize = largePools.Length + 1 ,多出一個元素儲存,借出時requiredSize > maximumBufferSize 所有byte[]大小,此byte[] 無法歸還到大物件池,會被GC直接回收。

maximumLargePoolFreeBytes = 256M,則代表大池的各個維度塊大小總和最大值,超出則歸還時不儲存到池中,各個維度如:線性有128個維度,指數8個維度,各個維度都是堆疊
線性最大空間:256M * 128 = 32G
指數最大空間:256M * 8 = 2G

1.4、byte[] 借出/歸還

借出:

internal byte[] GetBlock() //從小物件池獲取byte[],若無則直接建立 new byte[this.BlockSize]
    
internal byte[] GetLargeBuffer(long requiredSize, Guid id, string tag) //從大物件池獲取byte[],首先根據requiredSize計算對應大物件索引位置,若無則直接建立 new byte[requiredSize]

歸還:

internal void ReturnBlocks(List<byte[]> blocks, Guid id, string tag) //多個塊歸還小物件池,判斷是否maximumSmallPoolFreeBytes超出,不超出則歸還
internal void ReturnBlock(byte[] block, Guid id, string tag) //單個塊歸還小物件池,判斷是否maximumSmallPoolFreeBytes超出,不超出則歸還

//歸還大物件池,首先根據buffer.Length計算對應大物件索引位置,判斷索引對應層級大小是否maximumLargePoolFreeBytes超出,不超出則歸還
internal void ReturnLargeBuffer(byte[] buffer, Guid id, string tag) 

1.5、RecyclableMemoryStream建立

public MemoryStream GetStream(Guid id, string tag, long requiredSize, bool asContiguousBuffer)

asContiguousBuffer == true && requiredSize > this.BlockSize

請求連續byte[] 且 請求位元組大於1個小物件塊大小時,則使用大象池建立RecyclableMemoryStream,否則使用小物件池建立RecyclableMemoryStream。

其他過載方法,無asContiguousBuffer引數,預設使用小物件池建立RecyclableMemoryStream。

方法中,byte[] buffer、Memory<byte> buffer、ReadOnlySpan<byte> buffer 引數,會把其中的byte 資料寫入新申請的小物件blocks裡面,不會複用這些物件。

二、RecyclableMemoryStream

非執行緒安全

2.1、建構函式

internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, long requestedSize, byte[] initialLargeBuffer)

memoryManager:RecyclableMemoryStreamManager引用,呼叫其提供借出\歸還\通知狀態等方法或者屬性。

initialLargeBuffer:不為null代表使用大物件池,否則小物件池。GetBuffer()方法也會影響小物件池轉為大物件池。

requestedSize:根據請求大小,分配1個大物件池bye[] 或者多個小物件池Block byte[]

2.2、屬性

private readonly List<byte[]> blocks = new List<byte[]>(); //如果使用小物件池,則儲存借出的多個小物件

private byte[] largeBuffer; //如果使用大物件池,則儲存借出的大物件

/*
* 如果使用大物件池,Capacity調整需要更換更大的大物件;
* 老的大物件歸還,大物件池超出暫時無法回收大物件,則儲存到此,在物件Dispose時再次嘗試歸還。
* 因為可能有多次此情況發生,所有為List<>
*/
private List<byte[]> dirtyBuffers;

 2.3、釋放/關閉/析構方法

/// <summary>
/// The finalizer will be called when a stream is not disposed properly.
/// </summary>
/// <remarks>Failing to dispose indicates a bug in the code using streams. Care should be taken to properly account for stream lifetime.</remarks>
~RecyclableMemoryStream()
{
    // 析構方法,兜底釋放
	this.Dispose(false);
}

//非公開方法
/// <summary>
/// Returns the memory used by this stream back to the pool.
/// </summary>
/// <param name="disposing">Whether we're disposing (true), or being called by the finalizer (false).</param>
//disposing 區分是否解構函式呼叫
protected override void Dispose(bool disposing)
{
	if (this.disposed)
	{
		// 已釋放不在釋放,記錄通知事件
		string doubleDisposeStack = null;
		if (this.memoryManager.GenerateCallStacks)
		{
			doubleDisposeStack = Environment.StackTrace;
		}

		this.memoryManager.ReportStreamDoubleDisposed(this.id, this.tag, this.AllocationStack, this.DisposeStack, doubleDisposeStack);
		return;
	}

	//標記已釋放
	this.disposed = true;

	if (this.memoryManager.GenerateCallStacks)
	{
		this.DisposeStack = Environment.StackTrace;
	}

	this.memoryManager.ReportStreamDisposed(this.id, this.tag, this.AllocationStack, this.DisposeStack);

	if (disposing)
	{
		//已釋放,不用進入析構佇列,不會觸發解構函式。
		GC.SuppressFinalize(this);
	}
	else
	{
		// We're being finalized.
		this.memoryManager.ReportStreamFinalized(this.id, this.tag, this.AllocationStack);

		//如果此應用程式域正在解除安裝,並且公共語言執行時已開始呼叫終止程式,則不執行歸還池邏輯。
		if (AppDomain.CurrentDomain.IsFinalizingForUnload())
		{
			// If we're being finalized because of a shutdown, don't go any further.
			// We have no idea what's already been cleaned up. Triggering events may cause
			// a crash.
			base.Dispose(disposing);
			return;
		}
	}

	this.memoryManager.ReportStreamLength(this.length);

	if (this.largeBuffer != null)
	{
		//歸還大物件
		this.memoryManager.ReturnLargeBuffer(this.largeBuffer, this.id, this.tag);
	}

	if (this.dirtyBuffers != null)
	{
		//再次嘗試歸還老的大物件列表
		foreach (var buffer in this.dirtyBuffers)
		{
			this.memoryManager.ReturnLargeBuffer(buffer, this.id, this.tag);
		}
	}

	//歸還小物件塊列表
	this.memoryManager.ReturnBlocks(this.blocks, this.id, this.tag);
	this.blocks.Clear();

	base.Dispose(disposing);
}

//公共方法
/// <summary>
/// Equivalent to <c>Dispose</c>.
/// </summary>
public override void Close()
{
	this.Dispose(true);
}

 2.4、小物件使用內部類

標識位置資訊,方便引數傳遞,操作blocks屬性。

private struct BlockAndOffset
{
	public int Block; //小物件塊所在整體位置索引
	public int Offset; //小物件塊中未使用位元組開始位置\已使用位元組結束位置

	public BlockAndOffset(int block, int offset)
	{
		this.Block = block;
		this.Offset = offset;
	}
}

 2.5、不連續位元組流

public ReadOnlySequence<byte> GetReadOnlySequence()
{
	this.CheckDisposed();

	if (this.largeBuffer != null)
	{
		//大物件,只有1個位元組陣列,連續的
		AssertLengthIsSmall();
		return new ReadOnlySequence<byte>(this.largeBuffer, 0, (int)this.length);
	}

	if (this.blocks.Count == 1)
	{
		//小物件1個塊,只有1個位元組陣列,連續的
		AssertLengthIsSmall();
		return new ReadOnlySequence<byte>(this.blocks[0], 0, (int)this.length);
	}

	//小物件多個塊,多個位元組資料,不連續的
	var first = new BlockSegment(this.blocks[0]);
	var last = first;

	//建立關聯下一個塊物件
	for (int blockIdx = 1; last.RunningIndex + last.Memory.Length < this.length; blockIdx++)
	{
		last = last.Append(this.blocks[blockIdx]);
	}

	//首尾物件
	return new ReadOnlySequence<byte>(first, 0, last, (int)(this.length - last.RunningIndex));
}

private sealed class BlockSegment : ReadOnlySequenceSegment<byte>
{
	public BlockSegment(Memory<byte> memory) => Memory = memory;

	public BlockSegment Append(Memory<byte> memory)
	{
		var nextSegment = new BlockSegment(memory) { RunningIndex = RunningIndex + Memory.Length };
		Next = nextSegment;
		return nextSegment;
	}
}

 2.6、IBufferWriter<T>介面實現

private byte[] bufferWriterTempBuffer;

private ArraySegment<byte> GetWritableBuffer(int sizeHint)
{
	this.CheckDisposed();
	if (sizeHint < 0)
	{
		throw new ArgumentOutOfRangeException(nameof(sizeHint), $"{nameof(sizeHint)} must be non-negative.");
	}

	var minimumBufferSize = Math.Max(sizeHint, 1);

	this.EnsureCapacity(this.position + minimumBufferSize);
	if (this.bufferWriterTempBuffer != null)
	{
		this.ReturnTempBuffer(this.bufferWriterTempBuffer);
		this.bufferWriterTempBuffer = null;
	}

	if (this.largeBuffer != null)
	{
		return new ArraySegment<byte>(this.largeBuffer, (int)this.position, this.largeBuffer.Length - (int)this.position);
	}

	BlockAndOffset blockAndOffset = this.GetBlockAndRelativeOffset(this.position);
	int remainingBytesInBlock = this.MemoryManager.BlockSize - blockAndOffset.Offset;
	if (remainingBytesInBlock >= minimumBufferSize)
	{
		//分配小物件,範圍屬於一個block,則返回block連續段
		return new ArraySegment<byte>(this.blocks[blockAndOffset.Block], blockAndOffset.Offset, this.MemoryManager.BlockSize - blockAndOffset.Offset);
	}

	//分配小物件,單位大於一個block塊,則透過大物件/小物件分配byte[];記錄賦值給屬性bufferWriterTempBuffer;規避不好返回多個blocks中byte
	this.bufferWriterTempBuffer = minimumBufferSize > this.memoryManager.BlockSize ?
		this.memoryManager.GetLargeBuffer(minimumBufferSize, this.id, this.tag) :
		this.memoryManager.GetBlock();

	return new ArraySegment<byte>(this.bufferWriterTempBuffer);
}

public void Advance(int count)
{
	this.CheckDisposed();
	if (count < 0)
	{
		throw new ArgumentOutOfRangeException(nameof(count), $"{nameof(count)} must be non-negative.");
	}

	byte[] buffer = this.bufferWriterTempBuffer;
	if (buffer != null)
	{
		if (count > buffer.Length)
		{
			throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {buffer.Length}.");
		}
        
		//把bufferWriterTempBuffer屬性中資料,寫回小物件blocks
		this.Write(buffer, 0, count);
		this.ReturnTempBuffer(buffer);
		this.bufferWriterTempBuffer = null;
	}
	else
	{
		long bufferSize = this.largeBuffer == null
			? this.memoryManager.BlockSize - this.GetBlockAndRelativeOffset(this.position).Offset
			: this.largeBuffer.Length - this.position;

		if (count > bufferSize)
		{
			throw new InvalidOperationException($"Cannot advance past the end of the buffer, which has a size of {bufferSize}.");
		}

		this.position += count;
		this.length = Math.Max(this.position, this.length);
	}
}

 2.7、GetBuffer()

如果使用大物件,則返回大物件資料

如果使用小物件+塊1個,則直接返回這個塊。如果大於1個,則申請新大物件返回,之前小物件歸還。升級為使用大物件。

2.8、ToArray()

透過new byte[this.Length] 建立新byte陣列,複製大物件/小物件資料過來。

相關文章