【问题标题】:How to reduce memory usage for a HashMap<String, Integer> like data structure如何减少 HashMap<String, Integer> 类数据结构的内存使用量
【发布时间】:2017-09-10 06:51:12
【问题描述】:

在开始解释我的问题之前,我应该提一下,我并不是在寻找增加 Java 堆内存的方法。我应该严格存储这些对象。

我正在努力将大量(5-10 GB)的 DNA 序列及其计数(整数)存储在哈希表中。 DNA 序列(长度为 32 或更少)由“A”、“C”、“G”、“T”和“N”(未定义)字符组成。众所周知,在内存中存储大量对象时,与 C 和 C++ 等低级语言相比,Java 的空间效率较差。因此,如果我将此序列存储为字符串(它为长度约为 30 的序列保存大约 100 MB 内存),我会看到错误。

我试图将核酸表示为 'A'=00, 'C'=01, 'G'=10, 'T'=11 并忽略 'N'(因为它破坏了 char 到 2 位的转换为第五酸)。然后,将这些 2 位酸连接成字节数组。它带来了一些改进,但不幸的是,几个小时后我又看到了错误。我需要一个方便的解决方案或至少一个解决方法来处理此错误。提前谢谢你。

【问题讨论】:

  • 你需要增加 Java 内存。你给 JVM 多少钱?
  • 你能把你的问题分成可以顺序解决的等价子问题吗?即如果我需要对 1000 个数字求和,我可以将这个问题分成 10 个子问题,每个子问题由 100 个数字相加,然后对部分结果求和
  • @stdunbar 大约 2 GB。正如我所说,我应该继续使用 JVM 的默认设置。最有可能的是,如果我为 JVM 分配 10 GB,程序将运行而不会出错。
  • @FedericoPeraltaSchaffner 如果任务是找到最频繁的 N 个子序列及其频率怎么办?是的,我可以将问题拆分为 B 个子问题。然后,对于每个子问题,我可以选择 K 个最频繁的子序列。最后,我合并所有 (B*K) 子序列并将非唯一子序列分组以“近似”找到它们的频率。可以从这个近似列表中选择最频繁的 N 个后续,但我需要准确的结果。
  • 好的,如果您需要精确的结果并且您的问题不能被拆分为返回精确结果的子问题,并且如果您需要使用的数据结构不适合可用内存,那么您需要开始考虑使用数据库。

标签: java performance hashmap


【解决方案1】:

相当复杂,也许这是一个奇怪的想法,并且需要大量的工作,但这是我会尝试的:

您已经指出了整个任务的两个单独的子问题:

  • 默认的 HashMap 实现对于如此大的集合大小可能不是最佳的
  • 您需要存储字符串以外的其他内容

地图实现

我建议为Map&lt;String, Long&gt; 接口编写一个高度定制的哈希映射实现。在内部,您不必存储字符串。不幸的是 5^32 > 2^64,所以没有办法将你的整个字符串打包成一个长字符串,好吧,让我们坚持使用两个长字符串作为一个键。在为地图实现提供字符串键(使用位移等)时,您可以在运行中相当有效地进行字符串到/回长 [2] 转换。

至于打包值,这里有一些注意事项:

对于一个键值对,一个标准的 hashmap 需要有一个包含 N 个 long 的 buckets 数组,其中 N 是当前容量,当从 hash key 中找到 bucket 时,它需要一个 key 的链表-value 对来解析产生相同哈希码的键。对于您的具体情况,您可以尝试通过以下方式对其进行优化:

  • 使用大小为 3N 的 long[],其中 N 是在连续数组中存储键和值的容量
  • 在此数组中,在位置3 * (hashcode % N)3 * (hashcode % N) + 1 存储键的长[2] 表示,与此存储桶匹配的第一个键或唯一一个(在插入,否则为零),在位置 3 * (hashcode % N) + 2 存储相应的计数
  • 对于所有那些不同的密钥导致相同的哈希码和相同的存储桶的情况,您将数据存储在标准HashMap&lt;Long2KeyWrapper, Long&gt; 中。这个想法是保持上面提到的数组的容量(并相应地调整大小)足够大,以便在该连续数组中而不是在回退哈希映射中拥有迄今为止最大的数据部分。这将大大减少 hashmap 的存储开销
  • 不要在 N=2N 迭代中扩展容量,使增长步骤更小,例如10-20%。这会降低填充地图的性能,但会控制您的内存占用

钥匙

鉴于不等式5^32 &gt; 2^64,您使用位来编码 5 个字母的想法似乎是我现在能想到的最好的方法。使用 3 位和相应的 long[2]。

【讨论】:

    【解决方案2】:

    我建议您查看 Trove4j Collections API;它提供了包含原语的集合,这些原语将比它们的盒装包装类使用更少的内存。

    具体来说,你应该看看他们的TObjectIntHashMap

    另外,我不建议在 JDK 9 发布之前将任何内容存储为 Stringchar,因为 String 的支持 char 数组是 UTF-16 编码的,使用两个 bytechar。 JDK 9 默认为 UTF-8,其中仅使用一个 byte

    【讨论】:

    • 问题是长度为32的5-酸DNA序列如何编码为原始类型。考虑到 long,它是一个 64 位整数,但有 5^32 个可能的序列。此外,即使将char序列转换为字节数组,额外的结构和操作,如toCharArray()、concat()等,也可能会干扰Java堆并导致同样的错误。
    【解决方案3】:

    如果您正在使用大约 10gb 的数据,或者至少是内存中表示大小约为 10gb 的数据,那么您可能需要想办法将您不需要的数据写入时刻到磁盘并将数据集的各个部分加载到内存中以对其进行处理。

    几年前,当我使用蒙特卡罗模拟进行研究时,我遇到了这个确切的问题,所以我写了一个Java data structure 来解决它。您可以在此处克隆/分叉源代码:github.com/tylerparsons/surfdep

    该库支持 MySQL 和 SQLite 作为底层数据库。如果您都没有,我会推荐 SQLite,因为它设置起来要快得多。

    完全免责声明:这不是最有效的实现,但如果您让它运行几个小时,它将处理非常大的数据集。我在我的 Windows 笔记本电脑上成功地使用了多达 10 亿个元素的矩阵对其进行了测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多