代码之家  ›  专栏  ›  技术社区  ›  FCin

从数据库检索值时内存使用率高

  •  1
  • FCin  · 技术社区  · 6 年前

    我有一个项目,我必须在其中存储16个对象,每个对象包含一个185000 double s的列表。保存的对象的总大小应该在20-30 MB左右( size of(double)*16*185000 ),但是当我尝试从数据库中检索它时,数据库会分配200 MB来检索这个20-30 MB对象。

    我的问题是:

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

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

    类程序 { 私有静态字符串路径; 静态void main(string[]args) { _ path=path.combine(appdomain.currentdomain.basedirectory,“testdb”); //在第一次插入后添加注释,以避免添加同一对象。 AdDATA(); var data=getdata(); console.readline(); } 公共静态void adddata() { var items=新列表<item>(); 对于(var index=0;index<16;index++) { var item=new item values=Enumerable.range(0,185_000)。选择(v=>(double)v).tolist() 增加(项); } var testdata=new testclass name=“test1”,items=items.tolist() 使用(var db=new litedatabase(_path)) { var collection=db.getcollection<testclass>(); collection.insert(测试数据); } } 公共静态testclass getdata()。 { 使用(var db=new litedatabase(_path)) { var collection=db.getcollection<testclass>(); //这一行会导致巨大的内存分配,并多次唤醒垃圾收集器。 返回collection.findone(query.eq(name of(testclass.name),“test1”)); } } } 公共类TestClass { public int id_get;set; 公共字符串名称get;set; public ilist<item>项获取;设置; } 公共类项目 { public ilist<double>值get;set; } < /代码>

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

    < Pr>分析器: . sizeof(double) * 16 * 185 000
    ,但是当我尝试从数据库中检索它时,数据库会分配200MB来检索这个20-30MB对象。

    我的问题是:

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

    下面是完全可复制的profiler示例和屏幕截图:

    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!!)

    剖面仪:

    2 回复  |  直到 6 年前
        1
  •  1
  •   mbdavid    6 年前

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

    要理解这一点,您需要知道您键入的类被转换为 BsonDocument 结构(用) BsonValues )这个结构有一个开销(+1或+5字节每 BsonValue )

    此外,要序列化此类(插入时),LiteDB必须创建一个 byte[] 所有这些 BSON文档 (采用BSON格式)。之后,这个超大的 字节[] 复制到多个扩展页(每个页包含一个 byte[4070] )

    不仅如此,LiteDB还必须跟踪要存储在日志区域中的原始数据。所以这个尺寸可以翻一番。

    要反序列化,LiteDB必须执行相反的过程:将所有页面从磁盘读取到内存,将所有页面连接到一个单独的页面 字节[] ,反序列化为 BSON文档 完成你班的地图。

    对于小对象,此操作正常。这个内存对每个新文档的读/写都是重复使用的,因此内存保持控制。

    在下一个v5版本中,这个过程有一些优化,比如:

    • 反序列化不需要将所有数据分配到单个 字节[] 阅读文件。这可以用新的 ChunkStream(IEnumerable<byte[]>) . 序列化仍需要此单个 字节[]
    • 日志文件已更改为wal(提前写入日志)-不需要保留原始数据。
    • ExtendPage 不再存储在缓存中

    对于未来的版本,我想使用新的 Span<T> 类重新使用以前的内存分配。但我需要更多的研究。


    但是,在任何NoSQL数据库中,存储一个值为185000的单个文档都是最好的解决方案。MongoDB限制bson文档大小为16MB(早期版本限制为~368KB)…我将LiteDB限制为1MB In v2…但我删除了这个支票大小,只是添加了一个建议,以避免大的单个文档。

    尝试将类分成两个集合:一个用于数据,另一个用于每个值。您还可以将这个大数组拆分为块,如litedb filestore或mongodb gridfs。

        2
  •  0
  •   nvoigt    6 年前

    首先,按照创建列表的方式,它将为262.144个元素预留空间,因为 growth algorithm .

    您应该事先设置项目数量以避免出现这种情况(或者可能只是一起使用数组):

    Values = new List<double>(max);
    Values.AddRange(Enumerable.Range(0, max).Select(v => (double)v));
    

    就LiteDB而言,如果您不需要数据库(以及它带来的潜在开销),只需将其存储在您自己的数据结构中。如果您不实际使用数据库并且只存储一个项目,我看不到数据库的任何好处。