Skip to content

Commit 333ae44

Browse files
g-sg-vSergei Galiamichev
andauthored
InMemoryDataShard: Allow to replace TreeMap with an explicitly sorted LinkedHashMap to reduce comparison count
`TreeMap` requires log(N) comparisons for every `Map.get()` and every `Map.put()` operation. Due to the implementation of in-memory storage, this **not** cheap. If you have a massive amount of `insert()`s/`save()`s and only occasionally get the list of sorted entities, you will likely gain massive performance benefits from using an alternative in-memory data shard map implementation using `LinkedHashMap` (even if sorted on `insert()`, but especially if sorted on reads). To switch map implementation, set the `tech.ydb.yoj.repository.test.inmemory.impl` system property to `oninsert` (for sorting data on `insert()`) or `onget` (for sorting data on first read.) --------- Co-authored-by: Sergei Galiamichev <gsv@nebius.com>
1 parent c9406dc commit 333ae44

File tree

3 files changed

+176
-4
lines changed

3 files changed

+176
-4
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package tech.ydb.yoj.repository.test.inmemory;
2+
3+
import tech.ydb.yoj.repository.db.Entity;
4+
5+
import java.util.AbstractMap;
6+
import java.util.ArrayList;
7+
import java.util.Collections;
8+
import java.util.Comparator;
9+
import java.util.LinkedHashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Set;
13+
14+
/**
15+
* TreeMap<T, E> - is very expensive due to very complicated comparison log(n)
16+
*/
17+
class EntityIdMap<T extends Entity<T>, E> extends AbstractMap<Entity.Id<T>, E> {
18+
private final Comparator<Entity.Id<T>> comparator;
19+
private Map<Entity.Id<T>, E> entries = new LinkedHashMap<>(); // probably not the best here but very simple
20+
21+
public EntityIdMap(Comparator<Entity.Id<T>> comparator) {
22+
this.comparator = comparator;
23+
}
24+
25+
public EntityIdMap(EntityIdMap<T, E> entityLines) {
26+
this(entityLines.comparator);
27+
entries = new LinkedHashMap<>(entityLines);
28+
}
29+
30+
@Override
31+
public Set<Entry<Entity.Id<T>, E>> entrySet() {
32+
return entries.entrySet();
33+
}
34+
35+
@Override
36+
public E get(Object key) {
37+
return entries.get(key);
38+
}
39+
40+
@Override
41+
public E put(Entity.Id<T> key, E value) {
42+
List<Entity.Id<T>> temp = new ArrayList<>(entries.keySet());
43+
int index = Collections.binarySearch(temp, key, comparator);
44+
Map<Entity.Id<T>, E> result = new LinkedHashMap<>();
45+
E oldValue = null;
46+
int i = 0;
47+
for (Entry<Entity.Id<T>, E> entry : entries.entrySet()) {
48+
if (index < 0 && -index - 1 == i) {
49+
result.put(key, value);
50+
result.put(entry.getKey(), entry.getValue());
51+
} else if (index == i) {
52+
oldValue = entry.getValue();
53+
result.put(key, value);
54+
} else {
55+
result.put(entry.getKey(), entry.getValue());
56+
}
57+
i++;
58+
}
59+
if (-index - 1 == result.size()) {
60+
result.put(key, value); // add last (item i.e first item or item with max value to existing list)
61+
}
62+
entries = result;
63+
return oldValue;
64+
}
65+
66+
@Override
67+
public E remove(Object key) {
68+
return entries.remove(key);
69+
}
70+
71+
@Override
72+
public void clear() {
73+
entries.clear();
74+
}
75+
76+
@Override
77+
public boolean containsKey(Object key) {
78+
return entries.containsKey(key);
79+
}
80+
81+
@Override
82+
public boolean containsValue(Object value) {
83+
return entries.containsValue(value);
84+
}
85+
86+
@Override
87+
public int size() {
88+
return entries.size();
89+
}
90+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package tech.ydb.yoj.repository.test.inmemory;
2+
3+
import tech.ydb.yoj.repository.db.Entity;
4+
5+
import java.util.AbstractMap;
6+
import java.util.ArrayList;
7+
import java.util.Comparator;
8+
import java.util.LinkedHashMap;
9+
import java.util.LinkedHashSet;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Set;
13+
14+
class EntityIdMapOnGet<T extends Entity<T>, E> extends AbstractMap<Entity.Id<T>, E> {
15+
private final Comparator<Entity.Id<T>> comparator;
16+
private final Map<Entity.Id<T>, E> entries = new LinkedHashMap<>();
17+
private Set<Entry<Entity.Id<T>, E>> cachedEntrySet = null;
18+
19+
public EntityIdMapOnGet(Comparator<Entity.Id<T>> comparator) {
20+
this.comparator = comparator;
21+
}
22+
23+
@Override
24+
public Set<Entry<Entity.Id<T>, E>> entrySet() {
25+
if (cachedEntrySet == null) {
26+
List<Entry<Entity.Id<T>, E>> sorted = new ArrayList<>(entries.entrySet());
27+
sorted.sort(Comparator.comparing(Entry::getKey, comparator));
28+
cachedEntrySet = new LinkedHashSet<>(sorted);
29+
}
30+
return cachedEntrySet;
31+
}
32+
33+
@Override
34+
public E get(Object key) {
35+
return entries.get(key);
36+
}
37+
38+
@Override
39+
public E put(Entity.Id<T> key, E value) {
40+
E old = entries.put(key, value);
41+
cachedEntrySet = null; // invalidate cache
42+
return old;
43+
}
44+
45+
@Override
46+
public E remove(Object key) {
47+
E removed = entries.remove(key);
48+
if (removed != null) {
49+
cachedEntrySet = null; // invalidate cache
50+
}
51+
return removed;
52+
}
53+
54+
@Override
55+
public void clear() {
56+
entries.clear();
57+
cachedEntrySet = null;
58+
}
59+
60+
@Override
61+
public boolean containsKey(Object key) {
62+
return entries.containsKey(key);
63+
}
64+
65+
@Override
66+
public boolean containsValue(Object value) {
67+
return entries.containsValue(value);
68+
}
69+
70+
@Override
71+
public int size() {
72+
return entries.size();
73+
}
74+
}

repository-inmemory/src/main/java/tech/ydb/yoj/repository/test/inmemory/InMemoryDataShard.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@
2121
import java.util.TreeMap;
2222

2323
/*package*/ final class InMemoryDataShard<T extends Entity<T>> {
24+
private static final String DEFAULT_MAP_IMPLEMENTATION = "treemap";
25+
private static final String MAP_IMPLEMENTATION = System.getProperty("tech.ydb.yoj.repository.test.inmemory.impl", DEFAULT_MAP_IMPLEMENTATION);
26+
2427
private final TableDescriptor<T> tableDescriptor;
2528
private final EntitySchema<T> schema;
26-
private final TreeMap<Entity.Id<T>, InMemoryEntityLine> entityLines;
29+
private final Map<Entity.Id<T>, InMemoryEntityLine> entityLines;
2730
private final Map<Long, Set<Entity.Id<T>>> uncommited = new HashMap<>();
2831

2932
private InMemoryDataShard(
3033
TableDescriptor<T> tableDescriptor,
3134
EntitySchema<T> schema,
32-
TreeMap<Entity.Id<T>, InMemoryEntityLine> entityLines
35+
Map<Entity.Id<T>, InMemoryEntityLine> entityLines
3336
) {
3437
this.tableDescriptor = tableDescriptor;
3538
this.schema = schema;
@@ -44,12 +47,17 @@ public InMemoryDataShard(TableDescriptor<T> tableDescriptor) {
4447
);
4548
}
4649

47-
private static <T extends Entity<T>> TreeMap<Entity.Id<T>, InMemoryEntityLine> createEmptyLines(Class<T> type) {
50+
private static <T extends Entity<T>> Map<Entity.Id<T>, InMemoryEntityLine> createEmptyLines(Class<T> type) {
51+
if ("oninsert".equals(MAP_IMPLEMENTATION)) {
52+
return new EntityIdMap<>(EntityIdSchema.getIdComparator(type));
53+
} else if ("onget".equals(MAP_IMPLEMENTATION)) {
54+
return new EntityIdMapOnGet<>(EntityIdSchema.getIdComparator(type));
55+
}
4856
return new TreeMap<>(EntityIdSchema.getIdComparator(type));
4957
}
5058

5159
public synchronized InMemoryDataShard<T> createSnapshot() {
52-
TreeMap<Entity.Id<T>, InMemoryEntityLine> snapshotLines = createEmptyLines(tableDescriptor.entityType());
60+
Map<Entity.Id<T>, InMemoryEntityLine> snapshotLines = createEmptyLines(tableDescriptor.entityType());
5361
for (Map.Entry<Entity.Id<T>, InMemoryEntityLine> entry : entityLines.entrySet()) {
5462
snapshotLines.put(entry.getKey(), entry.getValue().createSnapshot());
5563
}

0 commit comments

Comments
 (0)