当前位置: 首页 > 知识库问答 >
问题:

多键映射-性能比较

秦宁
2023-03-14

我们的应用程序将大量数据存储在内存中,存储在多种不同类型的地图中,以便快速查找。为了保持简单(不考虑原始贴图),它始终是一个带有一个或多个键的贴图。性能对我们来说是一个很大的要求。

我想找到性能最好的地图实现,正如这里建议的那样,我比较了这些实现:

>

Map<K1, Map<K2, Map<K3, V>>>

java中的包装键(元组作为键)。util。哈希图

Map<Triple<K1, K2, K3>, V>

元组作为网络中的键。openhft。科洛博克。收集地图搞砸HashObjObjMap,根据这一点,它应该是最快的地图之一。

HashObjObjMap<Triple<K1, K2, K3>, V>
  1. 嵌套地图的GET速度最快,PUT速度最慢
  2. Koloboke哈希映射将比jdk哈希映射更快
Benchmark                                                Mode  Cnt   Score   Error  Units
TupleVsNestedMapsBenchmark.benchGetFromNestedMap         avgt   20  11.586 ± 0.205  ns/op
TupleVsNestedMapsBenchmark.benchGetFromTupleKolobokeMap  avgt   20  18.619 ± 0.113  ns/op
TupleVsNestedMapsBenchmark.benchGetFromTupleMap          avgt   20   8.985 ± 0.085  ns/op
TupleVsNestedMapsBenchmark.benchPutToNestedMap           avgt   20  15.106 ± 0.142  ns/op
TupleVsNestedMapsBenchmark.benchPutToTupleKolobokeMap    avgt   20  22.533 ± 0.335  ns/op
TupleVsNestedMapsBenchmark.benchPutToTupleMap            avgt   20   8.884 ± 0.084  ns/op
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(100000)
@Fork(1)
@Warmup(iterations = 10)
@Measurement(iterations = 20)
public class TupleVsNestedMapsBenchmark {

public static final int N = 10000;

static ObjObjObjObjHashMap<String, String, String, Integer> sourceNestedMap = new ObjObjObjObjHashMap<>();
static Map<Triple<String, String, String>, Integer> sourceTupleMap = new HashMap<>();
static HashObjObjMap<Triple<String, String, String>, Integer> sourceTupleKMap = HashObjObjMaps.newMutableMap();

static {
    for (int i = 0; i < N; i++) {
        sourceNestedMap.put("a-" + i, "b-" + i, "c-" + i, i);
        sourceTupleMap.put(ImmutableTriple.of("a-" + i, "b-" + i, "c-" + i), i);
        sourceTupleKMap.put(ImmutableTriple.of("a-" + i, "b-" + i, "c-" + i), i);
    }
}

@Benchmark
public List<Integer> benchGetFromNestedMap() {
    return benchmarkGet(sourceNestedMap::get);
}

@Benchmark
public List<Integer> benchGetFromTupleMap() {
    return benchmarkGet(((key1, key2, key3) -> sourceTupleMap.get(ImmutableTriple.of(key1, key2, key3))));
}

@Benchmark
public List<Integer> benchGetFromTupleKolobokeMap() {
    return benchmarkGet(((key1, key2, key3) -> sourceTupleKMap.get(ImmutableTriple.of(key1, key2, key3))));
}

@Benchmark
public ObjObjObjObjHashMap<String, String, String, Integer> benchPutToNestedMap() {
    ObjObjObjObjHashMap<String, String, String, Integer> map = new ObjObjObjObjHashMap<>();

    benchmarkPut(map::put);

    return map;
}

@Benchmark
public Map<Triple<String, String, String>, Integer> benchPutToTupleMap() {
    Map<Triple<String, String, String>, Integer> map = new HashMap<>();

    benchmarkPut((key1, key2, key3, value) -> map.put(ImmutableTriple.of(key1, key2, key3), value));

    return map;
}

@Benchmark
public Map<Triple<String, String, String>, Integer> benchPutToTupleKolobokeMap() {
    HashObjObjMap<Triple<String, String, String>, Integer> map = HashObjObjMaps.newMutableMap();

    benchmarkPut((key1, key2, key3, value) -> map.put(ImmutableTriple.of(key1, key2, key3), value));

    return map;
}

private List<Integer> benchmarkGet(MapValueSupplier<Integer> mapValueSupplier) {
    List<Integer> result = new ArrayList<>(N);
    for (int i = 0; i < N; i++) {
        result.add(mapValueSupplier.supply("a-" + i, "b-" + i, "c-" + i));

    }
    return result;
}

private void benchmarkPut(PutValueFunction<Integer> putValueFunction) {
    for (int i = 0; i < N; i++) {
        putValueFunction.apply("a-" + i, "b-" + i, "c-" + i, i);
    }
}

private interface MapValueSupplier<T> {

