在java中读取大数据文件时会产生巨大的内存开销

ct3nt3jp  于 2021-07-12  发布在  Java
关注(0)|答案(2)|浏览(394)

我正在做深度学习神经网络开发,使用mnist数据集进行测试。训练集由60000个序列组成,每个序列有784个双精度输入值。在java中将这些数据从文件读入数组的过程中,会产生大约4gb的内存开销,在整个程序运行过程中都会保持分配。这个开销是除了600007848=376mb之外的开销,该开销分配给双精度阵列本身。这种开销很可能是因为java除了存储数字数组外,还存储了文件的完整副本,但这可能是扫描器开销。
据一位消息人士称,将文件作为流读取可以避免将整个文件存储在内存中。但是,流读取仍然有这个问题。我将java 8与intellij 2016.2.4结合使用。这是流读取代码:

FileInputStream inputStream = null;
Scanner fileScan = null;
String line;
String[] numbersAsStrings;

totalTrainingSequenceArray = new double[60000][784];

try {
    inputStream = new FileInputStream(m_sequenceFile);
    fileScan = new Scanner(inputStream, "UTF-8");
    int sequenceNum = 0;
    line = fileScan.nextLine();//Read and discard the first line.
    while (fileScan.hasNextLine()) {
        line = fileScan.nextLine();
        numbersAsStrings = line.split("\\s+"); //Split the line into an array of strings using any whitespace delimiter.
        for (int inputPosition = 0; inputPosition < m_numInputs; inputPosition++) {
            totalTrainingSequenceArray[sequenceNum][inputPosition] = Double.parseDouble(numbersAsStrings[inputPosition]);
        }
        sequenceNum++;
    }
    if (fileScan.ioException() != null) {//Handle fileScan exception
        throw fileScan.ioException();
    }
} catch (IOException e) {//Handle the inputstream exception
    e.printStackTrace();
} finally {
    if (inputStream != null)  {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if (fileScan != null) {
        fileScan.close();
    }
}

在读取并调用system.gc()之后,我尝试将流和扫描程序设置为null,但这没有起到任何作用。这是扫描仪开销问题吗?要读取这个大数据文件而不产生巨大的永久开销,最简单的方法是什么?谢谢你的意见。

uyto3xhc

uyto3xhc1#

你的代码运行得很好。在一次完整的gc之后,实际上将使用380mb的堆。
java渴望分配内存以最小化gc开销,您可以使用 -Xmx512m 参数或使用不同的gc-例如。 -XX:+UseConcMarkSweepGC 或者 -XX:MaxHeapFreeRatio=40 .

c2e8gylq

c2e8gylq2#

定义“开销”。vm使用分配的堆来平衡垃圾收集时间和执行速度(您可以使用一些螺丝钉来影响其决策)。
通常情况下,vm会让堆填满,直到达到gc阈值,然后收集可以收集的任何垃圾,然后继续执行(这简化了很多)。这会导致堆使用的“锯齿”模式(逐渐填充,然后突然减少堆使用)。对于以一定速率产生垃圾的代码来说,这是完全正常的。
您可以影响的点是“齿”可以构建的高度(通过调整允许的堆和/或gc应该何时启动)。垃圾创建的速度(堆使用率上升的幅度)取决于执行的代码,其范围从零到可达到的最大分配率。
您的读取代码属于创建大量小垃圾对象的类型:来自扫描仪的行,您将行拆分为的部分。如果堆足够大,则可以读取整个文件而不收集任何垃圾(4gb堆设置很可能就是这种情况)。
如果您使堆变小,vm将更快地收集垃圾,从而减少内存使用(同样,您可以使用gc参数强制以较小的堆使用百分比进行收集)。
但是,期望代码只使用为数组计算的内存量运行是不合理的。您在任务管理器中看到的只是虚拟机使用的所有内存的累积。这包括堆栈、jre所需的任何资源、本机库和堆。
堆外的内存可能变化很大,这取决于程序使用的线程、文件和其他资源的数量。作为一个非常粗略的经验法则,jre本身至少使用20-50mb,即使只是运行一些简单的东西,比如“helloworld”。
无论您是调整堆大小还是微调gc参数,vm调优的问题在于,每当问题集发生更改时,都必须重新进行(例如,对于当前文件,您可能不需要使用-xmx512m,但是您需要调整下一个文件的值)。
或者,您可以尝试减少创建的垃圾量,理想情况下为零。代替扫描仪,逐行读取,您可以逐个字符读取,并使用状态机进行解析。这将大大减少垃圾的创建,但会使代码更加复杂。
在许多情况下,最“有效”的解决方案就是不用担心内存使用情况—通过集中精力改进程序,优化vm参数或代码所花的时间可能会更有效。只要“头顶”不妨碍你,何必费心呢?

相关问题