在db40 (.net)中处理大型数据集。

3
我希望使用db4o作为自定义缓存实现的后端。通常,我的程序需要将大约4000万个对象加载到内存中并同时处理它们。显然,这需要大量的内存,因此我考虑将一些对象(不在缓存中的对象)持久化到db4o数据库中。我的初步测试显示,db4o的速度比我想象的要慢一些(大约需要17分钟来保存100万个对象)。但是,我只是使用了最基本的设置。
using (var reader = new FileUnitReader(Settings, Dictionary, m_fileNameResolver, ObjectFactory.Resolve<DataValueConverter>(), ObjectFactory.Resolve<UnitFactory>()))
using (var db = Db4oEmbedded.OpenFile(Db4oEmbedded.NewConfiguration(), path))
{
    var timer = new Stopwatch();
    timer.Start();
    IUnit unit = reader.GetNextUnit();
    while (unit != null)
    {
        db.Store(unit);
        unit = reader.GetNextUnit();
    }
    timer.Stop()
    db.Close();

    var elapsed = timer.Elapsed;
}

有人能提供关于如何在这种情况下提高性能的建议吗?


你的对象大小相同还是有最大大小?它们有某种ID吗? - Mikael Svenson
它们确实有一个标识符。对象的大小在程序单次运行中不会变化,但是不同的运行将产生不同大小的对象(基于读入的变量数量)。 - Jeffrey Cameron
1
如果您可以使用.Net4,那么创建一个巨大的文件并为文件中的每个对象分配空间,并通过文件偏移量直接访问它们(id * itemsize)可能会更成功。并使用内存映射文件来进行随机访问。这将是一种不同于db4的方法。有点像我在这里所做的-https://dev59.com/1HE85IYBdhLWcg3w64EA - Mikael Svenson
我一直在考虑这个问题。这里有一个库:http://mmf.codeplex.com/,它提供了在早期版本的.NET中访问内存映射文件的功能,但它并没有针对32位系统进行优化,而这正是我真正想要的。我们在工作中有MSDN Universal订阅,所以我可以尝试在不久的将来升级到.NET4,并尝试您的解决方案。谢谢。 - Jeffrey Cameron
1
mmf 项目是我的项目 ;) 在 32 位系统上让它运行得更好并不难。而且你提醒我需要做 .Net4 版本和其他一些优化。 - Mikael Svenson
我真的很喜欢mmf项目!如何在不切换到.NET 4的情况下使其在32位上更好地工作?我很感兴趣知道! - Jeffrey Cameron
2个回答

2
我认为有几种选项可以改善这种情况下的性能。
我还发现,在这种情况下,反射开销可能会成为一个相当大的部分。因此,您可以尝试使用fast-reflector来解决问题。请注意,FastReflector消耗更多内存。但在您的情况下,这并不重要。您可以像这样使用快速反射器:
var config = Db4oEmbedded.NewConfiguration();
config.Common.ReflectWith(new FastNetReflector());

using(var container = Db4oEmbedded.OpenFile(config, fileName))
{
}

当我做类似的微小“基准测试”时,我发现较大的缓存大小即使在写入数据库时也能稍微提高性能:

var config = Db4oEmbedded.NewConfiguration();
config.File.Storage = new CachingStorage(new FileStorage(), 128, 1024 * 4);

其他注意事项: db4o的事务处理并不是针对大型事务进行优化的。当您在一个事务中存储100万个对象时,提交可能需要很长时间或者会耗尽内存。因此,您可能需要更频繁地提交。例如,在存储100,000个对象后进行提交。当然,您需要检查它是否真正对您的场景产生影响。


1
有趣。我尝试了FastNetReflector,它将所需时间减少了一半。然而,我仍然没有达到每100万条记录2分钟的目标加载速度。FastNetReflector让我降到了大约8-9分钟每100万条记录。还有其他建议吗? - Jeffrey Cameron
哦,我不知道有什么东西可以使其达到目标速度的四倍。我需要调查一下瓶颈在哪里。你真的需要一个复杂的对象数据库吗?由于你将其用于缓存,可能有更“轻量级”的解决方案,速度更快。 - Gamlor
有一些其他的解决方案(请参见上面的评论),但我想尝试db4o解决方案,因为它提供了简单性和稳健性。谢谢! - Jeffrey Cameron

1

另一个你可以尝试的小改进:

通过在OpenFile()调用中添加.Ext()来获取扩展接口。

在存储每个对象后清除它。

using (var db = Db4oEmbedded.OpenFile(Db4oEmbedded.NewConfiguration(), path).Ext())
// ....
db.Store(unit);
db.Purge(unit);
// ....

这样做可以减少db4o在当前事务中需要维护的引用数量。

如果您尝试调整存储配置(即db4o下面的可插拔文件系统),可能会有更大的改进潜力。最新的8.0版本具有更好的缓存实现,当您使用更多的缓存页面时,不会降低缓存维护的性能。

我建议您尝试使用Gamlor建议的缓存设置来尝试最新的8.0版本,看看是否有所改善:

config.File.Storage = new CachingStorage(new FileStorage(), 128, 1024 * 4);

如果可以的话,你也可以尝试更高的数字:
config.File.Storage = new CachingStorage(new FileStorage(), 1280, 1024 * 40);

很奇怪,升级从7.12到8.0并使用db.Purge(以及之前建议的FastNetReflector和CachingStorage),实际上程序运行时间更长了... :-/ 我将尝试再次运行但不使用Purge,看看是否有帮助。 - Jeffrey Cameron
好的,尝试了一下没有db.Purge()调用的情况。看起来它在管理内存方面要好得多,但仍然比7.12慢。 - Jeffrey Cameron
db4o 8.0 默认采用了新的 IdSystem,对于存在碎片化数据库,其速度要快得多,但在不存在碎片化影响的原始存储操作中,可能会稍微慢一些。旧的 PointerBasedIdSystem 仍然可以按以下方式使用:config.IdSystem.UsePointerBasedSystem(); - Carl Rosenberger
谢谢,卡尔。我也会试一下,然后告诉你结果的。 - Jeffrey Cameron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接