【问题标题】:Factory/Caching strategy for sharing large immutable objects共享大型不可变对象的工厂/缓存策略
【发布时间】:2013-08-05 14:23:40
【问题描述】:

我的问题很像之前的帖子Optimal HashSet Initialization (Scala | Java),我想使用HashSet 来加速(目前我正在使用Set)但是HashSet 没有表现出它的(恒定时间)优势。

对于提到的解决方案:

您可以通过实习来最大程度地降低 equals 的成本。这意味着你 通过工厂方法获取类的新对象,该方法 检查请求的新对象是否已经存在,如果存在, 返回对现有对象的引用。如果你断言每个 这种类型的对象是以这种方式构造的,你知道有 每个不同对象和 equals 的只有一个实例变为 相当于对象标识,这是一种廉价的参考比较 (Scala 中的 eq)。

但是,我不太确定检查的有效方法是什么

请求的新对象是否已经存在

对于大型对象(例如带有 hashmap 参数的案例类对象,一些其他对象结构...等)

通过比较这些复杂的字段中的每一个并不会给出太多的性能优势,不是吗?或者如果是,还有其他方法吗?

另外,我也很困惑如何制作

等于变成 相当于对象标识,这是一种廉价的参考比较 (Scala 中的 eq)。

在代码中。

我认为,上面提到的意图技术基本上是一种对象缓存。 因此,我参考了Caching strategy for small immutable objects in Java? 帖子中提到的技术。但是,我仍然看不到大型对象的有效方法是什么。

为方便起见,我引用了帖子中的缓存技术(Java 中),/// 表示我的想法和问题:

private static final int N_POINTS = 10191; 
private static final Point[] POINTS = new Point[N_POINTS];

public static Point of(int x, int y, int z) {
    int h = hash(x,y,z); ///  I can use hash code of each complicated field to construct the value
    int index = (h & 0x7fffffff) % N_POINTS;
    Point p = POINTS[index];
    if (p != null && p.x == x && p.y == y && p.z == z) /// Not working for large objects?
       return p;
    return POINTS[index] = new Point(x,y,z);
} 

总而言之,为大型对象实施高效缓存策略的最佳实践是什么,以便我可以在 Scala 中利用 HashSet

谢谢,

【问题讨论】:

  • 你想要缓存的东西是一种还是几种不同的类型,还是一个大的类层次结构?这样做的问题是您需要两个相等概念:一个(用于外部)使用 eq 和一个(用于初始查找/重复数据删除)使用“深度”相等。 Scala 案例类和集合将为您生成深度相等和哈希码,但这不是您在这种情况下所需要的。所以有效地做这样的事情需要相当多的手工工作。

标签: java scala


【解决方案1】:

实习的目标是使equals 方法能够使用引用相等来实现: this eq that(或 Java 中的 this == that)。 很明显,这个实现将具有最佳的运行时特性 比较某些字段集的传统equals

这种比较只有在只有一个实例时才有效 每个“唯一对象”由对象的某些字段集确定。

实习生-ing 仅在以下情况下有效 intern 操作的前期成本 可以通过调用equals(可能很多)的最小成本来完全抵消, 由HashMap驱动。

正如您所指出的,这个实习生可能需要一个潜在的昂贵的缓存机制: 存在运行时开销(执行检查) 和内存开销(缓存的大小)。

  • 最直接的缓存方式是使用HashMap 和传统的equalshashCode 应该是懒惰的;缓存它的结果,因此不需要重新计算。 可能需要考虑线程问题。

  • 实现这种缓存的一种方法是使用trie,可能在每个节点上使用哈希表实现,其中每个“级别”对应于对象中的一个字段(第一级 - 字段 1 , second level, field 2, etc...) 用于“用于建立唯一性的字段集”。

还有其他可行的方法来实现这样的缓存。 请原谅我避免对此进行任何进一步的讨论, 并请允许我介绍避免处理该问题的方法。

选项 1:不缓存

声明:您可能会通过以下方式获得足够有效的结果 使用快速哈希(并在内部缓存它), equals 的“传统”实现, 并以HashMapHashSet 开头sufficient minimal size

理想情况下,哈希表中的冲突很少, 并且对equals 的调用次数最少。

选项 2:将多个字段映射到一个唯一的 String

[此方法假定“唯一定义对象的字段”是不可变的。如果这不是真的,可以进行适当的调整来弥补。]

构建并缓存与唯一对象实例对应的private unique: String。 例如,对于一些简单的对象,以下内容可能是唯一的:

“用于建立唯一性的字段集”的字符串值的串联,用逗号分隔。

了解您的对象/字段特征将有助于确定 如何创建这样一个唯一的字符串。

有了这样的值,我们可以避免单独的intern/caching机制 并通过同时实施equalshashCode 来保留大部分好处 就这个unique 字符串而言:

def equals(thatObj: Any) = thatObj match {
    case that : MyType => unique.equals(that.unique)
    case _             => false
  }

def hashCode() = unique.hashCode

备选方案 2:

[编辑:Rüdiger Klaehn 提供 this link which offers compelling evidence to avoid String.intern()]

使用String.intern 并调整equals 以利用它:

private val unique = buildUnique().intern

def equals(thatObj: Any) = thatObj match {
    case that : MyType => unique.eq(that.unique) // In Java: unique == that.unique
    case _             => false
  }

【讨论】:

  • 在 Scala 中 == 遵循 equals;只有eq 是引用相等
  • @LuigiPlinge Doh! TY 抓住了这一点。
  • 不要使用 String.intern。与使用弱哈希图的手动解决方案相比,它的性能可怕。请参阅stackoverflow.com/questions/10624232/… 另外,我认为将对象序列化为字符串只是为了加快 equals 的速度,对于大型对象来说会产生很大的性能和内存开销。
  • @RüdigerKlaehn TY 分享了该链接,这对我来说是新的并且很感兴趣。我不同意你的评估。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-14
  • 2010-10-06
相关资源
最近更新 更多