c# - 托管环境中的临时内存

标签 c# memory-management managed

我正在使用 C# 进行流体模拟。每个循环我都需要计算空间中离散点处的流体速度。作为该计算的一部分,我需要几十 KB 的暂存空间来容纳一些 double[] 数组(数组的确切大小取决于一些输入数据)。数组仅在使用它们的方法的持续时间内需要,并且有一些不同的方法需要像这样的暂存空间。

在我看来,构建临时数组有几种不同的解决方案:

  1. 每次调用方法时,使用'new'从堆中获取内存。这就是我最初所做的,但是它给垃圾收集器带来了很大的压力,每秒一到两次几毫秒的峰值真的很烦人。

  2. 在调用方法时将临时数组作为参数传递。问题是这会迫使用户管理它们,包括适本地调整它们的大小,这是一个巨大的痛苦。由于它改变了 API,因此或多或少地使用暂存内存变得困难。

  3. 在不安全的上下文中使用 stackalloc 从程序堆栈分配暂存内存。这会工作得很好,除了我需要使用/unsafe 进行编译并不断地在我的代码中散布不安全的 block ,我想避免这种情况。

  4. 在程序启动时预分配一次私有(private)数组。这很好,除非我不一定知道我需要的数组的大小,直到我可以查看一些输入数据。而且它变得非常困惑,因为您不能将这些私有(private)变量的范围限制为仅单个方法,因此它们不断污染命名空间。随着需要暂存内存的方法数量的增加,它的扩展性很差,因为我分配了很多只在一小部分时间使用的内存。

  5. 创建某种中央池,并从池中分配暂存内存阵列。这样做的主要问题是我没有看到从中央池分配动态大小数组的简单方法。我可以使用起始偏移量和长度,并让所有暂存内存实质上共享一个大数组,但我有很多现有代码都采用 double[]。而且我必须小心确保这样的池线程安全。

...

有没有人遇到过类似的问题?从经验中可以提供任何建议/教训吗?

最佳答案

我很同情你的处境;当我在 Roslyn 工作时,我们非常仔细地考虑了分配临时工作数组的收集压力所导致的潜在性能问题。我们确定的解决方案是一种合并策略。

在编译器中,数组的大小往往很小,因此会经常重复。在你的情况下,如果你有大阵列,那么我会做的是按照汤姆的建议:简化管理问题并浪费一些空间。当您向池请求一个大小为 x 的数组时,将 x 取整为最接近的 2 的幂并分配一个该大小的数组,或者从池中取出一个。调用者得到一个有点太大的数组,但可以编写它们来处理这个问题。在池中搜索适当大小的数组应该不会太难。或者您可以维护一堆池,一个池用于大小为 1024 的数组,一个用于 2048,等等。

编写一个线程安全池并不太难,或者您可以将线程池​​设为静态,每个线程一个池。

棘手的一点是在池中取回内存。有几种方法可以解决这个问题。首先,您可以简单地要求池内存的用户在使用完数组后调用“回到池中”方法,如果他们不想承担收集压力的话。

另一种方法是围绕数组编写外观包装器,使其实现 IDisposable,以便您可以使用“使用”(*),并在将对象放回池中的对象上创建终结器,使它复活。 (确保让终结器重新打开“我需要被终结”位。) 复活的终结器让我感到紧张;我个人更喜欢前一种方法,这是我们在罗斯林所做的。


(*) 是的,这违反了“正在使用”应该表示非托管资源正在返回给操作系统的原则。本质上,我们通过自己的管理将托管内存视为非托管资源,所以它还不错。

关于c# - 托管环境中的临时内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15726214/

相关文章:

c# - WebRTC 媒体服务器

go - 如何让 Go 程序使用更多内存?是推荐的吗?

windows-phone-8 - C# 和 C++/CX 对象有什么关系?

.net - 如何将 double* 转换为数组<double>(6)

javascript - C# Javascript 日期/时间不匹配

c# - 没有结果时的 LINQ to SQL 连接

objective-c - 释放声明为 Objective-C 属性的 C 类型

c++ - 如何让 QObject::deleteLater() 将对象归零?

c# - 在 .Net 应用程序中调用 C++ Dll 时出现 System.Access 冲突异常

c# - 如何在 GridView 中显示基于排序类型的图像