【问题标题】:Why is it that, the more '1' bits in my Key, the longer it takes to place in the HashMap?为什么我的 Key 中的“1”位越多,放置在 HashMap 中的时间就越长?
【发布时间】:2012-02-16 05:18:52
【问题描述】:

我正在为一个课程做一个项目,该课程专注于在内存中存储一​​个大部分为 0 值的巨大矩阵,并对其执行一些矩阵数学运算。我的第一个想法是使用HashMap来存储矩阵元素,并且只存储非零元素,以避免使用大量内存。

我想为HashMap 创建一个键,它表示元素的行号和列号,当我访问地图中的那个条目时,我可以重新提取这两个值。我不知道 Java 和 C#——在 C# 中我会用 RowColumn 成员创建一个 struct,但在 Java 中我很快意识到没有用户值类型。随着最后期限的临近,我做了一个安全的赌注,并把Key 做得很长。我使用一些非常简单的位移将行数据(32 位 int)存储在前 32 位中,将列数据存储在后 32 位中。 [编辑:我还想指出,我的 HashMap 是用一个特定的初始大小初始化的,它完全代表我存储在其中的值的数量,这个数量永远不会超过。]

[旁注:我希望能够再次提取行/列数据的原因是为了大大提高矩阵乘法的效率,从O(n^2)O(n),以及一个更小的n来引导]

实现此结构后我注意到的是,从仅给出非零元素的文本文件中读取 23426 x 23426 矩阵需要 7 秒,但计算特征值只需要 2 秒我们必须给予!在对方法进行选择性注释后,我得出结论,这 7 秒时间跨度的大部分时间都用于将我的值存储在 HashMap 中。

public void Set(double value, int row, int column) {
    //assemble the long key, placing row and column in adjacent sets of bits
    long key = (long)row << SIZE_BIT_MAX; //(SIZE_BIT_MAX is 32)
    key += column;
    elements.put(key, value);
}

这是设置值的代码。如果我改用这种方法:

public void Set(double value, int row, int column) {
    //create a distinct but smaller key (around 32 bits max)
    long key = (long)(row * matrixSize) + column;
    elements.put(key, value);
}

读取仅需 2 秒。这两个版本的密钥对于每个元素都是不同的,都是长类型,并且创建它们的实际代码的复杂性最小。是 elements.put(key, value) 造成了 7 秒和 2 秒之间的差异。

我的问题是,为什么?我在这些关键版本之间看到的区别在于,第一个版本的位始终设置为 1,并且更频繁,而第二个版本的所有最高 32 位都设置为 0。我是在追逐红鲱鱼,还是这个相当显着的差异在性能上是 HashMap.put 方法内部的结果?

【问题讨论】:

  • 没有SSCCE,很难告诉你原因。我的猜测是您没有指定地图的初始大小。然后它开始时非常小,并且必须经常调整自己的大小。调整大小,尤其是大地图的调整成本非常高。
  • 初始大小已指定且永不超过。我将编辑我的帖子以反映这一点。
  • 可能会有小改进,但创建具有适当数量的初始元素的 HashMap 以避免在达到新容量时不断重新散列。例如 new HashMap(20000);
  • 另一个问题是,教授评估的是什么?只是你能不能做到?是否可以实现高效的稀疏矩阵算法?如果是后者,HashMap 可能会给你一个“C”。
  • 查看我的评论和编辑;我在项目中很快就意识到了这一点。

标签: java performance hashmap bit sparse-matrix


【解决方案1】:

看看Long如何实现hashCode()方法(至少在OpenJDK 7中):

public int hashCode() {
    return (int)(value ^ (value >>> 32));
}

这意味着您的密钥被塞回 32 位;所有低位经常相互抵消,导致大量冲突,这需要HashMap 花费额外的时间在桶中寻找空闲槽。您的第二种方法避免了这个问题,因为每个键生成的哈希码都是唯一值(因为您只有 23426 x 23426 = 548777476 个非常适合 32 位的项目)。

所以,原因是您的密钥选择,而不是设置的位数。

