Подтвердить что ты не робот

Java: многопоточное декодирование потока символов

Я поддерживаю высокопроизводительный анализатор CSV и стараюсь максимально использовать новейшие технологии для повышения пропускной способности. Для этих конкретных задач это означает:

  • Флэш-память (у нас есть относительно недорогая плата PCI-Express, 1 ТБ хранения, которая достигает 1 ГБ/с).
  • Несколько ядер (у нас есть дешевый сервер Nehalem с 16 аппаратными потоками)

Первая реализация анализатора CSV была однопоточной. Чтение файлов, декодирование символов, разбиение полей, разбор текста, все в пределах одного потока. Результатом была пропускная способность около 50 МБ/с. Неплохо, но значительно ниже предела хранения...

Вторая реализация использует один поток для чтения файла (на уровне байта), один поток для декодирования символов (от ByteBuffer до CharBuffer) и несколько потоков для синтаксического анализа полей (я имею в виду разбор текстовых полей с разделителями на двойные, целые числа, даты...). Это работает намного быстрее, около 400 МБ/с на нашей коробке.

Но все еще намного ниже производительности нашего хранилища. И эти SSD снова улучшатся в будущем, мы не будем максимально использовать его в Java. Понятно, что текущим ограничением является декодирование символов (CharsetDecoder.read(...)). Это узкое место, на мощном процессоре Nehalem он преобразует байты в символы со скоростью 400 Мбайт/с, довольно хорошо, но это должно быть однопоточным. CharsetDecoder несколько устарел, в зависимости от используемой кодировки, и не поддерживает многопотоковое декодирование.

Итак, мой вопрос к сообществу (и спасибо за то, что вы прочитали сообщение до сих пор): кто-нибудь знает, как распараллеливать операцию декодирования charset в Java?

4b9b3361

Ответ 1

Кто-нибудь знает, как распараллелить операцию декодирования charset в Java?

Возможно, вы сможете открыть несколько потоков ввода для этого (я не уверен, как вы это делаете с NIO, но это должно быть возможно).

Насколько сложно это будет зависеть от кодировки, от которой вы декодируете. Вам понадобится заказное решение для целевой кодировки. Если кодировка имеет фиксированную ширину (например, Windows-1252), то один байт == один символ и декодирование просты.

Современные кодировки с переменной шириной (например, UTF-8 и UTF-16) содержат правила для идентификации первого байта последовательности символов, поэтому можно перейти в середину файла и начать декодирование (у вас будет отметить конец предыдущего фрагмента, поэтому сначала нужно начать декодирование конца файла).

Некоторые устаревшие кодировки переменной ширины могут быть не очень хорошо разработаны, поэтому у вас не будет выбора, кроме как для декодирования с начала данных и последующего чтения.

Если это опция, сгенерируйте ваши данные как UTF-16BE. Затем вы можете вырезать декодирование и прочитать два байта прямо на char.

Если файл является Unicode, следите за обработкой спецификации, но я предполагаю, что вы уже знакомы со многими деталями низкого уровня.

Ответ 2

Ясно, что ограничение тока - это декодирование символов (CharsetDecoder.read(...))

Откуда вы это знаете? Ваш мониторинг/профилирование показывает окончательно, что поток декодера использует 100% одного из ваших ядер?

Другая возможность заключается в том, что ОС не способна управлять SSD с его теоретической максимальной скоростью.

Если декодирование UTF-8, безусловно, является узким местом, тогда должно быть возможно выполнить задачу параллельно. Но для этого вам обязательно понадобится реализовать свои собственные декодеры.

Ответ 3

Если вы знаете кодировку, и это либо фиксированный размер, либо не содержит перекрывающихся последовательностей байтов, вы можете сканировать специальную последовательность. В CSV последовательность для новых строк может иметь смысл. Даже если вы динамически обнаруживаете кодировку, вы можете запустить проход первых нескольких байтов для определения кодировки, а затем перейти к параллельному декодированию.

Ответ 4

Другой (сумасшедший) вариант заключается в том, чтобы просто отделить входные данные от кусков произвольного размера, игнорировать проблемы декодирования и затем декодировать каждый из кусков параллельно. Однако вы хотите убедиться, что куски перекрываются (с параметризованным размером). Если перекрывающаяся область двух кусков декодируется одинаковым образом двумя потоками (и ваше перекрытие было достаточно большим для указанной кодировки), было бы безопасно присоединиться к результатам. Чем больше перекрытие, тем больше требуется обработка и тем меньше вероятность ошибки. Кроме того, если вы находитесь в ситуации, когда вам известно, что кодировка UTF-8 или аналогично простая кодировка, вы можете установить перекрытие довольно низко (для этого клиента) и по-прежнему гарантировать правильную работу.

Если второй фрагмент окажется неправильным, вам придется его переделать, поэтому важно не делать большие куски параллельно. Если вы выполняете более двух кусков параллельно, важно "отремонтировать" от начала до конца, так что один несогласованный блок не приведет к недействительности следующего блока (который может быть правильно выровнен).