2016-06-03 2 views
4

У меня огромный массив байтов, который нужно обработать. Теоретически, можно было бы разрезать работу на ровные части и назначить их для разных потоков, чтобы повысить производительность на многоядерной машине.Многопоточные байтовые буферы медленнее, чем последовательные?

Я выделил ByteBuffer для каждого потока и обрабатываемых частей данных каждый. Конечная производительность медленнее, чем с одним потоком, хотя у меня есть 8 логических процессоров. И это очень непоследовательно. Иногда один и тот же ввод удваивается, как медленный процесс, или больше. Почему это? Сначала данные загружаются в память, поэтому выполняются не более IO операций.

размещаю мои ByteBuffers с помощью MappedByteBuffer, потому что это быстрее, чем ByteBuffer.wrap():

public ByteBuffer getByteBuffer() throws IOException 
{ 
    File binaryFile = new File("..."); 
    FileChannel binaryFileChannel = new RandomAccessFile(binaryFile, "r").getChannel(); 

    return binaryFileChannel.map(FileChannel.MapMode.READ_ONLY, 0, binaryFileChannel.size()); 
} 

Я делаю свою параллельную обработку с помощью Executors:

int threadsCount = Runtime.getRuntime().availableProcessors(); 
ExecutorService executorService = Executors.newFixedThreadPool(threadsCount); 
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executorService); 

for (ByteBufferRange byteBufferRange : byteBufferRanges) 
{ 
    Callable<String> task =() -> 
    { 
     performTask(byteBufferRange); 

     return null; 
    }; 

    completionService.submit(task); 
} 

// Wait for all tasks to finish 
for (ByteBufferRange ignored : byteBufferRanges) 
{ 
    completionService.take().get(); 
} 

executorService.shutdown(); 

Параллельные задачи performTask() использовать свои собственные ByteBuffer экземпляров для чтения памяти из буфера, делать вычисления и так далее. Они не синхронизируют, не пишут и не влияют друг на друга. Любые идеи о том, что происходит не так, или это не хороший случай распараллеливания?

такая же проблема есть ByteBuffer.wrap() и MappedByteBuffer такой же.

+0

Как вы бы сказали, массив? – Logan

+1

Массированные буферы не являются файлами, загружаемыми в память. ОС динамически отображает фрагменты (страницы) содержимого файла в память, когда вы его читаете, и сворачивает данные для других данных, как только вы читаете в другом месте. Это означает, что вы используете очень мало фактической памяти, в то время как она может выглядеть так, как если бы у вас были террабайты в памяти. Но также означает, что перескакивание может потребовать повторного чтения с диска. – zapl

+0

@LoganKulinski: Несколько 100MB – BullyWiiPlaza

ответ

2

Как упоминалось в @EJP, диск на самом деле не многопоточен, хотя SSD может помочь. Точка сопоставления буфера заключается в том, что вам не нужно самостоятельно управлять памятью; пусть OS это делает, так как его менеджер виртуальной памяти и кеш файловой системы будут быстрее, чем перемещать его в кучу Java и, вероятно, быстрее, чем любой код управления памятью, который вы пишете.

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

Возможно, вы захотите установить количество исполнителей на cores - 1, чтобы вы не проголодали нить чтения файла. Это дало бы ОС возможность поддерживать поток чтения файлов на одном ядре без переключения контекста, чтобы вы получили хорошую производительность ввода-вывода, а другие ядра - для интенсивной работы с ЦП.

FYI, это то, для чего создан Apache Spark. Вы можете захотеть взглянуть на это, если вам нужно работать с большими файлами или нужно обрабатывать быстрее, чем то, что может сделать одна система.

Смежные вопросы