广泛使用LOH会导致严重的性能问题

 Durston 发布于 2022-12-07 10:37

我们在Server 2012上使用了WebApi 2,.NET 4.5的Web服务.我们看到偶尔的延迟增加10-30ms,没有充分的理由.我们能够找到有问题的代码片段到LOH和GC.

有一些文本我们将其转换为UTF8字节表示(实际上,我们使用的序列化库就是这样).只要文本短于85000字节,延迟就会稳定且短暂:平均为~0.2 ms,为99%.一旦超过85000边界,平均延迟增加到约1ms,而99%跳跃到16-20ms.Profiler显示大部分时间都花在了GC上.可以肯定的是,如果我在迭代之间放置GC.Collect,测得的延迟会回到0.2ms.

我有两个问题:

    延迟来自哪里?据我所知,LOH没有被压缩.SOH正在被压缩,但没有显示延迟.

    有没有一种实用的方法来解决这个问题?请注意,我无法控制数据的大小并使其变小.

-

public void PerfTestMeasureGetBytes()
{
    var text = File.ReadAllText(@"C:\Temp\ContactsModelsInferences.txt");
    var smallText = text.Substring(0, 85000 + 100);
    int count = 1000;
    List latencies = new List(count);
    for (int i = 0; i < count; i++)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        var bytes = Encoding.UTF8.GetBytes(smallText);
        sw.Stop();
        latencies.Add(sw.Elapsed.TotalMilliseconds);

        //GC.Collect(2, GCCollectionMode.Default, true);
    }

    latencies.Sort();
    Console.WriteLine("Average: {0}", latencies.Average());
    Console.WriteLine("99%: {0}", latencies[(int)(latencies.Count * 0.99)]);
}

Adam Houldsw.. 6

性能问题通常来自两个方面:分配和碎片.

分配

运行时保证清洁内存,因此花费周期清理它.当你分配一个大对象时,这就是大量的内存,并开始为单个分配添加毫秒(当说实话,.NET中的简单分配实际上非常快,所以我们通常从不关心这一点).

分配LOH对象然后回收时发生碎片.直到最近,GC无法重新组织内存以移除这些旧对象"间隙",因此如果它的大小相同或更小,则只能适应该间隙中的下一个对象.最近,GC已经具有压缩LOH的能力,这消除了这个问题,但是在压实过程中花费了时间.

我的猜测是你遇到了两个问题并触发了GC运行,但这取决于你的代码尝试在LOH中分配项目的频率.如果要进行大量分配,请尝试对象池路由.如果您无法有效地控制池(块状对象生命周期或不同的使用模式),请尝试对您正在使用的数据进行分块以完全避免它.


你的选择

我遇到过LOH的两种方法:

躲开它.

使用它,但意识到你正在使用它并明确地管理它.

躲开它

这包括将你的大型物体(通常是某种阵列)分块成每个落在LOH屏障下的块.我们在序列化大对象流时执行此操作.运行良好,但实现将特定于您的环境,所以我犹豫是否提供编码示例.

用它

解决分配和碎片问题的一种简单方法是长期存在的对象.显式地创建一个大的空数组(或数组)来容纳你的大对象,并且不要去除它(或它们).保留它并像对象池一样重复使用它.您支付此分配,但可以在首次使用或应用程序空闲时间执行此操作,但您为重新分配支付的费用较少(因为您没有重新分配)并减少碎片问题,因为您不是经常要求分配东西,你不回收物品(这首先导致差距).

也就是说,中途的房子可能是有序的.为对象池预先保留一段内存.提前完成,这些分配应该在内存中连续,这样您就不会有任何间隙,并留下可用内存的尾端用于不受控制的项目.请注意,这显然会对应用程序的工作集产生影响 - 对象池占用空间而不管它是否被使用.


资源

LOH在网络上有很多内容,但要注意资源的日期.在最新的.NET版本中,LOH已经获得了一些爱,并且有所改进.也就是说,如果您使用的是旧版本,我认为网络上的资源相当准确,因为LOH从一开始就没有真正收到任何严重的更新和.NET 4.5(ish).

例如,2008年有这篇文章http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

以及.NET 4.5中的改进摘要:http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx

1 个回答
  • 性能问题通常来自两个方面:分配和碎片.

    分配

    运行时保证清洁内存,因此花费周期清理它.当你分配一个大对象时,这就是大量的内存,并开始为单个分配添加毫秒(当说实话,.NET中的简单分配实际上非常快,所以我们通常从不关心这一点).

    分配LOH对象然后回收时发生碎片.直到最近,GC无法重新组织内存以移除这些旧对象"间隙",因此如果它的大小相同或更小,则只能适应该间隙中的下一个对象.最近,GC已经具有压缩LOH的能力,这消除了这个问题,但是在压实过程中花费了时间.

    我的猜测是你遇到了两个问题并触发了GC运行,但这取决于你的代码尝试在LOH中分配项目的频率.如果要进行大量分配,请尝试对象池路由.如果您无法有效地控制池(块状对象生命周期或不同的使用模式),请尝试对您正在使用的数据进行分块以完全避免它.


    你的选择

    我遇到过LOH的两种方法:

    躲开它.

    使用它,但意识到你正在使用它并明确地管理它.

    躲开它

    这包括将你的大型物体(通常是某种阵列)分块成每个落在LOH屏障下的块.我们在序列化大对象流时执行此操作.运行良好,但实现将特定于您的环境,所以我犹豫是否提供编码示例.

    用它

    解决分配和碎片问题的一种简单方法是长期存在的对象.显式地创建一个大的空数组(或数组)来容纳你的大对象,并且不要去除它(或它们).保留它并像对象池一样重复使用它.您支付此分配,但可以在首次使用或应用程序空闲时间执行此操作,但您为重新分配支付的费用较少(因为您没有重新分配)并减少碎片问题,因为您不是经常要求分配东西,你不回收物品(这首先导致差距).

    也就是说,中途的房子可能是有序的.为对象池预先保留一段内存.提前完成,这些分配应该在内存中连续,这样您就不会有任何间隙,并留下可用内存的尾端用于不受控制的项目.请注意,这显然会对应用程序的工作集产生影响 - 对象池占用空间而不管它是否被使用.


    资源

    LOH在网络上有很多内容,但要注意资源的日期.在最新的.NET版本中,LOH已经获得了一些爱,并且有所改进.也就是说,如果您使用的是旧版本,我认为网络上的资源相当准确,因为LOH从一开始就没有真正收到任何严重的更新和.NET 4.5(ish).

    例如,2008年有这篇文章http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

    以及.NET 4.5中的改进摘要:http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx

    2022-12-11 02:05 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有