[google/guava]ImmutableTable.get 导致 copyOf 并调用实际的复制

2024-07-16 397 views
4

我遇到了一个奇怪的性能情况ImmutableTable

以下是正在发生的事情的堆栈跟踪(它是来自async-profiler的分析跟踪): 图像

基本上,似乎调用ImmutableTable.get()会导致ImmutableMap.copyOf()被调用,尽管有一条评论rowMap()copyOf仅是为了转换而调用(https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/SparseImmutableTable.java#L97),但实际上,确实存在通过 进行的实际复制toArray()

我相信这不应该发生,不是吗?

回答

1

记录显示,该ImmutableTable作品采用ImmutableTable.toImmutableTable

1

您好,我看了这个,我明白了为什么会出现这种情况get。基本上,这些类:

  1. 从 AbstractTable 继承;
  2. 它不会覆盖该V get方法;
  3. rowmap()实施过程中,使用copyOfImmutableMap.of

因此,基于这些标准,我可以找到那些具有在内部创建新闻地图的相同行为的类:

  • 稀疏不可变表
  • 单例不可变表
  • 密集不可变表

因此,尝试总结我的发现:在 AbstractTable 类中每次使用 rowMap()(+columnMap()) 方法都有可能创建额外的对象,而对于此类方法的实现来说,这些对象似乎没有必要,例如:containsRow, containsColumn, containsValue, contains, get对于 ImmutableTable 实现;

我最初的想法是,它的rowMapcolumnMap方法承担了以下责任:

  1. 提供外部数据(可以是可变或不可变数据)
  2. 在内部提供访问数据的支持。

也就是说,我可以看到这种可能的方法:有一种内部方法可以rowMap/columnMap从 ImmutableTable 类访问受保护的类范围,这样我们就可以通过这个方法实现这些方法,避免创建不必要的对象;

就像是

public abstract class ImmutableTable<R, C, V> extends AbstractTable<R, C, V> {
....
  protected abstract ImmutableMap<R, ImmutableMap<C, V>> immutableRowMap();

  @Override
  public boolean containsRow(@Nullable Object rowKey) {
    return Maps.safeContainsKey(immutableRowMap(), rowKey);
  }
...
}

final class DenseImmutableTable<R, C, V> extends RegularImmutableTable<R, C, V> {
  @Override
  protected ImmutableMap<R, ImmutableMap<C, V>> immutableRowMap() {
    return this.rowMap;
  }

想法@kluever / @ronshapiro?

3

我意识到ImmutableMap.copyOf如果出现以下情况,就不会创建新的对象:该地图是 ImmutableMap 并且不是 SortedMap 并且不是 PartialView;此外,如果它是 EnumMap,它将生成一个副本,并且对于其他地图类型也将生成一个副本;

public static <K, V> ImmutableMap<K, V> copyOf(Map<? extends K, ? extends V> map) {
    if ((map instanceof ImmutableMap) && !(map instanceof SortedMap)) {
      @SuppressWarnings("unchecked") // safe since map is not writable
      ImmutableMap<K, V> kvMap = (ImmutableMap<K, V>) map;
      if (!kvMap.isPartialView()) {
        return kvMap;
      }
    } else if (map instanceof EnumMap) {
      @SuppressWarnings("unchecked") // safe since map is not writable
      ImmutableMap<K, V> kvMap = (ImmutableMap<K, V>) copyOfEnumMap((EnumMap<?, ?>) map);
      return kvMap;
    }
    return copyOf(map.entrySet());
  }

https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableMap.java#L426-L439

2

最后,我无法使用常规构建器或不可变集合(ImmutableTable.toImmutableTable)重现我的假设。?对于这两种情况,当我们通过该方法命中copyOf方法时,此映射永远不会重新创建并返回自己的对象。我可能在这里遗漏了一些东西......欢迎任何帮助。?rowMap()get

8

这确实看起来很奇怪,它时不时就会出现在我的脑海里。

我所拥有的唯一甚至有点合理的理论是,它ImmutableTable是一个类加载器中的类型的实例,而调用ImmutableMap使用的是另一个类加载器中的类型。

[编辑:实际上,我不认为我会这样认为:对的两个引用ImmutableMap应该引用相同的类型,因为它们都是来自“ImmutableTable”的源引用。我认为?]

6

不管怎样,我相信如果我们看到多个类加载器之间存在这种交互,代码就会这样运行。但我完全不确定原始发帖者是否也存在这种交互。

如果您很好奇,绝对可以测试这样的事情,但这绝对很痛苦:)您可以在我们的测试中看到一些自定义类加载器的示例,例如在此测试中:https://github.com/google/guava/blob/081c486173032e6096c912e3c297c1d74eddcf93/guava-tests/test/com/google/common/util/concurrent/AbstractFutureInnocuousThreadTest.java#L48

2

@cpovirk 说得好...抱歉回复晚了:)

当我遇到这种情况时,我不认为它是在单独的类加载器中,但我同意,如果不是这种情况,这真的很神秘!