并行二进制反序列化?

4
我有一个解决方案,需要快速地将对象读入内存,但是二进制流可能会在内存中缓存压缩以节省磁盘IO时间。
我已经尝试了不同的解决方案,显然XmlTextWriter和XmlTextReader并不好用,内置的二进制序列化也不行。Protobuf-net非常出色,但仍然有点慢。以下是一些统计数据:
文件大小XML:217 kb
文件大小二进制:87 kb
压缩二进制:26 KB
压缩XML:26 KB
使用XML进行反序列化(XmlTextReader):8.4秒
使用二进制进行反序列化(Protobuf-net):6.2秒
使用未采用字符串内部化的二进制进行反序列化(Protobuf-net):5.2秒
从内存中反序列化二进制:5.9秒
将二进制文件解压到内存所需的时间:1.8秒
使用XML进行序列化(XmlTextWriter):11秒
使用二进制进行序列化(Protobuf):4秒
使用带长度前缀的二进制进行序列化(Protobuf-net):3.8秒
这让我想到,看起来(如果我错了,请纠正我),反序列化的主要罪魁祸首是实际的字节转换而不是IO。如果是这样的话,那么它应该是使用新的Parallel扩展的候选者。
由于我在二进制IO方面有点新手,所以在投入时间之前,我希望得到一些输入:)
为了简单起见,假设我们想反序列化一个没有可选字段的对象列表。我的第一个想法是简单地使用每个长度前缀存储每个对象。将每个字节数组读入字节数组列表中,并使用PLINQ进行字节数组 -> 对象反序列化。
然而,使用该方法仍然需要单线程读取字节数组,因此也许可以将整个二进制流读入内存(那么多大的二进制文件适合这样做?),并在二进制文件开头存储有多少个对象以及它们的长度和偏移量。然后我应该能够只创建ArraySegments或类似的东西,并且也可以在并行中进行分块。
那么你们觉得,这可行吗?

抱歉,有些问题我并没有得到明确的答案,但一开始我就没有意识到要设置它。不过我已经吸取了教训,感谢你指出来。不过似乎你不能在旧问题上设置答案,或者是我漏掉了什么? - Homde
支持Alon,你发布的那个Java字符串比较问题得到了一些非常深思熟虑的答案... - Jason D
同意,我如何设置正确答案? - Homde
没关系,我太蠢了,没意识到你可以点击那个勾号 :) - Homde
“sek”是指秒吗?如果是,那么似乎其他地方出了严重的问题。我们可以在几分之一秒内反序列化大小为数十MB的文档... - nitzmahone
4个回答

2

我经常做这样的事情,没有什么比使用BinaryReader读取数据更好的了。据我所知,使用BinaryReader.ReadInt32读取一个32位整数是最快的。

你可能会发现让代码并行执行,并将结果合并起来会增加太多额外的开销。如果你真的想采用并行的方式,我建议使用多个线程来读取多个文件,而不是使用多个线程来分块读取同一个文件。

你也可以尝试调整块的大小以匹配磁盘块的大小,但是在应用程序和磁盘之间存在太多的抽象层级,这可能是一种浪费时间的做法。


1

二进制文件可以被多个线程同时读取。为了做到这一点,必须使用适当的访问/共享修饰符打开它。然后每个线程可以在该文件中获取自己的偏移量和长度。因此,并行读取不是问题。

让我们假设您将坚持使用简单的二进制格式:每个对象都带有其长度前缀。知道这一点,您可以“滚动”文件并知道放置反序列化线程的偏移量。

反序列化算法可能如下所示: 1)分析文件(将其分成几个相对较大的块,块边界应与对象边界重合) 2)生成必要数量的反序列化器线程,并“指示”它们使用适当的偏移量和长度进行读取 3)将所有反序列化器线程的结果组合成一个列表


嗨,我正在开发一个类似的解决方案,但是与其并行从磁盘读取,我决定先将整个文件检索到字节缓冲区中,然后进行反序列化/序列化。如果你并行从磁盘读取,你会受到磁盘速度的限制,这种方法看起来更快。完成后,我会在这里发布我的解决方案的一些信息,谢谢。 - Homde
实际上,对于小数据大小来说,将其缓存在内存中更为可行。我提出了文件读取,因为它是一种更加通用的解决方案。可以将内存缓存作为优化添加(毕竟不知道有人会想要反序列化多少数据 :) ) - Vadym Stetsiak

1
这让我想到,似乎(如果我错了,请纠正)反序列化的主要罪魁祸首是实际的字节转换而不是IO。
不要假设时间花在哪里,使用分析器找出来。

0
当我反序列化超过1 MB的对象列表时,使用以下代码可以在不到2秒的时间内完成反序列化:

。这与您的程序开发和性能优化有关。

public static List<T> FromXML<T>(this string s) where T : class
        {
            var ls = new List<T>();
            var xml = new XmlSerializer(typeof(List<T>));
            var sr = new StringReader(s);
            var xmltxt = new XmlTextReader(sr);
            if (xml.CanDeserialize(xmltxt))
            {
                ls = (List<T>)xml.Deserialize(xmltxt);
            }
            return ls;
        }

如果针对 XML 的情况,试试这个怎么样?


XML序列化的工作原理是这样的,但开销的一部分可能是相对较小的大量对象,因此对象创建成为一个问题。尽管XML序列化几乎永远比二进制快,而且更加冗长,这导致了由于文件IO而需要更多时间。 - Homde

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