Skip to content

Commit 86e632b

Browse files
committed
Analyze construction cache misses
1 parent b1d6e63 commit 86e632b

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package org.fastfilter.analysis;
2+
3+
import java.util.Arrays;
4+
import java.util.LinkedHashMap;
5+
import java.util.Map;
6+
7+
public class AnalyzeXorConstructionCacheMisses {
8+
public static void main(String... args) throws Exception {
9+
int size = 1000000;
10+
long[] keys = new long[size];
11+
for (int i = 0; i < size; i++) {
12+
keys[i] = hash64(i, 0);
13+
}
14+
for (Class<? extends Xor> c : Arrays.asList(Xor.class, Fuse.class)) {
15+
Xor filter = c.getConstructor(int.class).newInstance(size);
16+
CacheAccessCallback cache;
17+
cache = new CacheAccessCallback();
18+
filter.access = cache;
19+
filter.construct1(keys);
20+
System.out.println(filter + " constuct1 cache misses: " + cache.cacheMisses);
21+
cache = new CacheAccessCallback();
22+
filter.access = cache;
23+
filter.construct2();
24+
System.out.println(filter + " constuct2 cache misses: " + cache.cacheMisses);
25+
}
26+
}
27+
28+
public static long hash64(long x, long seed) {
29+
x += seed;
30+
x = (x ^ (x >>> 33)) * 0xff51afd7ed558ccdL;
31+
x = (x ^ (x >>> 33)) * 0xc4ceb9fe1a85ec53L;
32+
x = x ^ (x >>> 33);
33+
return x;
34+
}
35+
36+
public static int reduce(int hash, int n) {
37+
return (int) (((hash & 0xffffffffL) * n) >>> 32);
38+
}
39+
40+
static class AccessCallback {
41+
void read(int index) {
42+
}
43+
void write(int index) {
44+
}
45+
}
46+
47+
static class CacheAccessCallback extends AccessCallback {
48+
int cacheMisses;
49+
50+
// assume 2 MB L3 cache, and 64 bytes per cache line
51+
final int MAX_ENTRIES = 2 * 1024 * 1024 / 64;
52+
Map<Integer, Integer> cache = new LinkedHashMap<Integer, Integer>(MAX_ENTRIES+1, .75F, true) {
53+
public boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
54+
return size() > MAX_ENTRIES;
55+
}
56+
};
57+
58+
int cacheLine(int index) {
59+
// assume 64 bytes per cache line, and cells to be 8 bytes; so divide by 8
60+
return index >> 3;
61+
}
62+
void read(int index) {
63+
int cacheLine = cacheLine(index);
64+
if (cache.get(cacheLine) != null) {
65+
return;
66+
}
67+
cacheMisses++;
68+
cache.put(cacheLine, 1);
69+
}
70+
void write(int index) {
71+
// small simplification
72+
read(index);
73+
}
74+
}
75+
76+
77+
static class Xor {
78+
AccessCallback access = new AccessCallback();
79+
int size;
80+
long[] values;
81+
int[] counts;
82+
int segmentLength;
83+
84+
Xor() {
85+
}
86+
87+
public Xor(int size) {
88+
this.size = size;
89+
int length = (int) (32 + 1.23 * size + 2) / 3 * 3;
90+
this.segmentLength = length / 3;
91+
this.counts = new int[length];
92+
this.values = new long[length];
93+
}
94+
95+
public String toString() {
96+
return "Xor";
97+
}
98+
99+
long getValue(int index) {
100+
access.read(index);
101+
return values[index];
102+
}
103+
104+
void xorValue(int index, long value) {
105+
access.write(index);
106+
values[index] ^= value;
107+
}
108+
109+
protected int[] indexes(long hash) {
110+
int h0 = 0 * segmentLength + reduce((int) hash64(hash, 0), segmentLength);
111+
int h1 = 1 * segmentLength + reduce((int) hash64(hash, 1), segmentLength);
112+
int h2 = 2 * segmentLength + reduce((int) hash64(hash, 2), segmentLength);
113+
return new int[] { h0, h1, h2 };
114+
}
115+
116+
void construct1(long[] keys) {
117+
for (long k : keys) {
118+
for (int x : indexes(k)) {
119+
counts[x]++;
120+
xorValue(x, k);
121+
}
122+
}
123+
}
124+
125+
void construct2() {
126+
int[] alone = new int[counts.length];
127+
int alonePos = 0;
128+
for (int i = 0; i < counts.length; i++) {
129+
if (counts[i] == 1) {
130+
alone[alonePos++] = i;
131+
}
132+
}
133+
int reverseOrderPos = 0;
134+
while (alonePos > 0) {
135+
alonePos--;
136+
int index = alone[alonePos];
137+
if (counts[index] == 1) {
138+
// It is still there!
139+
long k = getValue(index);
140+
// reverseOrder[reverseOrderPos] = hash;
141+
for (int index3 : indexes(k)) {
142+
if (index3 == index) {
143+
// reverseH[reverseOrderPos] = hi;
144+
// no need to decrement & remove
145+
continue;
146+
} else if (counts[index3] == 2) {
147+
// Found a new candidate !
148+
alone[alonePos++] = index3;
149+
}
150+
counts[index3]--;
151+
xorValue(index3, k);
152+
}
153+
reverseOrderPos++;
154+
}
155+
}
156+
if (reverseOrderPos != size) {
157+
throw new IllegalStateException();
158+
}
159+
}
160+
161+
}
162+
163+
static class Fuse extends Xor {
164+
int segmentCount;
165+
166+
public Fuse(int size) {
167+
this.size = size;
168+
// TODO hardcoded, for about 0.4 to 1.2 million keys
169+
this.segmentLength = 4096;
170+
int length = (int) (1.13 * size + segmentLength) / segmentLength * segmentLength;
171+
this.segmentCount = Math.max(1, length / segmentLength - 2);
172+
length = (segmentCount + 2) * segmentLength;
173+
this.counts = new int[length];
174+
this.values = new long[length];
175+
}
176+
177+
public String toString() {
178+
return "Fuse";
179+
}
180+
181+
protected int[] indexes(long hash) {
182+
int seg = reduce((int) hash64(hash, 0), segmentCount);
183+
int h0 = (seg + 0) * segmentLength + (int) (hash64(hash, 1) & (segmentLength - 1));
184+
int h1 = (seg + 1) * segmentLength + (int) (hash64(hash, 2) & (segmentLength - 1));
185+
int h2 = (seg + 2) * segmentLength + (int) (hash64(hash, 3) & (segmentLength - 1));
186+
return new int[] { h0, h1, h2 };
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)