【发布时间】:2021-07-03 01:30:41
【问题描述】:
注意:我的案例是在一个旧 API 的生态系统中,它只适用于字符串,没有现代 .NET 添加。
所以我非常需要没有分配的可变字符串。字符串每 X 毫秒更新一次,因此您可以在几分钟内计算出它可以产生多少垃圾(StringBuilder 甚至根本不接近相关)。我目前的方法是预先分配 fixed 大小的字符串并通过固定、直接写入字符以及在容量达到时静默掉落或抛出来对其进行变异。
这很好用。分配的字符串是长期存在的,因此最终 GC 会将其提升到 Gen2 并且固定不会对其造成太大影响,从而最大限度地减少开销。但是有 2 个主要的问题:
- 因为字符串是固定的,我必须用
\0填充它,虽然到目前为止这对所有默认的 NET/MONO 功能和第 3 方的东西都很好,但无法知道当字符串在 len 中为 1024 时其他东西会如何反应, 但最后 100 个是\0 - 我无法调整它的大小,因为这会导致分配。我可以一次分配一次蓝月亮,但由于字符串是相当动态的,我无法确定它何时会尝试进一步扩展或缩小。我可以使用 "expand only" 方法,这样我只在需要扩展时分配,但是,这有填充开销的缺点(如果字符串扩展为 5k 个字符,但下一个字符串只是 3k - 2k字符将被填充以获得额外的周期)以及额外的内存使用。我不确定 GC 对 mchuge 的感觉如何,通常在 Gen2 中而不是在 LOH 中固定字符串。 另一种方法是池化可重复使用的字符串对象,但是,这具有更高的内存和 GC 开销 + 查找开销。
由于目标字符串必须存在相当长的时间,我正在考虑通过字节缓冲区将其移动到Unmanaged memory。这将消除 GC 的负担(固定惩罚开销),并且我可以以比托管堆更低的成本重新调整大小/重新分配。
我很难理解的是 - 我怎样才能切片分配的非托管缓冲区的特定部分并将其包装为正常的网络字符串以在托管空间/代码中使用?比如,将它传递给Console.WriteLine 或一些在屏幕上绘制 UI 标签并接受字符串的第三方库。这甚至可行吗?
附:据我所知,NET5 的计划(我认为将在 NET6 中最终确定)您将不再能够改变字符串之类的东西(在运行时被阻止或未定义的故障)。他们的解决方案似乎是 POH,这基本上就是我所描述的,具有相同的限制。
【问题讨论】:
-
"GC 会将其提升到 Gen2 并且固定不会对它造成太大影响,从而最大限度地减少开销。" - 这不一定是真的。如果字符串在 gen0 中时被固定,那么 GC 可能会决定完全避免提升它!如果它被提升,它仍然会妨碍压缩。如果它在 在 gen2 中被固定,那么您可能是对的。
-
我真的鼓励你不要走改变字符串的路线——有很多事情假设字符串不会改变。你不能摆脱一个字符数组吗? .NET 字符串以长度开头,因此您不能将 char 数组的任意位分割成字符串,因为它上面没有适当的标头。
-
如果可以的话,我会在内存中保留一个
char[],它是变异的。当您需要将其转换为字符串以传递给Console.WriteLine等时(我假设您不是每 X 毫秒都这样做!),使用string.Create将您的char[]的相关部分复制到一个新的字符串。 -
不,我有一个只接受字符串的旧 API/生态系统。如果我有能力使用 char 数组或 Span 这个问题就不存在了。您提到的所有这些建议我已经多次提及,它们在这个特定情况下根本不相关。
-
对,但由于您没有提及它们,人们会建议它们。我不是读心术!
标签: c# .net string pointers unmanaged-memory