TPL 和内存管理
TPL and memory management
使用 Visual Studio Concurrency Visualizer 我现在明白为什么切换到 Parallel.For
没有任何好处:机器只有 9% 的时间忙于执行代码,其余时间是 71 % 同步和 17% 内存管理 (1).
检查下图中所有的橙色条纹,我发现 GC 总是涉及 (2)。
看完所有这些有趣的话题后...
Why do I have a lock here?
-
Prevent .NET Garbage collection for short period of time
-
..我是否正确地假设所有这些线程都需要使用单个内存管理对象并因此消除在堆上分配对象的需要我的场景会大大改善?比如用结构代替类,用数组代替动态列表等等?
我有很多工作要做,要让我的代码朝这个方向弯曲。只是想在开始之前确定一下。
Memory Management The Memory Management report shows the calls where memory management blocks occurred, along with the total blocking
times of each call stack. Use this information to identify areas that
have excessive paging or garbage collection issues.
还有更多
These segments in the timeline are associated with blocking times that
are categorized as Memory Management. This scenario implies that a
thread is blocked by an event that is associated with a memory
management operation such as Paging. During this time, a thread has
been blocked in an API or kernel state that the Concurrency Visualizer
is counting as memory management. These include events such as paging
and memory allocation. Examine the associated call stacks and profile
reports to better understand the underlying reasons for blocks that
are categorized as Memory Management.
是的,减少分配可能会对您的资源和效率带来很大好处,但几乎总是热路径 ] 和 申请失败
堆分配和特定的大型对象堆 (LOB) 分配成本高昂,它还会为您的垃圾收集器带来额外的工作,并可能使您的内存碎片化,从而导致效率更低下。您分配、重用内存或使用堆栈的次数越少,您就越好(通常)。
这也是您学习使用良好的内存分析器并了解垃圾收集器的地方。
关于这不是您用来减少应用程序分配的唯一工具。一个好的 内存分析器 将大有帮助,同时学习如何读取结果并根据结果影响变化。
创建最小分配代码是一种艺术形式,值得您学习
此外,正如 @mjwills 在评论中指出的那样,您也会 运行 通过基准测试软件进行任何更改,以 CPU 时间为代价删除分配不会感觉。有很多方法可以加速代码,低分配只是可能有帮助的众多方法之一。
最后,我建议您先关注 Marc Gravell 和他的博客(DeAllocation 先生),了解您的 垃圾收集器 以及各代人的工作方式,以及内存分析器等工具和高性能丝般光滑生产代码
的基准程序
从您的屏幕截图来看,内存分配似乎在等待 GC 完成时被阻止。有服务器GC和工作站GC两种模式,可能并发也可能不并发,但所有选项都至少需要阻塞线程一小会儿。我会更详细地检查您在 GC 上花费的频率和时间,以及 gen 0/1 和 2 的频率 运行.
我相信每个线程都有一个单独的临时段用于分配,因此它不需要同步分配,除非它需要一个新的段,或者分配是在大对象堆上。但是我找不到这方面的参考资料。
无论如何,您可能会从减少分配的数量和大小中受益。如果可能,使用对象池或内存池来重用内存。您还可以从增加内存量和检查应用程序的内存泄漏中获益。内存的一般建议是应该有两种类型的分配:
- 仅存在很短持续时间的小型临时分配,例如在方法调用期间存在的临时对象。
- 在“应用程序”期间存在的任何大小的长期分配。
如果遵循此模式,几乎所有垃圾都应在 Gen 0/1 中收集,而 gen 2 收集应该相当少见。
这也取决于您是分配许多小对象还是大块内存。如果是前者,您可以考虑使用结构,因为它们是堆栈分配的。如果后面你还需要考虑内存碎片,这也应该通过使用只分配固定大小的内存块的内存池来改善。
编辑:
最简单的对象池可能是这样的:
public class ObjectPool<T>
{
private ConcurrentBag<T> pool = new ConcurrentBag<T>();
public T Get(Func<T> constructor) => pool.TryTake(out var result) ? result : constructor();
public void Return(T obj) => pool.Add(obj);
}
这假定对象表示相同的资源,例如某些固定大小的字节数组。但是也有现成的实现:
- .Net core MemoryPool
- asp.Net core object pool
- stack overflow question regarding object pools
使用 Visual Studio Concurrency Visualizer 我现在明白为什么切换到 Parallel.For
没有任何好处:机器只有 9% 的时间忙于执行代码,其余时间是 71 % 同步和 17% 内存管理 (1).
检查下图中所有的橙色条纹,我发现 GC 总是涉及 (2)。
看完所有这些有趣的话题后...
Why do I have a lock here?
Prevent .NET Garbage collection for short period of time
..我是否正确地假设所有这些线程都需要使用单个内存管理对象并因此消除在堆上分配对象的需要我的场景会大大改善?比如用结构代替类,用数组代替动态列表等等?
我有很多工作要做,要让我的代码朝这个方向弯曲。只是想在开始之前确定一下。
Memory Management The Memory Management report shows the calls where memory management blocks occurred, along with the total blocking times of each call stack. Use this information to identify areas that have excessive paging or garbage collection issues.
还有更多
These segments in the timeline are associated with blocking times that are categorized as Memory Management. This scenario implies that a thread is blocked by an event that is associated with a memory management operation such as Paging. During this time, a thread has been blocked in an API or kernel state that the Concurrency Visualizer is counting as memory management. These include events such as paging and memory allocation. Examine the associated call stacks and profile reports to better understand the underlying reasons for blocks that are categorized as Memory Management.
是的,减少分配可能会对您的资源和效率带来很大好处,但几乎总是热路径 ] 和 申请失败
堆分配和特定的大型对象堆 (LOB) 分配成本高昂,它还会为您的垃圾收集器带来额外的工作,并可能使您的内存碎片化,从而导致效率更低下。您分配、重用内存或使用堆栈的次数越少,您就越好(通常)。
这也是您学习使用良好的内存分析器并了解垃圾收集器的地方。
关于这不是您用来减少应用程序分配的唯一工具。一个好的 内存分析器 将大有帮助,同时学习如何读取结果并根据结果影响变化。
创建最小分配代码是一种艺术形式,值得您学习
此外,正如 @mjwills 在评论中指出的那样,您也会 运行 通过基准测试软件进行任何更改,以 CPU 时间为代价删除分配不会感觉。有很多方法可以加速代码,低分配只是可能有帮助的众多方法之一。
最后,我建议您先关注 Marc Gravell 和他的博客(DeAllocation 先生),了解您的 垃圾收集器 以及各代人的工作方式,以及内存分析器等工具和高性能丝般光滑生产代码
的基准程序从您的屏幕截图来看,内存分配似乎在等待 GC 完成时被阻止。有服务器GC和工作站GC两种模式,可能并发也可能不并发,但所有选项都至少需要阻塞线程一小会儿。我会更详细地检查您在 GC 上花费的频率和时间,以及 gen 0/1 和 2 的频率 运行.
我相信每个线程都有一个单独的临时段用于分配,因此它不需要同步分配,除非它需要一个新的段,或者分配是在大对象堆上。但是我找不到这方面的参考资料。
无论如何,您可能会从减少分配的数量和大小中受益。如果可能,使用对象池或内存池来重用内存。您还可以从增加内存量和检查应用程序的内存泄漏中获益。内存的一般建议是应该有两种类型的分配:
- 仅存在很短持续时间的小型临时分配,例如在方法调用期间存在的临时对象。
- 在“应用程序”期间存在的任何大小的长期分配。
如果遵循此模式,几乎所有垃圾都应在 Gen 0/1 中收集,而 gen 2 收集应该相当少见。
这也取决于您是分配许多小对象还是大块内存。如果是前者,您可以考虑使用结构,因为它们是堆栈分配的。如果后面你还需要考虑内存碎片,这也应该通过使用只分配固定大小的内存块的内存池来改善。
编辑:
最简单的对象池可能是这样的:
public class ObjectPool<T>
{
private ConcurrentBag<T> pool = new ConcurrentBag<T>();
public T Get(Func<T> constructor) => pool.TryTake(out var result) ? result : constructor();
public void Return(T obj) => pool.Add(obj);
}
这假定对象表示相同的资源,例如某些固定大小的字节数组。但是也有现成的实现:
- .Net core MemoryPool
- asp.Net core object pool
- stack overflow question regarding object pools