我正在开发一个应用程序,它利用非常大的查找表来加速数学计算。这些表中最大的是一个int [],它有大约1000万个条目。并非所有查找表都是int []。例如,一个是包含约200,000个条目的词典。目前,我使用以下代码段生成每个查找表一次(需要几分钟)并将其序列化为磁盘(带压缩):
int[] lut = GenerateLUT();
lut.Serialize("lut");
其中Serialize的定义如下:
public static void Serialize(this object obj, string file)
{
using (FileStream stream = File.Open(file, FileMode.Create))
{
using (var gz = new GZipStream(stream, CompressionMode.Compress))
{
var formatter = new BinaryFormatter();
formatter.Serialize(gz, obj);
}
}
}
我遇到的烦恼是启动应用程序时,这些查找表的反序列化需要很长时间(超过15秒)。这种类型的延迟会使用户烦恼,因为在加载所有查找表之前,应用程序将无法使用。目前反序列化如下:
int[] lut1 = (Dictionary<string, int>) Deserialize("lut1");
int[] lut2 = (int[]) Deserialize("lut2");
...
其中反序列化定义为:
public static object Deserialize(string file)
{
using (FileStream stream = File.Open(file, FileMode.Open))
{
using (var gz = new GZipStream(stream, CompressionMode.Decompress))
{
var formatter = new BinaryFormatter();
return formatter.Deserialize(gz);
}
}
}
起初,我认为可能是导致速度减慢的gzip压缩,但删除它只是从序列化/反序列化例程中略过了几百毫秒。
有人建议在应用初次启动时加快这些查找表的加载时间吗?
答案 0 :(得分:2)
首先,在后台线程中反序列化将阻止应用程序在发生这种情况时“挂起”。仅这一点就足以解决你的问题了。
然而,一般而言,序列化和反序列化(特别是大型词典)往往非常慢。根据数据结构的不同,编写自己的序列化代码可以大大加快这一速度,尤其是在数据结构中没有共享引用的情况下。
话虽如此,根据其使用模式,数据库可能是更好的方法。您总是可以创建更加面向数据库的东西,并从DB中以惰性方式构建查找表(即:查找是在LUT中查找,但如果查找不存在,则从DB加载并保存它在表中)。这将使启动瞬间完成(至少在LUT方面),并且可能仍然保持查找相当活泼。
答案 1 :(得分:0)
我想明显的建议是在后台加载它们。一旦应用程序启动,用户已经打开了他们的项目,并选择了他们想要的任何操作,剩下15秒钟就没有多少时间等待。
答案 2 :(得分:0)
我们在这里谈论的数据有多少?根据我的经验,从磁盘读取一块千兆字节到内存大约需要20秒。因此,如果您的读数超过半千兆字节,那么您几乎肯定会遇到硬件限制。
如果数据传输速率不是问题,那么实际的反序列化需要时间。如果您有足够的内存,则可以将所有表加载到内存缓冲区(使用File.ReadAllBytes()
),然后从内存流中反序列化。这将允许您确定读取的时间,以及反序列化的时间。
如果反序列化需要花费很多时间,那么如果你有多个处理器,你可以生成多个thred来并行进行序列化。使用这样的系统,您可能会在为另一个表加载数据时反序列化一个或多个表。这种流水线方法可以使您的整个加载/反序列化时间几乎与仅加载一样快。
答案 3 :(得分:0)
另一种选择是将表格放入表:真实数据库表中。甚至像Access之类的引擎也应该产生相当好的性能,因为每个查询都有一个明显的索引。现在,应用程序只需要在实际使用数据时读取数据,即使这样,它也会确切地知道在文件中查找的位置。
这可能会使应用程序的实际性能降低一些,因为您必须为每次计算执行磁盘读取。但它会使应用程序的感知性能更好,因为从不漫长的等待。并且,无论喜欢与否,感知可能比现实更重要。
答案 4 :(得分:0)
为什么拉链?
磁盘大于RAM。
直接二进制读取应该非常快。