首页 > 系统相关 >GC优化:栈内存、span、NativeMemory、指针、池化内存 笔记

GC优化:栈内存、span、NativeMemory、指针、池化内存 笔记

时间:2024-11-16 18:45:50浏览次数:1  
标签:span string NativeMemory Length separator 内存 values ref public

stackalloc


使用栈内存,减少GC压力

var wordMatchCounts = stackalloc float[wordCount];

Span


Span 支持 reinterpret_cast 的理念,即可以将 Span 强制转换为 Span

Span 支持 reinterpret_cast 的理念,即可以将 Span 强制转换为 Span(其中,Span 中的索引 0 映射到 Span 的前四个字节)。这样一来,如果读取字节缓冲区,可以安全高效地将它传递到对分组字节(视作整数)执行操作的方法。

Span 也能装在集合之ValueListBuilder & .AsSpan()

.NET 内部提升性能对象:ValueListBuilder & .AsSpan()

ValueListBuilder & .AsSpan()

.NET Core 源码中的内部提升性能对象:ValueListBuilder & .AsSpan()

它在 String.Replace 中被使用

public unsafe string Replace(string oldValue, string? newValue) {
	  ArgumentException.ThrowIfNullOrEmpty(oldValue, nameof (oldValue));
	  if (newValue == null)	newValue = string.Empty;
	  // ISSUE: untyped stack allocation
	  ValueListBuilder<int> valueListBuilder = new ValueListBuilder<int>(new Span<int>((void*) __untypedstackalloc(new IntPtr(512)), 128));

	  if (oldValue.Length == 1)
	  {
		if (newValue.Length == 1)
		  return this.Replace(oldValue[0], newValue[0]);
		char ch = oldValue[0];
		int elementOffset = 0;

		while (true)
		{
		  int num = SpanHelpers.IndexOf(ref Unsafe.Add<char>(ref this._firstChar, elementOffset), ch, this.Length - elementOffset);
		  if (num >= 0){
			valueListBuilder.Append(elementOffset + num);
			elementOffset += num + 1;
		  }
		  else break;
		}
	  }
	  else{
		int elementOffset = 0;
		while (true){
		  int num = SpanHelpers.IndexOf(ref Unsafe.Add<char>(ref this._firstChar, elementOffset), this.Length - elementOffset, ref oldValue._firstChar, oldValue.Length);
		  if (num >= 0){
			valueListBuilder.Append(elementOffset + num);
			elementOffset += num + oldValue.Length;
		  }
		  else break;
		}
	  }
	  if (valueListBuilder.Length == 0) eturn this;
	  string str = this.ReplaceHelper(oldValue.Length, newValue, **valueListBuilder.AsSpan()**);
	  valueListBuilder.Dispose();
	  return str;
	}

