Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/392.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在文本文件中求整数和的最快方法 问题:_Java_Performance_File Io - Fatal编程技术网

Java 在文本文件中求整数和的最快方法 问题:

Java 在文本文件中求整数和的最快方法 问题:,java,performance,file-io,Java,Performance,File Io,假设您有一个大的ASCII文本文件,每行上都有一个随机的非负整数,每个整数的范围从0到100000000。文件中有100000000行。读取文件并计算所有整数之和的最快方法是什么 约束条件:我们有10MB的RAM可供使用。该文件的大小为1GB,因此我们不希望读取整个文件,然后再对其进行处理 以下是我尝试过的各种解决方案。我发现结果相当令人惊讶 我错过了什么更快的吗 请注意:下面给出的所有计时均用于运行算法10次(运行一次并放弃;启动计时器;运行10次;停止计时器)。这台机器是一个相当慢的核心2二

假设您有一个大的ASCII文本文件,每行上都有一个随机的非负整数,每个整数的范围从0到100000000。文件中有100000000行。读取文件并计算所有整数之和的最快方法是什么

约束条件:我们有10MB的RAM可供使用。该文件的大小为1GB,因此我们不希望读取整个文件,然后再对其进行处理

以下是我尝试过的各种解决方案。我发现结果相当令人惊讶

我错过了什么更快的吗

请注意:下面给出的所有计时均用于运行算法10次(运行一次并放弃;启动计时器;运行10次;停止计时器)。这台机器是一个相当慢的核心2二重唱

方法1:自然法 首先要尝试的是显而易见的方法:

private long sumLineByLine() throws NumberFormatException, IOException {
    BufferedReader br = new BufferedReader(new FileReader(file));
    String line;
    long total = 0;
    while ((line = br.readLine()) != null) {
        int k = Integer.parseInt(line);
        total += k;
    }
    br.close();
    return total;
}
请注意,最大可能的返回值是10^17,这仍然很容易适应
长的
,因此我们不必担心溢出

在我的机器上,运行11次并打折第一次运行大约需要92.9秒

方法2:一个小的调整 受到对的一条评论的启发,我尝试不创建新的
intk
来存储解析行的结果,而是直接将解析后的值添加到
total
。因此:

    while ((line = br.readLine()) != null) {
        int k = Integer.parseInt(line);
        total += k;
    }
变成这样:

    while ((line = br.readLine()) != null)
        total += Integer.parseInt(line);
我确信这不会有任何区别,并且认为编译器很可能会为这两个版本生成相同的字节码。但是,令我惊讶的是,它确实缩短了一点时间:我们的时间降到了92.1秒

方法3:手动解析整数 到目前为止,让我感到困扰的是,我们将
字符串
转换为
int
,然后在末尾添加它。在我们前进的过程中添加,不是更快吗?如果我们自己解析
字符串
会发生什么?像这样的

private long sumLineByLineManualParse() throws NumberFormatException,
        IOException {
    BufferedReader br = new BufferedReader(new FileReader(file));
    String line;
    long total = 0;
    while ((line = br.readLine()) != null) {
        char chs[] = line.toCharArray();
        int mul = 1;
        for (int i = chs.length - 1; i >= 0; i--) {
            char c = chs[i];
            switch (c) {
            case '0':
                break;
            case '1':
                total += mul;
                break;
            case '2':
                total += (mul << 1);
                break;
            case '4':
                total += (mul << 2);
                break;
            case '8':
                total += (mul << 3);
                break;
            default:
                total += (mul*((byte) c - (byte) ('0')));   
            }
            mul*=10;
        }
    }
    br.close();
    return total;
}
运行时间为30.8秒!这比以前最好的速度提高了3倍

