处理Java/Clojure中的大型数据集:littleBig数据

Isa*_*aac 10 java memory performance clojure

我一直使用Clojure 开发一个图形/数据处理应用程序(你可以看到这里的截图)(虽然,通常情况下,感觉我使用的是比Clojure更多的Java),并且已经开始用更大的数据集测试我的应用程序.我有大约100k点没有问题,但是当我开始变得更高时,我遇到了堆空间问题.

现在,从理论上讲,大约半个GB应该足以容纳大约7000万双打.当然,我做了许多需要一些开销的事情,事实上我可能同时在内存中保存了2-3份数据,但我还没有进行太多优化,而500k左右仍是订单比我应该能够加载的量级小.


我知道Java对堆的大小有人为限制(可以更改),我理解这些可以部分地用JVM启动时可以指定的选项进行更改.这引出了我的第一个问题:

  • 如果我在启动时使用JVM启用的Swank-Clojure(通过Leiningen),我可以更改允许的最大堆空间吗?

  • 如果我将此应用程序(我计划)打包为Uberjar,我是否能够确保我的JVM具有某种最小堆空间?

但我不满足于只依靠JVM的堆来为我的应用程序提供动力.我不知道我最终可能使用的数据的大小,但它可能达到数百万点,也许堆不能适应.因此,我很有兴趣找到只是打包数据的替代方案.以下是我的一些想法,以及有关它们的问题:

  • 是否有可能一次仅读取大(文本)文件的一部分,因此我可以以"块"(例如,一次n一行)导入和处理数据?如果是这样,怎么样?

  • 是否有一些更快的方式来访问我正在读取的文件(可能很快,取决于实现),而不是简单地一次读取它?如果你做过类似的事情,我想我在这里要求提供过去适合你的任何提示/黑客.

  • 我可以从文件中"采样"; 例如,只读取每一z行,有效地下采样我的数据?

现在我计划,如果有上面的答案(我将继续搜索!),或者提供的见解导致等效的解决方案,一次读入一大块数据,将其绘制到时间线(请参见屏幕截图) - 时间线为绿色),并允许用户与该位进行交互,直到她点击next chunk(或某事),然后我保存对文件所做的更改并加载下一个"数据块"的数据并显示它.

或者,我会显示所有数据的整个时间轴(下采样,因此我可以加载它),但只允许在主窗口中一次访问它的一个"块"(在绿色上方查看的部分)时间轴,如时间轴中的视口矩形所示.


最重要的是,虽然,有没有更好的办法?请注意,我无法对主窗口的数据进行下采样,因为我需要能够处理它并让用户与其进行交互(例如,单击一个点或接近一个点以向该点添加"标记":该标记被绘制为关于那一点的垂直规则).

我很感激任何见解,答案,建议或更正!我也愿意以你想要的任何方式阐述我的问题.

希望这至少部分是开源的; 我想要一个简单易用但快速的方法来在Clojure世界中制作大量数据的xy图.


编辑下采样只有在绘图时才有可能,而不是总是如此,具体取决于绘制的部分.我需要访问所有数据来执行分析.(只是清理它!)虽然我一定要考虑下采样,但我认为这至少不会解决我的内存问题,因为我所做的只是在绘制BufferedImage.

j-g*_*tus 7

如果我在启动时使用JVM启用的Swank-Clojure(通过Leiningen),我可以更改允许的最大堆空间吗?

您可以通过在启动时提供-Xms(最小堆)和-Xmx(最大堆)选项来更改Java堆大小,请参阅文档.

所以类似的东西java -Xms256m -Xmx1024m ...会给256MB的初始堆提供增长到1GB的选项.

我不使用Leiningen/Swank,但我希望可以改变它.如果不出意外,应该有一个适用于Java的启动脚本,您可以在其中更改参数.

如果我将此应用程序(我计划)打包为Uberjar,我是否能够确保我的JVM具有某种最小堆空间?

内存不是从jar文件中控制,而是从启动脚本控制,通常是调用java并提供参数的.sh或.bat文件.

我可以从文件中"采样"; 例如,只读每一行z?

java.io.RandomAccessFile通过字节索引提供随机文件访问,您可以构建它以对内容进行采样.

是否有可能一次仅读取大(文本)文件的部分内容,因此我可以以"块"的形式导入和处理数据,例如,一次读取n行?如果是这样,怎么样?

line-seq返回文件中每行的延迟序列,因此您可以根据需要一次处理.

或者,在java.io中使用Java机制- BufferedReader.readLine()FileInputStream.read(byte[] buffer)

是否有一些更快的方式来访问我正在读取的文件(可能很快,取决于实现),而不是简单地一次读取它?

在Java/Clojure中存在的BufferedReader,或者您也可以维护自己的字节缓冲区和一次读取较大的块.

为了最大限度地利用你的记忆,保持数据的原始越好.

对于一些实际的数字,让我们假设你要绘制一个音乐CD的内容:

  • CD有两个通道,每个通道每秒有44,100个样本
    • 60分钟 那么音乐就是大约3亿个数据点
  • 每个数据点表示为16位(2字节,短):600MB
  • 表示为原始int数组(每个数据点4个字节):1.2GB
  • 表示为整数数组(每个数据点32个字节):10GB

从使用数字此博客为对象大小(每个对象的16字节的开销,4个字节用于原始int,则阵列中的对准,以8字节边界,8字节的指针的对象=每整数数据点32个字节).

甚至600MB的数据也可以在"普通"计算机上同时保存在内存中,因为您可能也会在其他地方使用大量内存.但是从原始数字到盒装数字的转换本身都会减少你在内存中保存数据点的数量级.

如果您要在1900像素宽的"概览"时间线上绘制60分钟CD中的数据,那么您将有一个像素显示两秒音乐(约180,000个数据点).这显然太少,无法显示任何详细程度,您可能需要某种形式的子采样或汇总数据.

因此,您描述的解决方案 - 一次处理完整数据集一个块以在"概览"时间轴中进行摘要显示,并且仅在内存中保留主"详细信息"窗口的小子集 - 听起来非常合理.

更新:

关于快速文件读取:本文以13种不同方式读取100MB文件的文件读取速度 - 结果从0.5秒到10分钟(!)不等.通常,读取速度很快,缓冲区大小合适(4k到8k字节),一次读取一个字节时读取速度非常慢.

如果有人感兴趣的话,该文章还与C进行了比较.(Spoiler:最快的Java读取在C中的内存映射文件的2倍范围内.)