在代码段执行期间暂停GC

19

有没有一种方法可以完全暂停GC来执行代码的某个部分?其他类似问题中我找到的唯一解决方案是GC.TryStartNoGCRegion,但它仅限于指定数量的内存,而这本身又受限于短暂段的大小。

是否有一种完全绕过此限制的方法,告诉.NET“分配所需的任何内容,不要进行GC”,或者增加段大小?从我找到的资料来看,在多核服务器上最多只能使用1GB,这远远不足以分配我需要的内存,但我不想发生GC(我有多达TB级的可用RAM,并且在该部分期间会出现成千上万次GC峰值,我愿意用10倍甚至100倍的RAM使用量来换取这些)。

编辑:

现在有赏金了,我认为指定用例会更容易。我正在使用LINQ to XML将非常大的XML文件(现在为1GB,很快就会变为12GB)加载和解析为内存中的对象。我不想寻找替代方案。我从数百万个XElements创建数百万个小对象,并且GC一直在尝试收集,而我希望保持所有RAM的使用率很高。我有数百GB的RAM,一旦它使用了4GB,GC就开始不停地收集,这非常友好但性能不佳。我不关心内存,但我很在乎性能。我想要相反的权衡。

虽然我无法在此处发布实际代码,但以下示例代码与最终代码非常接近,可以帮助那些请求更多信息的人:

var items = XElement.Load("myfile.xml")
.Element("a")
.Elements("b") // There are about 2 to 5 million instances of "b"
.Select(pt => new
{
    aa = pt.Element("aa"),
    ab = pt.Element("ab"),
    ac = pt.Element("ac"),
    ad = pt.Element("ad"),
    ae = pt.Element("ae")
})
.Select(pt => new 
{
    aa = new
    {
        aaa = double.Parse(pt.aa.Attribute("aaa").Value),
        aab = double.Parse(pt.aa.Attribute("aab").Value),
        aac = double.Parse(pt.aa.Attribute("aac").Value),
        aad = double.Parse(pt.aa.Attribute("aad").Value),
        aae = double.Parse(pt.aa.Attribute("aae").Value)
    },
    ab = new
    {
        aba = double.Parse(pt.aa.Attribute("aba").Value),
        abb = double.Parse(pt.aa.Attribute("abb").Value),
        abc = double.Parse(pt.aa.Attribute("abc").Value),
        abd = double.Parse(pt.aa.Attribute("abd").Value),
        abe = double.Parse(pt.aa.Attribute("abe").Value)
    },
    ac = new
    {
        aca = double.Parse(pt.aa.Attribute("aca").Value),
        acb = double.Parse(pt.aa.Attribute("acb").Value),
        acc = double.Parse(pt.aa.Attribute("acc").Value),
        acd = double.Parse(pt.aa.Attribute("acd").Value),
        ace = double.Parse(pt.aa.Attribute("ace").Value),
        acf = double.Parse(pt.aa.Attribute("acf").Value),
        acg = double.Parse(pt.aa.Attribute("acg").Value),
        ach = double.Parse(pt.aa.Attribute("ach").Value)
    },
    ad1 = int.Parse(pt.ad.Attribute("ad1").Value),
    ad2 = int.Parse(pt.ad.Attribute("ad2").Value),
    ae = new double[]
    {
        double.Parse(pt.ae.Attribute("ae1").Value),
        double.Parse(pt.ae.Attribute("ae2").Value),
        double.Parse(pt.ae.Attribute("ae3").Value),
        double.Parse(pt.ae.Attribute("ae4").Value),
        double.Parse(pt.ae.Attribute("ae5").Value),
        double.Parse(pt.ae.Attribute("ae6").Value),
        double.Parse(pt.ae.Attribute("ae7").Value),
        double.Parse(pt.ae.Attribute("ae8").Value),
        double.Parse(pt.ae.Attribute("ae9").Value),
        double.Parse(pt.ae.Attribute("ae10").Value),
        double.Parse(pt.ae.Attribute("ae11").Value),
        double.Parse(pt.ae.Attribute("ae12").Value),
        double.Parse(pt.ae.Attribute("ae13").Value),
        double.Parse(pt.ae.Attribute("ae14").Value),
        double.Parse(pt.ae.Attribute("ae15").Value),
        double.Parse(pt.ae.Attribute("ae16").Value),
        double.Parse(pt.ae.Attribute("ae17").Value),
        double.Parse(pt.ae.Attribute("ae18").Value),
        double.Parse(pt.ae.Attribute("ae19").Value)
    }
})
.ToArray();

