Java 在两个单独的线程中读取和处理文件的速度是一个线程的两倍
我解决了计算文本文件中唯一行的任务。每个字符串都是一个有效的ip地址。该文件可以是任意大小的(从字面上说,可以是数百或数千GB)。我编写了一个简单的类,它实现了一个位数组并使用它进行计数Java 在两个单独的线程中读取和处理文件的速度是一个线程的两倍,java,multithreading,file-processing,Java,Multithreading,File Processing,我解决了计算文本文件中唯一行的任务。每个字符串都是一个有效的ip地址。该文件可以是任意大小的(从字面上说,可以是数百或数千GB)。我编写了一个简单的类,它实现了一个位数组并使用它进行计数 public class IntArrayBitCounter { public static final long MIN_BIT_CAPACITY = 1L; public static final long MAX_BIT_CAPACITY = 1L << 32; p
public class IntArrayBitCounter {
public static final long MIN_BIT_CAPACITY = 1L;
public static final long MAX_BIT_CAPACITY = 1L << 32;
private final int intArraySize;
private final int[] intArray;
private long counter;
public IntArrayBitCounter(long bitCapacity) {
if (bitCapacity < MIN_BIT_CAPACITY || bitCapacity > MAX_BIT_CAPACITY) {
throw new IllegalArgumentException("Capacity must be in range [1.." + MAX_BIT_CAPACITY + "].");
}
this.intArraySize = 1 + (int) ((bitCapacity - 1) >> 5);
this.intArray = new int[intArraySize];
}
private void checkBounds(long bitIndex) {
if (bitIndex < 0 || bitIndex > ((long) intArraySize << 5)) {
throw new IndexOutOfBoundsException("Bit index must be in range [0.." + (MAX_BIT_CAPACITY - 1) + "].");
}
}
public void setBit(long bitIndex) {
checkBounds(bitIndex);
int index = (int) (bitIndex >> 5);
int bit = 1 << (bitIndex & 31);
if ((intArray[index] & bit) == 0) {
counter++;
intArray[index] |= bit;
}
}
public boolean isBitSets(long bitIndex) {
checkBounds(bitIndex);
int index = (int) (bitIndex >> 5);
int bit = 1 << (bitIndex & 31);
return (intArray[index] & bit) != 0;
}
public int getIntArraySize() {
return intArraySize;
}
public long getBitCapacity() {
return (long) intArraySize << 5;
}
public long getCounter() {
return counter;
}
}
公共类IntArrayBitCounter{
公共静态最终长最小位容量=1L;
公共静态最终长最大比特容量=1L最大比特容量){
抛出新的IllegalArgumentException(“容量必须在范围[1..”+最大位容量+“]);
}
this.intArraySize=1+(int)((比特容量-1)>>5);
this.intArray=newint[intArraySize];
}
私有无效检查边界(长bitIndex){
如果(bitIndex<0 | | bitIndex>((长)不规则>5);
int位=1>5);
int bit=1是否有可能在数据块进入队列后,阻塞队列不仅会阻塞消费者,还会阻塞发送者?在这种情况下,您的读取线程必须暂停,并且启动下一次读取操作意味着等待硬盘驱动器完成下一次旋转
如果增加阻塞队列的大小,会获得什么性能
因此,您必须确保读卡器从不暂停。如果队列太大,请增加使用线程的数量。要回答为什么多线程尝试的速度比单线程慢两倍的问题,请尝试测量
- 整个过程所用的时间(您已经这样做了)
- 生产者活动时间(从磁盘读取和格式化队列数据)
- 生产者队列等待时间(实际将数据填充到队列中并最终阻塞的时间)
我想这就是你得到答案的地方。@user15358848我不是在两个线程中读取。我在一个线程中读取,在第二个线程中计数。@chptr one:但是你的代码在读取上花费的时间仍然比在计数上花费的时间要多。而且由于单个线程必须等待更多的数据到达(即瓶颈不是CPU速度),将任何工作分散到多个线程实际上并不会加快速度。换句话说,如果您的硬盘需要X个时间来完全读取文件,并且一个CPU可以在X个时间内计算行数,那么拆分计数将没有帮助(因为您可以在硬盘继续读取时进行计数)。而且由于计算行数非常简单,而且HDD速度很慢,这一点将始终保持不变。即使在两个独立线程中执行IO和CPU,从HDD读取的速度也不会超过130-135 MB/s。查看代码,我认为阻塞队列及其脆弱的1024限制会导致争用,并实际增加HDD读取总的来说是延迟。给自己找一个好的分析器,看看你一半的时间都花在哪里了(比如通过分析阻塞的线程)。您可以通过跳过处理来检查读取的实际上限。删除所有处理并测试应用程序的实际读取速度。单线程有可能已经达到最大速度。当您使用两个线程时,您需要花费时间进行同步。您可以通过将数据批量提交到队列来减少开销。是什么让您变瘦了k添加更多上下文切换和并行处理会更快?性能实际上不会因队列的大小而改变。如果队列不为空,则队列不应阻止使用者,这不是正常行为。发件人:另外支持在检索时等待队列变为非空的操作的队列元素,并等待队列中的空间在存储元素时变为可用。=>因此请注意,每个设计都有等待时间。我所指出的是确保它们不在读取器端。是的,它不在消费者端,我检查了它。我的队列几乎总是满的。这是对制作者等待的确认。你知道吗如果不是更快的话,至少要以生产者的速度消费。所以要么增加队列大小,要么添加消费者。谢谢,这是一个显而易见的决定,我应该尝试一下。但这并不能回答为什么我的多线程尝试比单线程慢两倍的问题。经常发生的情况是,我没有衡量实际应该是什么当然。现在我使用了一个分析器和jmh,发现了什么是真正的瓶颈。谢谢,现在我可以睁大眼睛继续前进了。
public class IpCounterApp {
private static long toLongValue(String ipString) throws UnknownHostException {
long result = 0;
for (byte b : InetAddress.getByName(ipString).getAddress())
result = (result << 8) | (b & 255);
return result;
}
public static void main(String[] args) {
long startTime = System.nanoTime();
String fileName = "src/test/resources/test.txt";
var counter = new IntArrayBitCounter(1L << 32);
long linesProcessed = 0;
try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
counter.setBit(toLongValue(line));
linesProcessed++;
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.printf("%d unique lines in %d processed\n", counter.getCounter(), linesProcessed);
long elapsedTime = System.nanoTime() - startTime;
System.out.println("duration: " + elapsedTime / 1000000 + " milliseconds");
}
}
public class ConcurrentIpCounterApp {
public static void main(String[] args) {
long startTime = System.nanoTime();
String fileName = "src/test/resources/test.txt";
var stringsQueue = new ArrayBlockingQueue<String>(1024);
var reader = new BlockingQueueFileReader(stringsQueue, fileName);
var counter = new BlockingQueueCounter(stringsQueue);
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<Long> linesProcessed = executorService.submit(reader);
Future<Long> uniqueLines = executorService.submit(counter);
try {
System.out.printf("%d unique lines in %d processed\n", uniqueLines.get(), linesProcessed.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
long elapsedTime = System.nanoTime() - startTime;
System.out.println("duration: " + elapsedTime / 1000000 + " milliseconds");
}
}
public class BlockingQueueCounter implements Callable<Long> {
private final BlockingQueue<String> queue;
private final IntArrayBitCounter counter;
public BlockingQueueCounter(BlockingQueue<String> queue) {
this.queue = queue;
this.counter = new IntArrayBitCounter(1L << 32);
}
private static long toLongValue(String ipString) throws UnknownHostException {
long result = 0;
for (byte b : InetAddress.getByName(ipString).getAddress())
result = (result << 8) | (b & 255);
return result;
}
@Override
public Long call() {
String line;
while (true) {
try {
line = queue.take();
if ("EOF".equals(line)) {
break;
}
counter.setBit(toLongValue(line));
} catch (InterruptedException | UnknownHostException e) {
e.printStackTrace();
}
}
return counter.getCounter();
}
}
public class BlockingQueueFileReader implements Callable<Long> {
private final BlockingQueue<String> queue;
private final String fileName;
private long totalLines;
public BlockingQueueFileReader(BlockingQueue<String> queue, String fileName) {
this.queue = queue;
this.fileName = fileName;
}
@Override
public Long call() {
try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
queue.put(line);
totalLines++;
}
queue.add("EOF");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return totalLines;
}
}