但是,“用户价值类型”到底是什么意思?

public class MatrixKey {
    private final int row;
    private final int column;
    public MatrixKey(int row, int column) {
        this.row = row;
        this.column = column;
    }
    public int getRow() { return row; }
    public int getColumn() { return column; }
}

一旦你实现了hashCode()equals(),这个类就可以成为Java 中Map 的完美键。只要确保你没有像Long 那样实现它的hashCode 方法。 :)

【讨论】:

  • +1,但要将其用作映射键,您应该实现 hashcode 和 equals。否则,您将无法从地图中检索任何内容...
  • 我想我只是不太了解 Java,但我不知道像 C# 中的 Struct 这样的值类型,它使用按位相等而不是引用相等或定义的哈希。我使用 Integer 或 Long 作为密钥的主要动力是利用 Java 预先实现的唯一哈希而不是自己编写哈希,因为我基本上对此很烂,我不想浪费时间在项目上弄清楚它.
  • @jackrabbit:“否则,您将无法从地图中检索任何内容”是什么意思?同意强烈推荐实现hashCodeequals,但MatrixKey 如果没有定义这些行为,则应使用Object 类实现进行查找?
  • Java 不实现唯一哈希。事实上,“唯一哈希”是一个矛盾的说法 - 用语自相矛盾。
  • 我的意思是不同对象的不同哈希。或者,我真正的意思是,当我编写散列时,对于不相等的对象,它们往往最终是相同的。诚然,我已经有一段时间没有尝试编写散列函数了,但我不想在截止日期前再次尝试。
【解决方案2】:

来自JDK 6 documentation for Long.hashCode()(请注意,您的long 原语被自动装箱到Long 对象中——而在C# 中,原语实际上是 对象):

返回此 Long 的哈希码。结果是此 Long 对象持有的原始 long 值的两半的异或。也就是hashcode就是表达式的值:

(int)(this.longValue()^(this.longValue()>>>32))

我认为根据这个定义,这就解释了原因:

当您引入更多熵时,碰撞率会降低,从而通过long 值的上半部分将其分散得更多。编辑:我阅读了命令错了,所以这是下面的反驳)

当扩展到long 范围时,冲突可能更有可能发生——毕竟,在Java 中,hashCode 的大小只有int,所以你只能有有限数量的均等分布。如果您知道它“均匀”分布在 int 范围内,那么您的冲突就会减少。如果将其分散到long 范围内,则会大大增加发生碰撞的机会。

这是from the HashMap Java documentation(强调我的):

此实现为基本操作(get 和 put)提供恒定时间性能,假设哈希函数将元素正确地分散在桶中

附注:通过调整 initial capacityload factor,您会发现更大的性能提升 - 请查看 HashMap 文档了解更多信息。

【讨论】:

  • 我认为 OP 正在观察完全相反的情况。当上半部分全为零时,速度更快。
  • 哦,哎呀,好消息。我将用不同的方法编辑我的回复。
  • 看起来 Bombe 在你编辑的时候偷了你这个!哦,好吧,它们都很好地解释了 Java 中 Long 和 HashMap 的一些内部工作原理。感谢您的回复!我会把你们俩都标记为正确的,但这是不允许的……
  • 好吧,我一开始就完全相反,所以他应该得到分数:)
  • “如果你知道它“均匀”分布在 int 范围内,那么它肯定不会发生冲突”——抱歉,这是完全错误的。碰撞只会被最小化;您可以很好地获得地图中只有两个元素的哈希码冲突。
【解决方案3】:

根据实现,您可能会遇到哈希冲突。

如果您的所有哈希值最终都在同一个“桶”中,则实现通常会将它们扔到某种类型的列表中。如果是这种情况,您的访问时间将受到严重影响。

【讨论】:

  • 访问时间似乎并没有什么不同,不过,除非您正在讨论在插入新值以检查相等性时访问地图中的现有值。
猜你喜欢
  • 2012-03-25
  • 2022-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-28
  • 2022-11-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多