Java 在两个单独的线程中读取和处理文件的速度是一个线程的两倍

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

我解决了计算文本文件中唯一行的任务。每个字符串都是一个有效的ip地址。该文件可以是任意大小的(从字面上说,可以是数百或数千GB)。我编写了一个简单的类,它实现了一个位数组并使用它进行计数

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;
    }
}