.NET 内部类直接将集合转回为 Span<T>:CollectionsMarshal.AsSpan<string>(List<string>)

	private static unsafe string JoinCore<T>(ReadOnlySpan<char> separator, IEnumerable<T> values){

	  if (typeof (T) == typeof (string)){
		if (values is List<string> list)
		  return string.JoinCore(separator, (ReadOnlySpan<string>) CollectionsMarshal.AsSpan<string>(list));
		if (values is string[] array)
		  return string.JoinCore(separator, new ReadOnlySpan<string>(array));
	  }

ref struct,使用ref读取值类型,避免值类型拷贝


使用ref读取值类型,避免值类型拷贝,但要注意对当前值类型的修改,会影响被ref的那个值类型,因为本质上你在操作一个指针

ref var hierarchy = ref ph[i];
ref var words = ref hierarchy.Words;

Unsafe.IsNullRef

可以使用 Unsafe.IsNullRef 来判断一个 ref 是否为空。如果用户没有对 Foo.X 进行初始化,则默认是空引用:

ref  struct Foo {
  public  ref  int X;
  public  bool IsNull => Unsafe.IsNullRef(ref X);
  public  Foo(ref  int x) { X = ref x; }
}

1.4 NativeMemory

相比 Marshal.AllocHGlobal 和 Marshal.FreeHGlobal,其实现在更推荐 NativeMemory.*,有诸多好处:

  • 支持控制是否零初始化

  • 支持控制内存对齐

  • 参数是 nuint 类型,支持在 64 位进程上支持分配超过 int 上限的大小

1.5 struct 直接转换内存数据

1.5.1 C#使用struct直接转换下位机数据

数据结构
假定下位机(C语言编写)给到我们的数据结构是这个,传输方式为小端方式

typedef struct {

		 unsigned long int time;          // 4个字节

		 float tmpr[3];                   //  4*3 个字节

		 float forces[6];                 //  4*6个字节

		 float distance[6];               // 4*6个字节

} dataItem_t;

方法1(略麻烦)

首先需要定义一个struct:

[StructLayout(LayoutKind.Sequential, Size = 64, Pack = 1)]
public struct HardwareData {
	//[FieldOffset(0)]
	public UInt32 Time; // 4个字节

	[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
	//[FieldOffset(4)]
	public float[] Tmpr; //  3* 4个字节
	
	//[FieldOffset(16)]
	[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
	public float[] Forces; //  6* 4个字节

	//[FieldOffset(40)]
	[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
	public float[] Distance; //  6*4个字节

}

然后使用以下代码进行转换

// converts byte[] to struct
public static T RawDeserialize(byte[] rawData, int position) {
	int rawsize = Marshal.SizeOf(typeof(T));
	if (rawsize > rawData.Length - position) throw new ArgumentException("Not enough data to fill struct. Array length from position: " + (rawData.Length - position) + ", Struct length: " + rawsize);
	IntPtr buffer = Marshal.AllocHGlobal(rawsize);
	Marshal.Copy(rawData, position, buffer, rawsize);
	T retobj = (T)Marshal.PtrToStructure(buffer, typeof(T));
	Marshal.FreeHGlobal(buffer);
	return retobj;
}

// converts a struct to byte[]
public static byte[] RawSerialize(object anything) {
	int rawSize = Marshal.SizeOf(anything);
	IntPtr buffer = Marshal.AllocHGlobal(rawSize);
	Marshal.StructureToPtr(anything, buffer, false);
	byte[] rawDatas = new byte[rawSize];
	Marshal.Copy(buffer, rawDatas, 0, rawSize);
	Marshal.FreeHGlobal(buffer);
	return rawDatas;
}

注意这里我使用的方式为LayoutKind.Sequential,如果直接使用LayoutKind.Explicit并设置FieldOffset会弹出一个诡异的错误System.TypeLoadException:"Could not load type 'ConsoleApp3.DataItem' from assembly 'ConsoleApp3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field."。

方法2

既然用上了 unsafe,就干脆直接一点。

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct DataItem {
	public UInt32 time; // 4个字节
	public fixed float tmpr[3]; //  3* 4个字节
	public fixed float forces[6]; //  6* 4个字节
	public fixed float distance[6]; //  6*4个字节
}

这样,获得数组可以直接正常访问了。


C#使用struct直接转换下位机数据

https://blog.51cto.com/u_15127641/2754559

1.6 string.Join 内部实现解析

CollectionsMarshal.AsSpan(valuesList)

if (values is List<string?> valuesList) {
    return JoinCore(separator.AsSpan(), CollectionsMarshal.AsSpan(valuesList));
}
if (values is string?[] valuesArray)
{
    return JoinCore(separator.AsSpan(), new ReadOnlySpan<string?>(valuesArray));
}

Join

public static string Join(string? separator, IEnumerable<string?> values)
{
	if (values is List<string?> valuesList)
	{
		return JoinCore(separator.AsSpan(), CollectionsMarshal.AsSpan(valuesList));
	}
	if (values is string?[] valuesArray)
	{
		return JoinCore(separator.AsSpan(), new ReadOnlySpan<string?>(valuesArray));
	}
	if (values == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);
	}
	using (IEnumerator<string?> en = values.GetEnumerator())
	{
		if (!en.MoveNext())
		{
			return Empty;
		}
		string? firstValue = en.Current;
		if (!en.MoveNext())
		{
			// Only one value available
			return firstValue ?? Empty;
		}
		// Null separator and values are handled by the StringBuilder
		var result = new ValueStringBuilder(stackalloc char[256]);
		result.Append(firstValue);
		do
		{
			result.Append(separator);
			result.Append(en.Current);
		}
		while (en.MoveNext());
		return result.ToString();
	}
}

JoinCore

private static string JoinCore(ReadOnlySpan<char> separator, ReadOnlySpan<string?> values)
{
	if (values.Length <= 1)
	{
		return values.IsEmpty ?
			Empty :
			values[0] ?? Empty;
	}

	long totalSeparatorsLength = (long)(values.Length - 1) * separator.Length;
	if (totalSeparatorsLength > int.MaxValue)
	{
		ThrowHelper.ThrowOutOfMemoryException();
	}
	int totalLength = (int)totalSeparatorsLength;

	// Calculate the length of the resultant string so we know how much space to allocate.
	foreach (string? value in values)
	{
		if (value != null)
		{
			totalLength += value.Length;
			if (totalLength < 0) // Check for overflow
			{
				ThrowHelper.ThrowOutOfMemoryException();
			}
		}
	}

	// Copy each of the strings into the result buffer, interleaving with the separator.
	string result = FastAllocateString(totalLength);
	int copiedLength = 0;

	for (int i = 0; i < values.Length; i++)
	{
		// It's possible that another thread may have mutated the input array
		// such that our second read of an index will not be the same string
		// we got during the first read.

		// We range check again to avoid buffer overflows if this happens.

		if (values[i] is string value)
		{
			int valueLen = value.Length;
			if (valueLen > totalLength - copiedLength)
			{
				copiedLength = -1;
				break;
			}

			// Fill in the value.
			FillStringChecked(result, copiedLength, value);
			copiedLength += valueLen;
		}

		if (i < values.Length - 1)
		{
			// Fill in the separator.
			// Special-case length 1 to avoid additional overheads of CopyTo.
			// This is common due to the char separator overload.

			ref char dest = ref Unsafe.Add(ref result._firstChar, copiedLength);

			if (separator.Length == 1)
			{
				dest = separator[0];
			}
			else
			{
				separator.CopyTo(new Span<char>(ref dest, separator.Length));
			}

			copiedLength += separator.Length;
		}
	}

	// If we copied exactly the right amount, return the new string.  Otherwise,
	// something changed concurrently to mutate the input array: fall back to
	// doing the concatenation again, but this time with a defensive copy. This
	// fall back should be extremely rare.
	return copiedLength == totalLength ?
		result :
		JoinCore(separator, values.ToArray().AsSpan());
}

标签:span,string,NativeMemory,Length,separator,内存,values,ref,public
From: https://www.cnblogs.com/darklx/p/18549678

相关文章

  • delphi 新版内存表 FDMemTable
    c++builderXE官方demo最全60多个http://community.embarcadero.com/blogs?view=entry&id=8761  FireDAC.Comp.Client 用好FDMemTable代替之前的ClientDataSet,以前ClientDataSet内存表转换太繁琐了步骤。 TClientDataSet*cds=newTClientDataSet(this);  DataSetP......
  • 中科蓝讯内存COM区和Bank区:【图文讲解】
    中科蓝讯芯片采用最近比较流行的RISC-V(32位)开源内核架构+国产RT-Thread操作系统. 不过从代码上来看, 操作系统代码已经被封装到库中,一般用户可以不用涉及操作系统代码,降低了开发难度.中科蓝讯芯片 "冯·诺依曼结构",即代码与数据的统一编址.框架结构大致如下:......
  • C 语言的内存管理
    目录1.简介2.void指针3.malloc()4.free()5.calloc()6.realloc()7.restrict说明符8.memcpy()9.memmove()10.memcmp()1.简介C语言的内存管理,分成两部分。一部分是系统管理的,另一部分是用户手动管理的。系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量......
  • DDR内存基础知识和带宽测试
    一、DDR基础知识1.几个频率(1)核心频率:真实运行频率。(2)倍增系数:DDR通过数据预取技术放大速率,每代ddr倍率是固定的,ddr=2,ddr2=4,ddr3=8,ddr4=8,ddr5=16(3)有效频率;厂商标注的频率,可以理解为数据传输速率。厂商也想逐步淡化其它频率的概念,只让我们记住有效频率。其实......
  • JVM内存以及垃圾回收
    JVM基本概念线程JVM内存区域程序计数器(线程私有)虚拟机栈(线程私有)本地方法区(线程私有)堆(Heap-线程共享)-运行时数据区方法区/永久代(线程共享)JVM运行时内存新生代Eden区ServivorFromServivorToMinorGC的过程(复制->清空->互换)eden、ServivorFrom复制到ServivorTo,年龄+1清空......
  • 【C语言指南】C语言内存管理 深度解析
           ......
  • iPhone内存恢复:​​如何从iPhone内存中恢复数据?
    我们必须接受这样一个事实:没有100%有效的方法来避免iPhone上的数据丢失。您可能会因错误删除而丢失iPhone存储中的数据。或者,您可以将iPhone重置为出厂设置以修复一些故障,重置后所有数据都会消失。由于iPhone没有外置卡,您可能会问:有没有办法从iPhone内置存储卡中恢复数......
  • Python cache 内存泄漏问题
    @functools.cache函数装饰器在一些特殊情况的时候会影响变量引用计数器的计数,从而导致内存泄漏。比如:@functools.cache和@@functools.property一起使用的时候,或者更复杂的嵌套引用1fromfunctoolsimportwraps234classCacheManager:5def__init__(self):......
  • C语言进阶3:字符串+内存函数
    本章重点求字符串长度strlen长度不受限制的字符串函数strcpystrcatstrcmp长度受限制的字符串函数strncpystrncatstrncmp字符串查找strstrstrtok误信息报告strerror字符操作内存操作memcpymemmovememcmpmemset0.前言:C语言中对字符和字符串的处理很是......
  • 网页直播/点播播放器EasyPlayer.js RTSP播放器出现多路视频卡顿、内存开始飙升的原因
    EasyPlayer.jsRTSP播放器是TSINGSEE青犀流媒体组件系列中关注度较高的产品,经过多年的发展和迭代,目前已经有多个应用版本,包括RTSP版、RTMP版、Pro版以及js版,其中js版本作为网页播放器,受到了用户的广泛使用。1、问题说明在已经使用硬解码基础上,播放多路视频,会出现卡顿,内存开始飙......