    T supply(String key1, String key2, String key3);
}

private interface PutValueFunction<T> {

    void apply(String key1, String key2, String key3, T value);
}
}

注意:请不要建议使用原始地图。整数as(value)只是廉价对象的一个例子。

  1. 为什么koloboke地图比jdk地图慢2.5倍?
  2. 为什么嵌套映射没有更快?(我预计元组键对象的分配开销会更大。)
  3. 或者我的基准是错的?那么,我该如何改进呢?

基于@leventov的好建议,我更改了Benchmark并尝试了缓存哈希码(并且具有更好的分发)的Triple实现-测试被命名为Tuple2。

@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(TupleVsNestedMapsBenchmark.TOTAL_OPS)
@Fork(1)
@Warmup(iterations = 5)
@Measurement(iterations = 20)
public class TupleVsNestedMapsBenchmark {

static final int N = 30;
static final int TOTAL_OPS = N * N * N;

private ObjObjObjObjHashMap<String, String, String, Integer> sourceNestedMap;
private Map<Triple<String, String, String>, Integer> sourceTupleMap;
private HashObjObjMap<Triple<String, String, String>, Integer> sourceTupleKMap;
private Map<Triple<String, String, String>, Integer> sourceTuple2Map;
private HashObjObjMap<Triple<String, String, String>, Integer> sourceTuple2KMap;
private String[] keys;

@Setup
public void init() {
    sourceNestedMap = new ObjObjObjObjHashMap<>();
    sourceTupleMap = new HashMap<>(TOTAL_OPS);
    sourceTupleKMap = HashObjObjMaps.newMutableMap(TOTAL_OPS);
    sourceTuple2Map = new HashMap<>(TOTAL_OPS);
    sourceTuple2KMap = HashObjObjMaps.newMutableMap(TOTAL_OPS);
    keys = new String[N];
    for (int i = 0; i < N; i++) {
        keys[i] = "k" + i;
    }
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            for (int k = 0; k < N; k++) {
                sourceNestedMap.put(keys[i], keys[j], keys[k], i);
                sourceTupleMap.put(ImmutableTriple.of(keys[i], keys[j], keys[k]), i); 
                sourceTupleKMap.put(ImmutableTriple.of(keys[i], keys[j], keys[k]), i); 
                sourceTuple2Map.put(ImmutableTriple2.of(keys[i], keys[j], keys[k]), i);
                sourceTuple2KMap.put(ImmutableTriple2.of(keys[i], keys[j], keys[k]), i);
            }
        }
    }
}

@Benchmark
public List<Integer> benchGetFromNestedMap() {
    return benchmarkGet(sourceNestedMap::get);
}

@Benchmark
public List<Integer> benchGetFromTupleMap() {
    return benchmarkGet(((key1, key2, key3) -> sourceTupleMap.get(ImmutableTriple.of(key1, key2, key3))));
}

@Benchmark
public List<Integer> benchGetFromTupleKolobokeMap() {
    return benchmarkGet(((key1, key2, key3) -> sourceTupleKMap.get(ImmutableTriple.of(key1, key2, key3))));
}

@Benchmark
public List<Integer> benchGetFromTuple2Map() {
    return benchmarkGet(((key1, key2, key3) -> sourceTuple2Map.get(ImmutableTriple2.of(key1, key2, key3))));
}

@Benchmark
public List<Integer> benchGetFromTuple2KolobokeMap() {
    return benchmarkGet(((key1, key2, key3) -> sourceTuple2KMap.get(ImmutableTriple2.of(key1, key2, key3))));
}