2
也许这个链接可以帮到你:https://dev59.com/OW025IYBdhLWcg3wblg3 - Nik Bo
1
我也想要数百GB的RAM! - user3791372
4
那么也许你选错工具了?如果你想要对内存有明确的控制,就选择一种(非托管的)语言,在那里你可以拥有这种控制(但同时也有责任)。 - Damien_The_Unbeliever
1
你负责加载XML文件吗?如果是的话,考虑优化解析流程,而不是将整个XML解析:https://msdn.microsoft.com/zh-cn/library/bb387013(v=vs.100).aspx - Good Night Nerd Pride
1
@RonanThibaudau,这也为您提供了隐藏文件I/O操作和计算的机会,因为两者都可以异步进行。无论如何,我认为您的要求是正交的:您想要一个易于使用的工具,它可以隐藏内存管理,但您也想要(部分地)自己管理内存。 - Good Night Nerd Pride
显示剩余25条评论
3个回答

3
我认为在你的情况下最好的解决方案是我以前在一个项目中使用过的这段代码。
var currentLatencySettings = GCSettings.LatencyMode;   
GCSettings.LatencyMode = GCLatencyMode.LowLatency;

//your operations

GCSettings.LatencyMode = currentLatencySettings;

你已经尽可能地进行了压缩(根据我的知识),你仍然可以手动调用GC.Collect()
请参阅MSDN文章这里 此外,我强烈建议使用LINQ的Skip()Take()方法对解析后的集合进行分页。最后将输出数组连接起来。

正如评论中所提到的,我的问题不在于第二代集合,而是我正在创建许多小对象(因此是第0和1代),尽管我认为这不会产生任何影响,但我会尝试一下(服务器目前正在忙于另一个任务)。 - Ronan Thibaudau
由于没有更合适的答案,授予您赏金,谢谢! - Ronan Thibaudau
很高兴听到这个消息,抱歉我不能提供更多的帮助。 - Peuczynski

3

目前我能找到的最好的解决办法是切换到服务器GC(本身没有任何变化),这样可以使用更大的段大小,并让我使用更大的无gc部分数量:

        GC.TryStartNoGCRegion(10000000000); // On Workstation GC this crashed with a much lower number, on server GC this works

这与我的预期不符(这是10GB,但从我在在线文档中找到的内容来看,我的当前设置中段大小应为1到4GB,因此我预期会出现无效参数)。

通过这个设置,我得到了我想要的结果(GC处于保持状态,我分配了22GB而不是7GB,所有临时对象都没有被GC,但GC只运行一次(仅一次!)整个批处理过程,而不是每秒运行多次(在更改之前,Visual Studio中的GC视图看起来像GC触发的所有单个点的直线)。

这并不好,因为它无法扩展(添加0会导致崩溃),但它比我迄今为止找到的任何其他东西都要好。

除非有人找出如何增加段大小以便我可以进一步推进,或者有更好的替代方法完全停止GC(而不仅仅是某个代),否则我将在几天内接受自己的答案。


2
如果那些投反对票的人能够表明自己的意见,而不仅仅是投票就走,这会帮助我为未来的读者改进答案,而不是在考虑如何处理它! - Ronan Thibaudau
有些人的思维不够灵活,无法深入了解为什么要防止GC(垃圾回收)。他们认为你选择了错误的语言等等。在你的情况下停止GC是合理的,不必过多关注那些投反对票的人。 - Evk
如果能让你感到更好一些,当我以问答方式分享我的知识时,我曾经因没有评论而被下投票:http://stackoverflow.com/questions/37310418/how-to-use-activex-component-in-classlibrary-without-winforms 所以我知道这种沮丧的感觉。 - Peuczynski

1
我不确定在您的情况下是否可行,但您是否尝试并行处理XML文件。如果您可以将XML文件分解成较小的部分,您可以从代码内部生成多个进程。每个进程处理一个单独的文件。然后您可以组合所有结果。这肯定会提高性能,并且每个进程都有其单独的内存分配,这也应该增加在处理所有XML文件时特定时间的内存分配。

没有多进程也帮不上忙,它不是一个XML处理器,我只是加载整个文件,因为我需要全部内容作为内存中的对象图(整个XML文件仅用于此用例)。 - Ronan Thibaudau
4
XML是文本,而文本处理并不以其速度和内存效率著称(仅考虑标签中的冗余级别即可)。我敢打赌80%的内存都被'XElement'内部浪费了,还有成千上万个相同字符串的副本实例。大多数的处理时间都花费在解析上。如果这个XML文件只是为了这个目的而导出,也许你可以将其替换为自定义的二进制序列化格式 - 这应该会改变事情的局面。从经验上来说,当你开始有大型文件的时候,自定义序列化很划算。 - Lucas Trzesniewski
@RonanThibaudau,您能否提供一些关于如何在内存对象图中使用它的详细信息? - DotNetDev
3
我了解您的意思,但是GC会妨碍程序运行,因为您浪费了内存,所以减少浪费内存将减轻您尝试解决的问题。我对需要一个12GB的可读文件持怀疑态度,但如果真的需要,您可以设计一种更有效的自定义文本存储格式和优化的序列化器。当您扩展规模时,您确定性能会保持“完好无损”吗? - Lucas Trzesniewski
@tarunjindal 我添加了一个非常非常类似于我所做的示例代码,不确定是否有帮助。 - Ronan Thibaudau
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接