首页 > 解决方案 > 非托管内存中的可变字符串可用于托管空间

问题描述

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

所以我非常需要没有分配的可变字符串。字符串每 X 毫秒更新一次,因此您可以在几分钟内计算出它可以产生多少垃圾(StringBuilder 甚至根本不接近相关)。我目前的方法是预先分配固定大小的字符串,并通过固定、直接写入字符以及在容量达到时静默掉落或抛出来对其进行变异。

这工作正常。分配的字符串是长期存在的,因此最终 GC 会将其提升到 Gen2 并且固定不会对其造成太大影响,从而最大限度地减少开销。但是有两个主要问题

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

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

PS 据我所知,NET5 的计划(我认为将在 NET6 中最终确定)您将不再能够改变字符串之类的东西(在运行时被阻止或未定义的故障)。他们的解决方案似乎是我所描述的 POH,具有相同的限制。

标签: c#.netstringpointersunmanaged-memory

解决方案


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

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

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

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

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


推荐阅读