Vla*_*lad 7 c# memory multithreading struct garbage-collection
我有一个处理请求的单例对象.每个请求大约需要一毫秒才能完成,通常更少.此对象不是线程安全的,它期望以特定格式的请求,封装在Request类中,并将结果返回为Response.该处理器具有另一个通过套接字发送/接收的生产者/消费者.
我实施了生产者/消费者方法来快速工作:
RequestCommand包含a TaskCompletionSource<Response>和目标的命令对象Request.Queue<>)并等待command.Completion.Task.Thread)从"请求队列"中拉出命令,处理command.Request,生成Response并发出命令信号,如使用command.Completion.SetResult(response).但是在进行小内存基准测试时,我看到很多这些对象被创建并且在内存中排列最常见的对象列表.请注意,没有内存泄漏,GC可以在每次触发时很好地清理所有内容,但显然很快就创建了很多对象,这使得Gen 0非常大.我想知道更好的内存使用是否会产生更好的性能.
我正在考虑将这些对象中的一些转换为结构以避免分配,特别是现在有一些新功能可以使用C#7.1.但我没有看到这样做的方法.
ValueTask<> 这似乎不适合这里,因为事情是异步的.TaskCompletionSource<>有一个州,但它是object,所以它将被装箱.TaskCompletionSource<>和a string).有什么方法可以利用结构来减少内存使用量和/或提高性能?还有其他选择吗?
Eri*_*ert 26
值类型可以在堆栈中实例化,但是如果它们从一个线程传递到线程,它们必须被复制到stackA-> heap和heap-> stackB我猜.
不,这根本不是真的.但是你的思想中存在更深层次的问题:
立即停止将结构视为生活在堆栈中.当你创建一个具有一百万个整数的int数组时,你认为这400万字节的整数存在于你的一百万字节堆栈中吗?当然不是.
事实上,堆栈与堆没有任何关系的值类型.而不是"堆栈和堆",开始说"短期分配池"和"长期分配池". 无论该变量是包含int还是对象的引用,都会从短期分配池中分配具有较短生存期的变量.一旦你开始正确地考虑变量生命周期,那么你的推理就变得非常简单了.短期内,短命的东西显然存在于短期内.
所以:当你将一个结构从一个线程传递到另一个线程时,它是否曾"存在于堆中"? 问题是荒谬的,因为价值观不是生活在堆上的东西. 变量是存储的东西; 变量存储值.
那么:是否将类转换为结构将提高性能,因为"那些结构可以存在于堆栈中"?不,当然不.引用类型和值类型之间的相关差异不是它们存在的位置,而是它们的复制方式.值类型按值复制,引用类型通过引用复制,引用副本是最快的副本.
我看到很多这些对象被创建并且顶部是内存中最常见的对象列表.请注意,没有内存泄漏,GC可以在每次触发时很好地清理所有内容,但显然很快就创建了很多对象,这使得Gen 0非常大.我想知道更好的内存使用是否会产生更好的性能.
好的,现在我们来看你问题的合理部分.这是一个很好的观察,它是一个可以用科学测试的.您应该做的第一件事是使用分析器来确定第0代集合对应用程序性能的实际负担.
可能这个负担不是你程序中最慢的东西,事实上它是无关紧要的.在这种情况下,您现在将知道将精力集中在真正的问题上,而不是追逐不是真正问题的内存分配问题.
假设您发现第0代收藏真的在扼杀您的表现; 你能做什么?是否有更多结构的答案?这可行,但你必须非常小心:
当我们在罗斯林面对这个问题时,我们非常仔细地考虑了这个问题并做了很多实验.我们采用的策略通常不是将事物移到堆栈上.相反,我们使用分析器确定每种类型的内存中有多少小的,短暂的活动在内存中活动 - 然后在这些对象上实现池化策略.你需要一个小物件,你把它带出池子.当你完成后,你把它放回池中.结果是,你最终得到了池中的O(任何时候都有活动的对象数),它很快被移动到第2代堆中; 然后,你会大大降低第0代堆上的收集压力,同时增加相对罕见的第2代收藏品的成本.
我不是说这是你的最佳选择.我说我们在罗斯林遇到了同样的问题,我们用科学解决了这个问题.你也可以做到的.