可在托管空间中使用的非托管内存中的可变字符串

问题描述

注意:我的情况是在旧 API 的生态系统中,它只适用于字符串,没有现代 .NET 添加

所以我非常需要没有分配的可变字符串。 String 每 X ms 更新一次,因此您可以在几分钟内计算出它可以产生多少垃圾(StringBuilder 在这里根本不相关)。我目前的方法是预先分配固定大小的字符串,并通过固定来改变它,直接写入字符,或者在容量达到时静脱落或抛出。

这很好用。分配的字符串是长期存在的,因此最终 GC 会将其提升到 Gen2,并且固定不会那么麻烦,从而最大限度地减少开销。不过有 2 个主要问题

  1. 因为字符串是固定的,我必须用 \0 填充它,虽然到目前为止所有认的 NET/MONO 功能和 3rd 方的东西都很好用,但无法知道当字符串为 1024 时其他东西会如何反应在 len 中,但最后 100 个是 \0
  2. 我无法调整它的大小,因为这会导致分配。我可以在蓝月亮一次分配一次,但由于字符串相当动态,我无法确定它何时会尝试进一步扩大或缩小。我可以使用 "expand only" 方法,这样我只在需要扩展时分配,但是,这有填充开销的缺点(如果字符串扩展为 5k 个字符,但下一个字符串只是 3k - 2k字符将被填充以获得额外的周期)以及内存额外使用。我不确定 GC 对 mchuge 的感觉如何,通常在 Gen2 中固定字符串而不是在 LOH 中。 另一种方式是池可重用字符串对象,但是,这具有更高的内存和 GC 开销 + 查找开销。

由于目标字符串必须存在相当长的一段时间,我正在考虑通过字节缓冲区将其移动到 Unmanaged memory 中。这将消除 GC 的负担(固定惩罚开销),并且我可以以比托管堆更低的成本重新调整大小/重新分配。 我很难理解的是 - 我怎么可能对分配的非托管缓冲区的特定部分进行切片并将其包装为普通的网络字符串以在托管空间/代码中使用?比如,将它传递给 Console.WriteLine 或一些在屏幕上绘制 UI 标签并接受字符串的第三方库。这甚至可行吗?

附言据我所知,NET5 的计划(我认为将在 NET6 中完成)你将不再能够改变字符串之类的东西(在运行时被阻止或未定义的失败)。他们的解决方案似乎是 POH,这基本上就是我所描述的,但具有相同的局限性。

解决方法

我怎么可能切片分配的非托管缓冲区的特定部分并将其包装为普通的网络字符串以在托管空间/代码中使用

据我所知这是不可能的。 .Net 有自己的方式来定义对象(对象头等),您不能将某些任意内存区域视为 .net 对象。固定和改变字符串似乎很危险,因为字符串是不可变的,而且有些事情可能无法正常工作(例如,使用字符串作为字典键)。

正确的方法是(如 Canton7 提到的)使用 char[] 缓冲区和 Span<char> / Memory<char> 对字符串进行切片。传递给其他方法时,您可以将字符串的一部分转换为实际的字符串对象。调用 Console.WriteLine 等方法或 UI 方法时,分配字符串对象的开销与其他正在发生的事情相比无关紧要。

如果您有只接受 string 的旧代码,您要么需要接受这带来的限制,要么重写代码以接受内存/跨度表示。

我强烈建议您进行分析,看看这是否是频繁分配的实际问题。只要字符串适合小对象堆(SOH,即小于 87kb)并且没有提升到第 2 代,开销可能不会很大。 SOH 上的分配速度很快,并且运行第 0 代 GC 的时间不会与分配的数量直接成比例。所以每隔几毫秒更新一次可能并不可怕。如果你在谈论微秒,我会更担心。