@Benchmark
public ObjObjObjObjHashMap<String, String, String, Integer> benchPutToNestedMap() {
    ObjObjObjObjHashMap<String, String, String, Integer> map = new ObjObjObjObjHashMap<>();
    benchmarkPut(map::put);
    return map;
}

@Benchmark
public Map<Triple<String, String, String>, Integer> benchPutToTupleMap() {
    Map<Triple<String, String, String>, Integer> map = new HashMap<>();
    benchmarkPut((key1, key2, key3, value) -> map.put(ImmutableTriple.of(key1, key2, key3), value));
    return map;
}

@Benchmark
public Map<Triple<String, String, String>, Integer> benchPutToTupleKolobokeMap() {
    HashObjObjMap<Triple<String, String, String>, Integer> map = HashObjObjMaps.newMutableMap();
    benchmarkPut((key1, key2, key3, value) -> map.put(ImmutableTriple.of(key1, key2, key3), value));
    return map;
}

@Benchmark
public Map<Triple<String, String, String>, Integer> benchPutToTuple2Map() {
    Map<Triple<String, String, String>, Integer> map = new HashMap<>();
    benchmarkPut((key1, key2, key3, value) -> map.put(ImmutableTriple2.of(key1, key2, key3), value));
    return map;
}

@Benchmark
public Map<Triple<String, String, String>, Integer> benchPutToTuple2KolobokeMap() {
    HashObjObjMap<Triple<String, String, String>, Integer> map = HashObjObjMaps.newMutableMap();
    benchmarkPut((key1, key2, key3, value) -> map.put(ImmutableTriple2.of(key1, key2, key3), value));
    return map;
}

private List<Integer> benchmarkGet(MapValueSupplier<Integer> mapValueSupplier) {
    List<Integer> result = new ArrayList<>(TOTAL_OPS);
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            for (int k = 0; k < N; k++) {
                Integer value = mapValueSupplier.supply(keys[i], keys[j], keys[k]);
                result.add(value);
            }
        }
    }
    return result;
}

private void benchmarkPut(PutValueFunction<Integer> putValueFunction) {
    Integer value = 1;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            for (int k = 0; k < N; k++) {
                putValueFunction.apply(keys[i], keys[j], keys[k], value);
            }
        }
    }
}

private interface MapValueSupplier<T> {

    T supply(String key1, String key2, String key3);
}

private interface PutValueFunction<T> {

    void apply(String key1, String key2, String key3, T value);
}
}

结果如下:

Benchmark                                                 Mode  Cnt      Score      Error  Units
TupleVsNestedMapsBenchmark.benchGetFromNestedMap          avgt   20     24.524 ±    0.144  ns/op
TupleVsNestedMapsBenchmark.benchGetFromTuple2KolobokeMap  avgt   20     65.604 ±    1.135  ns/op
TupleVsNestedMapsBenchmark.benchGetFromTuple2Map          avgt   20     22.653 ±    0.745  ns/op
TupleVsNestedMapsBenchmark.benchGetFromTupleKolobokeMap   avgt   20  34824.901 ± 1718.183  ns/op
TupleVsNestedMapsBenchmark.benchGetFromTupleMap           avgt   20   2565.835 ±   57.402  ns/op
TupleVsNestedMapsBenchmark.benchPutToNestedMap            avgt   20     43.160 ±    0.340  ns/op
TupleVsNestedMapsBenchmark.benchPutToTuple2KolobokeMap    avgt   20    237.300 ±    3.362  ns/op
TupleVsNestedMapsBenchmark.benchPutToTuple2Map            avgt   20     40.952 ±    0.535  ns/op
TupleVsNestedMapsBenchmark.benchPutToTupleKolobokeMap     avgt   20  52315.769 ±  399.769  ns/op
TupleVsNestedMapsBenchmark.benchPutToTupleMap             avgt   20   3205.538 ±   44.306  ns/op
  • 如果键类的哈希码函数没有缓存和/或分发良好,“元组”方法可能会变得非常慢,尤其是对于koloboke。
  • 正如这里的结论(在这个(Obj-Obj)案例中),java.util.HashMap“非常”快。

