我有一个需要处理的巨大字节数组.从理论上讲,应该可以将工作分成均匀的部分,并将它们分配给不同的线程,以提高多核机器的性能.
我为每个线程分配了一个ByteBuffer,并分别处理了部分数据.即使我有8个逻辑处理器,最终性能也比单个线程慢.它也是非常不一致的.有时相同的输入是加倍或更多的两倍.这是为什么?数据首先加载到内存中,因此不再执行IO操作.
我使用MappedByteBuffer
分配我的ByteBuffers,因为它比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都存在同样的问题.
最佳答案 正如@EJP所提到的,虽然SSD可能有所帮助,但磁盘并不是真正的多线程.映射缓冲区的目的是让您不必自己管理内存;让操作系统做到这一点,因为它的虚拟内存管理器和文件系统缓存比将其移入Java堆更快,并且可能比你编写的任何内存管理代码更快.
如果处理真的可以并行化,那么最好让单个线程读取整个文件,将其分成块(可能是某些中间数据格式),然后让执行程序在这些块上工作.文件读取线程可以与其他线程并发运行,因此您无需读取整个文件即可开始处理.
您可能想尝试将执行程序的数量设置为核心 – 1,这样您就不会使文件读取线程饿死.这将使操作系统有机会在没有上下文切换的情况下保持文件读取线程在单个内核上运行,因此在使用其他内核进行CPU密集型工作时,您将获得良好的IO性能.
仅供参考,这就是Apache Spark的基础.如果您需要处理更大的文件或者需要比单个系统更快地处理,您可能需要查看它.