Java:多线程字符流解码

时间:2010-08-09 08:39:11

标签: java multithreading character-encoding

我正在维护一个高性能的CSV解析器,并尝试充分利用最新技术来提高吞吐量。对于这个特殊任务,这意味着:

  • 闪存(我们拥有相对便宜的PCI-Express卡,1 TB存储,可达到1 GB / s的持续读取性能)
  • 多核(我们拥有一个具有16个硬件线程的廉价Nehalem服务器)

CSV解析器的第一个实现是单线程。文件读取,字符解码,字段拆分,文本解析,都在同一个线程中。结果是吞吐量约为50MB / s。不错但远低于存储限制...

第二个实现使用一个线程来读取文件(在字节级别),一个线程来解码字符(从ByteBuffer到CharBuffer),以及多个线程来解析字段(我的意思是将分隔的文本字段解析为双精度数,整数,约会......)。这种方法效果更快,在我们的盒子上接近400MB / s。

但仍低于我们存储的性能。而那些SSD将来会再次改进,我们并没有在Java中充分利用它。很明显,当前的限制是字符解码(CharsetDecoder.read(...))。这是瓶颈,在强大的Nehalem处理器上,它将字节转换为400MB / s的字符,非常好,但这必须是单线程的。 CharsetDecoder有些有状态,具体取决于使用的字符集,并且不支持多线程解码。

所以我对社区的问题是(并感谢您阅读目前为止的帖子):有没有人知道如何在Java中并行化charset解码操作?

4 个答案:

答案 0 :(得分:3)

  

有没有人知道如何在Java中并行化charset解码操作?

您可以打开多个输入流来执行此操作(我不确定您是如何使用NIO进行此操作的,但必须可行)。

这将有多困难取决于您正在解码的编码。您将需要针对目标编码的定制解决方案。如果编码具有固定宽度(例如Windows-1252),那么一个字节==一个字符并且解码很容易。

现代可变宽度编码(如UTF-8和UTF-16)包含用于识别字符序列的第一个字节的规则,因此可以跳转到文件的中间并开始解码(您将拥有注意前一个块的结束,所以首先开始解码文件的结尾是明智的。

某些传统的可变宽度编码可能不是这么精心设计的,所以除了从数据的开头解码并按顺序读取它之外别无选择。

如果是选项,请将数据生成为UTF-16BE。然后你可以切断解码并直接读取两个字节为char。

如果文件是Unicode,请注意BOM处理,但我猜你已经熟悉了很多低级细节。

答案 1 :(得分:1)

  

很明显,目前的限制是字符解码(CharsetDecoder.read(...))

你怎么知道的?您的监控/分析是否显示最终解码器线程正在使用100%的核心?

另一种可能性是操作系统无法以理论最大速度驱动SSD。

如果UTF-8解码肯定是瓶颈,那么应该可以并行完成任务。但你肯定需要实现自己的解码器才能做到这一点。

答案 2 :(得分:0)

如果您知道编码,并且它是固定大小,或者不包含重叠的字节序列,则可以扫描特殊序列。在CSV中,换行符的序列可能有意义。即使您动态检测编码,也可以运行前几个字节的传递来确定编码,然后继续进行并行解码。

答案 3 :(得分:0)

另一个(疯狂的)替代方案是将输入分成任意大小的块,忽略解码问题,然后并行解码每个块。但是,您希望确保块重叠(使用参数化大小)。如果两个线程的重叠区域由两个线程以相同的方式解码(并且您的重叠对于指定的编码来说足够大),则加入结果应该是安全的。重叠越大,所需的处理越多,出错的概率就越小。此外,如果您处于您知道编码为UTF-8或类似简单编码的情况,您可以将重叠设置得非常低(对于该客户端)并且仍然保证正确操作。

如果第二块显示错误,则必须重做它,因此不要并行处理大块是很重要的。如果并行执行两个以上的块,那么从头到尾进行“修复”非常重要,这样一个未对齐的块不会导致下一个块无效(可能正确对齐)。