共有3个答案

柯永福
2023-03-14

三元组作为抽象是可以的(至少,我看不出明显更好的替代方案,您可以重写Apache Commons的Triple抽象类来定义更好的hashCode()函数)。

final class ImmutableTriple<L, M, R> extends Triple<L, M, R> {
    public final L left;
    public final M middle;
    public final R right;
    private int h;

    public static <L, M, R> ImmutableTriple<L, M, R> of(L left, M middle, R right) {
        return new ImmutableTriple(left, middle, right);
    }

    public ImmutableTriple(L left, M middle, R right) {
        this.left = left;
        this.middle = middle;
        this.right = right;
    }

    public L getLeft() {
        return this.left;
    }

    public M getMiddle() {
        return this.middle;
    }

    public R getRight() {
        return this.right;
    }

    private int innerHash() {
        int h = left.hashCode();
        h *= 1000003;
        h += middle.hashCode();
        h *= 1000003;
        h += right.hashCode();
        return (int) LongHashFunction.murmur_3().hashInt(h);
    }

    @Override
    public int hashCode() {
        return h != 0 ? h : (h = innerHash());
    }
}
寿嘉悦
2023-03-14

你的基准问题清单如下:

  • 初始化是在静态区域完成的,应该使用@Setup方法@States
阙阳夏
2023-03-14

[更新问题的答案。]

嗯,基准仍然存在问题:

