在 C# 中处理大内存列表时如何避免垃圾排序问题

How to avoid garbage collation problems when dealing with large in memory lists in c#

我们的文章-标签关系 SQL 性能存在一些问题,因此我们决定将 article/tags 保留在内存中,这给了我们显着的提升,但它现在导致我们当整个列表被删除并替换为新列表(3m + 记录)时,垃圾收集令人头疼。

这是一段代码:

private readonly IContextCreator _contextCreator;
private volatile  static List<TagEngineCacheResponse> _cachedList = new List<TagEngineCacheResponse>();
private readonly int KEYWORD_GROUP_NAME = 1;
private static BitmapIndex.BitmapIndex _bitMapIndex = new BitmapIndex.BitmapIndex();

public TagEngineService(IContextCreator contextCreator)
{
    _contextCreator = contextCreator;
}

public async Task RepopulateEntireCacheAsync()
{

    using (var ctx = _contextCreator.PortalContext())
    {

        var cmd = ctx.Database.Connection.CreateCommand();
        cmd.CommandText = BASE_SQL_QUERY;

        await ctx.Database.Connection.OpenAsync();
        var reader = await cmd.ExecuteReaderAsync();

        var articles = ((IObjectContextAdapter)ctx)
            .ObjectContext
            .Translate<TagEngineCacheResponse>(reader).ToList();

        //recreate bitmap indexes
        BitmapIndex.BitmapIndex tempBitmapIndex = new BitmapIndex.BitmapIndex();
        int recordRow = 0;
        foreach (var record in articles)
        {
            tempBitmapIndex.Set(new BIKey(KEYWORD_GROUP_NAME, record.KeywordId), recordRow);
            recordRow++;
        }

        
        _cachedList = articles;
        _bitMapIndex = tempBitmapIndex;

       
    }

}

Class定义:

public class TagEngineCacheResponse
{
    public int ArticleId { get; set; }
    public int KeywordId { get; set; }
    public DateTime PublishDate { get; set; }
    public int  ViewCountSum { get; set; }
}

如您所见,当缓存被重新创建时,_cachedList 被替换为一个新的列表,而旧的则准备好被垃圾收集。此时,cpu GC 时间跳到 60-90%,持续 2-3 秒。

有什么想法可以改进这段代码以避免 GC 问题吗?

我猜这个列表每个对象大约需要 44 个字节,或者 3m 个对象大约需要 130Mb。这有点偏大,但并非难以置信。

一些建议:

该列表远远超过小对象堆 (SOH) 的 87k 限制,因此它将分配在大对象堆 (LOH) 上。这仅在第 2 代收集,第 2 代收集可能很昂贵。为避免这种情况,建议尽可能避免取消分配 gen2 对象,即分配一次,然后尽可能重用它们。

您可以以较小的块从数据库中获取列表并就地更新列表。确保每个块都在 SOH 的限制内。您可以考虑锁定列表以确保在更新时不访问它,或者在更新一个列表的地方保留两个交替列表,然后切换 'active' 列表。

您正在为 TagEngineCacheResponse 使用 class,这将导致分配大量对象。虽然它们足够小以适合 SOH,但如果您不走运,它们可能会存活足够长的时间以放入第 2 代堆。虽然 GC 时间不会受到未引用对象的很大影响,但使用值类型并避免该问题可能仍然更好。配置文件以确保它确实有帮助。