后续问题
  • 为什么这么快?我原以为它会赢,但没那么令人印象深刻。主要是转换成
    字符串的开销吗?还有所有关于角色设置之类的幕后担忧
  • 通过使用
    MappedByteBuffer
    来提供帮助,我们能做得更好吗?我有一种感觉,调用方法从缓冲区读取的开销会减慢速度,特别是从缓冲区向后读取时
  • 向前读取文件比向后读取文件更好,但仍然向后扫描缓冲区吗?这样做的想法是,您读取文件的第一块,然后向后扫描,但在最后丢弃半个数字。然后在读取下一个块时,设置偏移量,以便从丢弃的数字的开头开始读取
  • 有没有什么我没有想到的,可以产生重大影响
  • 更新:更令人惊讶的结果 首先是观察。我以前就应该想到这一点,但我认为基于
    String
    的读取效率低下的原因与其说是创建所有
    String
    对象所花费的时间太长,不如说是因为它们的寿命太短:我们有100000000个对象需要垃圾收集器处理。那肯定会使它心烦意乱

    现在,一些实验基于人们发布的答案/评论

    我在缓冲区的大小上作弊吗? 一个建议是,由于
    BufferedReader
    使用了16KB的默认缓冲区,而我使用了8MB的缓冲区,所以我不会比较like和like。如果使用更大的缓冲区,速度肯定会更快

    令人震惊的是。
    sumBinary()
    方法(方法4)昨天使用8MB缓冲区在30.8秒内运行。今天,代码没变,风向变了,我们现在是30.4秒。如果我将缓冲区大小降低到16KB,看看它会变慢多少,它会变快现在运行时间为23.7秒。疯子谁看见那个人来了

    一些实验表明16KB是最佳的。也许Java的人做了同样的实验,这就是为什么他们使用16KB

    问题是I/O绑定的吗? 我也在想这件事。在磁盘访问上花费了多少时间,在数字运算上花费了多少时间?如果它几乎是所有的磁盘访问,正如对其中一个建议答案的一个支持良好的评论所建议的那样,那么无论我们做什么,我们都无法做出多大的改进

    通过在注释掉所有解析和数字运算的情况下运行代码,这很容易进行测试,但读取内容仍然完整:

    private long sumBinary() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        int lastRead = (int) raf.length();
        byte buf[] = new byte[16 * 1024];
        int mul = 1;
        long total = 0;
        while (lastRead > 0) {
            int len = Math.min(buf.length, lastRead);
            raf.seek(lastRead - len);
            raf.readFully(buf, 0, len);
            lastRead -= len;
            /*for (int i = len - 1; i >= 0; i--) {
                if ((buf[i] >= 48) && (buf[i] <= 57)) {
                    total += mul * (buf[i] - 48);
                    mul *= 10;
                } else
                    mul = 1;
            }*/
        }
        raf.close();
        return total;
    }
    
    这将在20.0秒的时间内运行,比向后扫描版本快一段距离。很好

    乘法缓存 然而,我在晚上意识到,尽管我在每次迭代中执行两次乘法,但有可能使用缓存来存储这些乘法,这样我就可以避免在反向迭代中执行乘法。我很高兴地看到,当我醒来时,有人有同样的想法

    关键是,我们扫描的数字最多有10个数字,而且只有10个可能的数字,因此一个数字的值与累计总数的比值只有100个可能。我们可以预先计算这些,然后在反向扫描代码中使用它们。这应该比前向扫描版本要好,因为我们现在已经完全消除了乘法。(请注意,我们不能用正向扫描来实现这一点,因为乘法是累加器的乘法,累加器可以取10^9以内的任何值。只有在反向情况下,两个操作数都被限制在少数可能性内。)

    这似乎确实改善了一些情况:我们现在的时间是19.0秒。我们已经采取了行动
    private long sumBinary() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        int lastRead = (int) raf.length();
        byte buf[] = new byte[16 * 1024];
        int mul = 1;
        long total = 0;
        while (lastRead > 0) {
            int len = Math.min(buf.length, lastRead);
            raf.seek(lastRead - len);
            raf.readFully(buf, 0, len);
            lastRead -= len;
            /*for (int i = len - 1; i >= 0; i--) {
                if ((buf[i] >= 48) && (buf[i] <= 57)) {
                    total += mul * (buf[i] - 48);
                    mul *= 10;
                } else
                    mul = 1;
            }*/
        }
        raf.close();
        return total;
    }
    
    private long sumBinaryForward() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        int fileLength = (int) raf.length();
        byte buf[] = new byte[16 * 1024];
        int acc = 0;
        long total = 0;
        int read = 0;
        while (read < fileLength) {
            int len = Math.min(buf.length, fileLength - read);
            raf.readFully(buf, 0, len);
            read += len;
            for (int i = 0; i < len; i++) {
                if ((buf[i] >= 48) && (buf[i] <= 57))
                    acc = acc * 10 + buf[i] - 48;
                else {
                    total += acc;
                    acc = 0;
                }
            }
        }
        raf.close();
        return total;
    }
    
    private long sumBinaryCached() throws IOException {
        int mulCache[][] = new int[10][10];
        int coeff = 1;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++)
                mulCache[i][j] = coeff * j;
            coeff *= 10;
        }
    
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        int lastRead = (int) raf.length();
        byte buf[] = new byte[16 * 1024];
        int mul = 0;
        long total = 0;
        while (lastRead > 0) {
            int len = Math.min(buf.length, lastRead);
            raf.seek(lastRead - len);
            raf.readFully(buf, 0, len);
            lastRead -= len;
            for (int i = len - 1; i >= 0; i--) {
                if ((buf[i] >= 48) && (buf[i] <= 57))
                    total += mulCache[mul++][buf[i] - 48];
                else
                    mul = 0;
            }
        }
        raf.close();
        return total;
    }
    
    private long sumBinaryForwardMap() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        byte buf[] = new byte[16 * 1024];
        final FileChannel ch = raf.getChannel();
        int fileLength = (int) ch.size();
        final MappedByteBuffer mb = ch.map(FileChannel.MapMode.READ_ONLY, 0,
                fileLength);
        int acc = 0;
        long total = 0;
        while (mb.hasRemaining()) {
            int len = Math.min(mb.remaining(), buf.length);
            mb.get(buf, 0, len);
            for (int i = 0; i < len; i++)
                if ((buf[i] >= 48) && (buf[i] <= 57))
                    acc = acc * 10 + buf[i] - 48;
                else {
                    total += acc;
                    acc = 0;
                }
        }
        ch.close();
        raf.close();
        return total;
    }
    
    private class SumTaskResult {
        long subtotal;
        int leftPartial;
        int leftMulCount;
        int rightPartial;
    
        public void append(SumTaskResult rightward) {
            subtotal += rightward.subtotal + rightPartial
                    * rightward.leftMulCount + rightward.leftPartial;
            rightPartial = rightward.rightPartial;
        }
    }
    
    private class SumForkTask extends RecursiveTask<SumTaskResult> {
    
        private byte buf[];
        // startPos inclusive, endPos exclusive
        private int startPos;
        private int endPos;
    
        public SumForkTask(byte buf[], int startPos, int endPos) {
            this.buf = buf;
            this.startPos = startPos;
            this.endPos = endPos;
        }
    
        private SumTaskResult computeDirectly() {
            SumTaskResult result = new SumTaskResult();
            int pos = startPos;
    
            result.leftMulCount = 1;
    
            while ((buf[pos] >= 48) && (buf[pos] <= 57)) {
                result.leftPartial = result.leftPartial * 10 + buf[pos] - 48;
                result.leftMulCount *= 10;
                pos++;
            }
    
            int acc = 0;
            for (int i = pos; i < endPos; i++)
                if ((buf[i] >= 48) && (buf[i] <= 57))
                    acc = acc * 10 + buf[i] - 48;
                else {
                    result.subtotal += acc;
                    acc = 0;
                }
    
            result.rightPartial = acc;
            return result;
        }
    
        @Override
        protected SumTaskResult compute() {
            if (endPos - startPos < 64)
                return computeDirectly();
            int mid = (endPos + startPos) / 2;
            SumForkTask left = new SumForkTask(buf, startPos, mid);
            left.fork();
            SumForkTask right = new SumForkTask(buf, mid, endPos);
            SumTaskResult rRes = right.compute();
            SumTaskResult lRes = left.join();
            lRes.append(rRes);
            return lRes;
        }
    
    }
    
    private long sumBinaryForwardMapForked() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        ForkJoinPool pool = new ForkJoinPool();
    
        byte buf[] = new byte[1 * 1024 * 1024];
        final FileChannel ch = raf.getChannel();
        int fileLength = (int) ch.size();
        final MappedByteBuffer mb = ch.map(FileChannel.MapMode.READ_ONLY, 0,
                fileLength);
        SumTaskResult result = new SumTaskResult();
        while (mb.hasRemaining()) {
            int len = Math.min(mb.remaining(), buf.length);
            mb.get(buf, 0, len);
            SumForkTask task = new SumForkTask(buf, 0, len);
            result.append(pool.invoke(task));
        }
        ch.close();
        raf.close();
        pool.shutdown();
        return result.subtotal;
    }
    
    public static void genRandoms() {
        Random r = new Random();
        for (int i = 0; i < 100000000; i++)
            System.out.println(r.nextInt(1000000000));
    }
    
    BufferedReader br = new BufferedReader(new InputStreamReader(
            new FileInputStream(file), StandardCharsets.US_ASCII),
            1_024_000_000);
    
    // 4k buffer size.
    static final int SIZE = 4 * 1024;
    static byte[] buffer = new byte[SIZE];
    
    // Fastest because a FileInputStream has an associated channel.
    private static void ScanDataFile(Hunter p, FileInputStream f) throws FileNotFoundException, IOException {
        // Use a mapped and buffered stream for best speed.
        // See: http://nadeausoftware.com/articles/2008/02/java_tip_how_read_files_quickly
        final FileChannel ch = f.getChannel();
        long red = 0L;
        do {
            final long read = Math.min(Integer.MAX_VALUE, ch.size() - red);
            final MappedByteBuffer mb = ch.map(FileChannel.MapMode.READ_ONLY, red, read);
            int nGet;
            while (mb.hasRemaining() && p.ok()) {
                nGet = Math.min(mb.remaining(), SIZE);
                mb.get(buffer, 0, nGet);
                for (int i = 0; i < nGet && p.ok(); i++) {
                    p.check(buffer[i]);
                    //size += 1;
                }
            }
            red += read;
        } while (red < ch.size() && p.ok());
        // Finish off.
        p.close();
        ch.close();
        f.close();
    }
    
    class Summer {
    
        long sum = 0;
        long val = 0;
    
        public void add(byte b) {
            if (b >= '0' && b <= '9') {
                val = (val * 10) + (b - '0');
            } else {
                sum += val;
                val = 0;
            }
        }
    
        public long getSum() {
            return sum + val;
        }
    }
    
    private long sumMapped() throws IOException {
        Summer sum = new Summer();
        FileInputStream f = new FileInputStream(file);
        final FileChannel ch = f.getChannel();
        long red = 0L;
        do {
            final long read = Math.min(Integer.MAX_VALUE, ch.size() - red);
            final MappedByteBuffer mb = ch.map(FileChannel.MapMode.READ_ONLY, red, read);
            int nGet;
            while (mb.hasRemaining()) {
                nGet = Math.min(mb.remaining(), SIZE);
                mb.get(buffer, 0, nGet);
                for (int i = 0; i < nGet; i++) {
                    sum.add(buffer[i]);
                }
            }
            red += read;
        } while (red < ch.size());
        // Finish off.
        ch.close();
        f.close();
        return sum.getSum();
    }
    
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file), 8*1024*1024/2);
        long    total = 0;
        int i;
        while ((i = bis.read()) != -1)
        {
            byte    b = (byte)i;
            long    number = 0;
            while (b >= '0' && b <= '9')
            {
                number = number*10+b-'0';
                if ((i = bis.read()) == -1)
                    break;
                b = (byte)i;
            }
            total += number;
        }