c# - 从数据库检索值时内存使用率较高

标签 c# litedb

我有一个项目,必须存储 16 个对象,每个对象包含 185 000 个 double 的列表。保存的对象的总体大小应约为 20-30 mb (sizeof(double) * 16 * 185 000),但是当我尝试从数据库检索它时,数据库分配 200 mb 来检索它20-30 mb 对象。

我的问题是:

  1. 这是预期的行为吗?
  2. 当我只想这样做时,如何避免如此巨大的内存分配 检索一份文档?

这是完全可复制的示例和探查器的屏幕截图:

class Program
{
    private static string _path;

    static void Main(string[] args)
    {
        _path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "testDb");

        // Comment after first insert to avoid adding the same object.
        AddData();

        var data = GetData();

        Console.ReadLine();
    }

    public static void AddData()
    {
        var items = new List<Item>();
        for (var index = 0; index < 16; index++)
        {
            var item = new Item {Values = Enumerable.Range(0, 185_000).Select(v => (double) v).ToList()};
            items.Add(item);
        }
        var testData = new TestClass { Name = "Test1", Items = items.ToList() };

        using (var db = new LiteDatabase(_path))
        {
            var collection = db.GetCollection<TestClass>();
            collection.Insert(testData);
        }
    }

    public static TestClass GetData()
    {
        using (var db = new LiteDatabase(_path))
        {
            var collection = db.GetCollection<TestClass>();
            // This line causes huge memory allocation and wakes up garbage collector many many times.
            return collection.FindOne(Query.EQ(nameof(TestClass.Name), "Test1"));
        }
    }
}

public class TestClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IList<Item> Items { get; set; }
}

public class Item
{
    public IList<double> Values { get; set; }
}

185_000 更改为 1_850_000 使我的 RAM 使用量达到 >4GB(!)

分析器: Profiler image

最佳答案

LiteDB 中分配比直接分配更多内存的原因有几个 List<Double> .

要理解这一点,您需要知道您的类型化类已转换为 BsonDocument结构(带有 BsonValues )。此结构有一定的开销(每个 BsonValue +1 或 +5 字节)。

此外,要序列化此类(在插入时),LiteDB 必须创建一个 byte[]有了这一切BsonDocument (BSON 格式)。之后,这个超大byte[]被复制到许多扩展页面(每个页面包含 byte[4070] )。

不仅如此,LiteDB还必须跟踪原始数据以存储在日志区域中。因此,这个大小可以加倍。

为了反序列化,LiteDB 必须执行相反的过程:将所有页面从磁盘读取到内存,将所有页面连接到单个 byte[] ,反序列化为BsonDocument完成您类(class)的 map 。

这个操作对于小物体来说是可以的。每个新文档读/写都会重复使用此内存,以便内存保持控制。

在下一个 v5 版本中,此过程进行了一些优化,例如:

  • 反序列化不需要将所有数据分配到单个 byte[]阅读文档。这可以使用新的 ChunkStream(IEnumerable<byte[]>) 来完成。连载还需要这个单byte[]
  • 日志文件已更改为 WAL(预写日志)- 不需要保留原始数据。
  • ExtendPage不再存储在缓存中

对于 future 的版本,我考虑使用 new Span<T>类重新使用以前的内存分配。但我需要对此进行更多研究。


但是,存储包含 185,000 个值的单个文档是任何 nosql 数据库中的最佳解决方案。 MongoDB 将 BSON 文档大小限制为 16Mb(早期版本限制为 ~368kb)...我在 v2 中将 LiteDB 限制为 1Mb...但我删除了此检查大小,只是添加为建议以避免大型单个文档。

尝试将您的类分成 2 个集合:一个用于您的数据,另一个用于每个值。您还可以将此大型数组拆分为多个 block ,例如 LiteDB FileStorage 或 MongoDB GridFS。

关于c# - 从数据库检索值时内存使用率较高,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51948892/

相关文章:

C# 将泛型参数转换为接口(interface)

c# - 从 .NET core 2.2 迁移到 3.1 api 结果中间件在将 newtonsoft 添加到项目时中断

c# - 从 LiteDB 获取数据

c# - LiteDB:字段 'Null' 上的 BSON 数据类型 '_id' 无效

c# - LiteDB 对不同的条目使用相同的 ID

c# - 您是只使用一次 EnsureIndex() 还是为您插入到数据库中的每个文档使用?

c# - Xamarin.Forms - 标签 FontSize OnPlatform - XAML 错误

c# - 无法确定元表

c# - 我应该在哪里存储我的 ASP.NET Core 应用程序生产环境的连接字符串?

c# - 如何正确实现为异步使用而设计的接口(interface)?