>

  • 在制作State生命周期时,您应该将state对象作为参数传递给benhcmark方法(请参阅下面的代码)。
  • 基准测试put()s应该以不同的方式进行:1)在@Setup方法中,应该创建集合(具有足够的容量size参数)2)在另一个@Setup(Level.调用)方法中,您应该调用collection.clear()3)在基准测试方法中测量纯put()s

    在基准测试方法中,您仍然会进行大量分配。这可能是您的情况,但它隐藏了集合性能贡献。

    所以,我写的是:

    package tests;
    
    import net.openhft.koloboke.collect.map.hash.HashObjObjMap;
    import net.openhft.koloboke.collect.map.hash.HashObjObjMaps;
    import org.apache.commons.lang3.tuple.Triple;
    import org.openjdk.jmh.annotations.*;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    @Fork(1)
    @Threads(1)
    @Warmup(iterations = 10)
    @Measurement(iterations = 20)
    @State(Scope.Thread)
    public class SoMultiMap {
    
        public static final int N = Integer.getInteger("runs", 100000);
    
        private static final double kbk = Double.parseDouble(System.getProperty("kbk", "1.0"));
    
        static class ImmutableTriple<L, M, R> extends Triple<L, M, R> {
            public final L left;
            public final M middle;
            public final R right;
            private int h;
    
            public static <L, M, R> ImmutableTriple<L, M, R> of(L left, M middle, R right) {
                return new ImmutableTriple(left, middle, right);
            }
    
            public ImmutableTriple(L left, M middle, R right) {
                this.left = left;
                this.middle = middle;
                this.right = right;
            }
    
            public L getLeft() {
                return this.left;
            }
    
            public M getMiddle() {
                return this.middle;
            }
    
            public R getRight() {
                return this.right;
            }
    
            private int innerHash() {
                int h = left.hashCode();
                h *= 1000003;
                h += middle.hashCode();
                h *= 1000003;
                h += right.hashCode();
                return h * 1000003;
            }
    
            @Override
            public int hashCode() {
                return h != 0 ? h : (h = innerHash());
            }
    
            @Override
            public boolean equals(Object obj) {
                if (!(obj instanceof ImmutableTriple))
                    return super.equals(obj);
                ImmutableTriple triple = (ImmutableTriple) obj;
                if (h != 0 && triple.h != 0 && h != triple.h)
                    return false;
                return super.equals(obj);
            }
        }
    
        ImmutableTriple<String, String, String>[] keys = new ImmutableTriple[N];
        Integer[] values = new Integer[N];
        Map<Triple<String, String, String>, Integer> sourceTupleMap;
        HashObjObjMap<Triple<String, String, String>, Integer> sourceTupleKMap;
    
        @Setup
        public void fill() {
            sourceTupleMap = new HashMap<>((int) (N / 0.75));
            sourceTupleKMap = HashObjObjMaps.newUpdatableMap((int) (N * kbk));
            for (int i = 0; i < N; i++) {
                keys[i] = ImmutableTriple.of("a-" + i, "b-" + i, "c-" + i);
                values[i] = i;
                sourceTupleKMap.put(keys[i], values[i]);
                sourceTupleMap.put(keys[i], values[i]);
            }
        }
    
        @Benchmark
        public int tupleHashMapGet(SoMultiMap st) {
            ImmutableTriple<String, String, String>[] keys = st.keys;
            Map<Triple<String, String, String>, Integer> map = st.sourceTupleMap;
            int s = 0;
            for (int i = 0; i < N; i++) {
                s += map.get(keys[i]);
            }
            return s;
        }
    
        @Benchmark
        public int tupleKolobokeGet(SoMultiMap st) {
            ImmutableTriple<String, String, String>[] keys = st.keys;
            HashObjObjMap<Triple<String, String, String>, Integer> map = st.sourceTupleKMap;
            int s = 0;
            for (int i = 0; i < N; i++) {
                s += map.get(keys[i]);
            }
            return s;
        }
    
        public static void main(String[] args) {
            SoMultiMap st = new SoMultiMap();
            st.fill();
            st.tupleKolobokeGet(st);
            st.tupleHashMapGet(st);
        }
    }
    

    现在有趣的是结果:

    使用Java 7u55:

    HashMap:  65 +- 6 ns/op
    Koloboke: 46 +- 2
    

    Java8u51:

    HashMap:  42 +- 0.5
    Koloboke: 49 +- 1
    

    因此,我们有一些虚拟机的变化,介于两者之间,这使得HashMap大大加快,而Kolobokemaps稍微慢一点。这需要调查,我现在没有时间。看见https://github.com/OpenHFT/Koloboke/issues/42

    另外,请注意以下几点:

    • 在服务器VM上运行基准测试
    • 在运行过程中禁用CPU缩放
    • 关闭繁重的应用程序(浏览器、Intellij等),除非您有16个硬件线程

  •  类似资料:
    • 问题内容: 我认为我的问题与此相似:如何实现具有多个键的Map?但有一个重要的区别。在这个问题中(如果我对它的理解是正确的,请告诉我是否正确),这些键应该总是唯一的。我想要一个Map形式: MyMap ,其中的键不一定是唯一的。如果那没有任何意义,我基本上想要一个二维数组,而不是通过坐标对引用元素,而是通过对象对引用它们。 是否有人对可以在其中工作的图书馆或自己实现此想法的好方法有任何想法?就库而

    • PyCharm包含各种键盘映射,以显示编辑器中最常用的命令。 本章详细讨论Pycharm键盘映射。 可以在文件菜单:帮助 -> 键盘映射参考 中找到可用键盘列表,如下面的屏幕截图所示 - 可以找到Pycharm键盘映射列表和可用的PDF格式快捷方式,如下所示 - 注 - Windows和Linux操作系统的默认键盘映射是默认键盘映射,而在Mac OS中,默认键盘映射是OSX 10.5。 还可以使用

    • 考虑下面两个片段(从这个JSPIF条目): 这里,是一个,是一个普通的老JavaScript对象(),而

    • 有两张数据表,通过第三张数据表来表示关联关系,我们称之为多对多的映射 如上图,通过一个中间数据表的两个字段,分别指向两个对象的主键,可以实现多对多映射。所以,Pet.foods(一个 List<Food>) 或者 Food.pets(一个List<Pet>)就是多对多映射。 在 POJO 中配置多对多映射 在 POJO 类中字段中增加注解 @ManyMany: @Table("t_food")

    • persistenceException:DB2 SQL错误:sqlcode=-206,sqlstate=42703,sqlerrmc=t0.id,driver=3.52.95{prepstmnt 1029586270

    • 我有多个数组映射。 我想从多个地图中获取重复地图键的列表。 例如 除了遍历所有地图键,检查集合是否包含键,如果不将键添加到集合中,我想不出任何更干净的方法。有没有办法通过streams来实现这一点?