Java:多线程字符流解码

Java:多线程字符流解码,java,multithreading,character-encoding,Java,Multithreading,Character Encoding,我正在维护一个高性能的CSV解析器,并试图充分利用最新技术来提高吞吐量。对于此特定任务,这意味着: 闪存(我们拥有一个相对便宜的PCI Express卡,1 TB的存储容量,可达到1 GB/s的持续读取性能) 多核(我们拥有一台廉价的Nehalem服务器,具有16个硬件线程) CSV解析器的第一个实现是单线程的。文件读取、字符解码、字段拆分、文本解析,都在同一个线程中。结果是吞吐量约为50MB/s。不错,但远低于存储限制 第二个实现使用一个线程读取文件(在字节级别),一个线程解码字符(从By

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

  • 闪存(我们拥有一个相对便宜的PCI Express卡,1 TB的存储容量,可达到1 GB/s的持续读取性能)
  • 多核(我们拥有一台廉价的Nehalem服务器,具有16个硬件线程)
CSV解析器的第一个实现是单线程的。文件读取、字符解码、字段拆分、文本解析,都在同一个线程中。结果是吞吐量约为50MB/s。不错,但远低于存储限制

第二个实现使用一个线程读取文件(在字节级别),一个线程解码字符(从ByteBuffer到CharBuffer),多个线程解析字段(我的意思是将分隔的文本字段解析为双精度、整数、日期…)。这工作速度更快,在我们的机箱上接近400MB/s

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

因此,我向社区提出的问题是(感谢您阅读到目前为止的文章):有人知道如何在Java中并行化字符集解码操作吗

有人知道如何在Java中并行化字符集解码操作吗

您可能可以打开多个输入流来实现这一点(我不确定如何使用NIO实现这一点,但这必须是可能的)

这有多困难取决于解码的编码方式。您需要为目标编码定制解决方案。如果编码具有固定宽度(例如Windows-1252),则一个字节==一个字符,解码很容易

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

一些传统的可变宽度编码可能设计得不太好,因此您别无选择,只能从数据开始解码并按顺序读取

如果是选项,则将数据生成为UTF-16BE。然后你可以停止解码,直接把两个字节读成一个字符

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

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

你怎么知道的?您的监视/评测是否最终显示解码器线程使用了您的一个内核的100%

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


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

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

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


如果第二个块被证明是错误的,您将不得不重做它,因此重要的是不要并行处理大块。如果并行执行两个以上的块,则必须从头到尾“修复”,以便一个未对齐的块不会导致下一个块(可能已正确对齐)无效。

不幸的是,UTF-16是一种可变长度编码。您需要UTF32来进行这种简单的Unicode解析。@ GRDDEV-我在我的文章中涵盖了这一点——在UTF -16数据流的中间可以识别字符序列。高代理对是0xD800—0xDbFF,低代理是0xDC00 -0xDFFF。其他任何内容都包含在一对字节中。我的评论提到了UTF-16BE。你不能完全停止解码。但这真的很简单。感谢那些鼓舞人心的元素。当然,固定长度编码相对容易并行化。可以将原始字节存储在块中,以确保块不会在中间剪切字符,在并发任务中解码块并组装结果。也许我应该这样做,当我检测到一个可变长度的字符集时,就回到单线程模式。当然,我的目标是提供一个CSV解析器,无论字符集是什么,它都可以工作,以向世界各地的客户提供它的外来字符集。可能可以为各种字符集解码器的后续产品(如sun.nio.cs.UTF_8.Decoder等)获取源代码