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

4

1 回答 1

2

正如@EJP 提到的,磁盘并不是真正的多线程,尽管 SSD 可能会有所帮助。映射缓冲区的目的是让您不必自己管理内存;让操作系统来做吧,因为它的虚拟内存管理器和文件系统缓存会比将其移动到 Java 堆中更快,并且可能比您编写的任何内存管理代码都快。

如果处理真的可以并行化,最好让一个线程读取整个文件,将其分成块(可能以某种中间数据格式),然后让执行程序处理这些块。文件读取线程可以与其他线程并发运行,因此您无需读取整个文件即可开始处理。

您可能想尝试将执行程序的数量设置为,cores - 1这样您就不会饿死文件读取线程。这将使操作系统有机会在没有上下文切换的情况下保持文件读取线程在单个内核上运行,因此您将获得良好的 IO 性能,同时使用其他内核执行 CPU 密集型工作。

仅供参考,这就是构建 Apache Spark 的目的。如果您需要处理更大的文件或需要比单个系统更快的处理速度,您可能需要查看它。

于 2016-06-04T01:54:01.000 回答