C# 从数据库检索值时内存使用率高
我有一个项目,其中我必须存储16个对象,每个对象包含185000个C# 从数据库检索值时内存使用率高,c#,litedb,C#,Litedb,我有一个项目,其中我必须存储16个对象,每个对象包含185000个double。保存的对象的总大小应该在20-30 mb左右(sizeof(double)*16*185000),但当我尝试从数据库检索它时,数据库会分配200 mb来检索这个20-30 mb的对象 我的问题是: 这是预期的行为吗 当我只想这样做时,我如何避免如此巨大的内存分配 检索一个文档 以下是profiler的完全可复制示例和屏幕截图: class Program { private static string _pa
double
。保存的对象的总大小应该在20-30 mb左右(sizeof(double)*16*185000
),但当我尝试从数据库检索它时,数据库会分配200 mb来检索这个20-30 mb的对象
我的问题是:
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; }
}
类程序
{
私有静态字符串_路径;
静态void Main(字符串[]参数)
{
_path=path.Combine(AppDomain.CurrentDomain.BaseDirectory,“testDb”);
//在第一次插入后添加注释,以避免添加相同的对象。
AddData();
var data=GetData();
Console.ReadLine();
}
公共静态void AddData()
{
var items=新列表();
对于(var指数=0;指数<16;指数++)
{
var item=newitem{Values=Enumerable.Range(0185_000)。选择(v=>(double)v.ToList();
项目。添加(项目);
}
var testData=newtestclass{Name=“Test1”,Items=Items.ToList()};
使用(var db=new LiteDatabase(_path))
{
var collection=db.GetCollection();
collection.Insert(testData);
}
}
公共静态测试类GetData()
{
使用(var db=new LiteDatabase(_path))
{
var collection=db.GetCollection();
//这一行会导致巨大的内存分配,并多次唤醒垃圾收集器。
return collection.FindOne(Query.EQ(nameof(TestClass.Name),“Test1”);
}
}
}
公共类TestClass
{
公共int Id{get;set;}
公共字符串名称{get;set;}
公共IList项{get;set;}
}
公共类项目
{
公共IList值{get;set;}
}
将185_000
更改为1_850_000
会使我的RAM使用率变为>4GB(!)
探查器:
首先,按照您创建列表的方式,它将为262.144个元素预留空间,因为它的空间有限 您应该事先设置项目数量以避免出现这种情况(或者可能只是一起使用数组):
值=新列表(最大值);
Values.AddRange(Enumerable.Range(0,max).Select(v=>(double)v));
就LiteDB而言,如果您不需要数据库(以及它带来的潜在开销),只需将其存储在您自己的数据结构中即可。如果您不实际使用数据库而只存储单个项目,我看不到数据库的任何好处。在LiteDB中分配比direct
List
多得多的内存有几个原因
要理解这一点,您需要知道键入的类被转换为BsonDocument
结构(使用BsonValues
)。此结构具有开销(+1或+5字节/BsonValue)
此外,要序列化此类(插入时),LiteDB必须使用所有这些BsonDocument
(BSON格式)创建一个字节[]
。之后,这个超大的字节[]
被复制到许多扩展页面(每个页面包含一个字节[4070]
)
不仅如此,LiteDB还必须跟踪原始数据以存储在日志区域中。所以,这个尺寸可以加倍
要反序列化,LiteDB必须执行相反的过程:将所有页面从磁盘读取到内存,将所有页面连接到单个字节[]
,反序列化到BsonDocument
,以完成到类的映射
对于小对象,此操作正常。每次读/写新文档时都会重新使用该内存,因此内存可以控制
在下一个v5版本中,此过程有一些优化,如:
- 反序列化不需要将所有数据分配到单个
中即可读取文档。这可以使用新的字节[]
完成。序列化仍然需要这个ChunkStream(IEnumerable)
字节[]
- 日志文件已更改为WAL(提前写入日志)-不需要保留原始数据
不再存储在缓存中ExtendPage
Span
类来重用以前的内存分配。但我需要更多的研究
但是,存储一个具有185000个值的文档是任何nosql数据库中的最佳解决方案。MongoDB将BSON文档大小限制为16Mb(早期版本限制为~368kb)。。。我将v2中的LiteDB限制为1Mb。。。但我删除了这个检查大小,只是作为建议添加,以避免大型单个文档
尝试将类拆分为两个集合:一个用于数据,另一个用于每个值。您还可以将这个大数组拆分为块,如LiteDB FileStorage或MongoDB GridFS。@Caramiriel您的意思是什么?您是指
可枚举的.Range(0185_000)
还是探查器屏幕截图?现在我看到了BsonArray,这使得它更可能成为LiteDB框架中的问题。这是预期的行为吗?
我不是LiteDB
专家,但这不会让我感到惊讶。一个大型数组将位于大型对象堆上。如果它是作为读取过程的一部分克隆的,那么这可以解释一些RAM的使用。如果它被转换成或从另一种格式(如BSON)转换而来,这可能也解释了它的一部分。从根本上说,你关心的是什么。NET是一个垃圾收集的运行时,它使用的RAM比您想象的要多,这并没有什么害处。你为什么担心?@mjwills我知道复制会增加内存使用,但从20MB增加到>200MB是荒谬的。如果我将其更改为创建1185000个元素,那么由于ram的使用,它将变得不稳定。我不是nosql专家,但为200 mb的文件分配>4gb似乎不太合适。如果是这样,我怀疑您需要尝试其他数据库平台,看看
Values = new List<double>(max);
Values.AddRange(Enumerable.Range(0, max).Select(v => (double)v));