Skip to content
This repository was archived by the owner on Jul 7, 2020. It is now read-only.

Commit 5868141

Browse files
authored
Merge pull request #131 from jomsdev/hyperbitbit
Hyperbitbit
2 parents 7e38ed9 + db41538 commit 5868141

3 files changed

Lines changed: 244 additions & 1 deletion

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright (C) 2011 Clearspring Technologies, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.clearspring.experimental.stream.cardinality;
18+
19+
import com.clearspring.analytics.hash.MurmurHash;
20+
import com.clearspring.analytics.stream.cardinality.ICardinality;
21+
import com.clearspring.analytics.stream.cardinality.CardinalityMergeException;
22+
23+
import java.io.IOException;
24+
25+
/**
26+
* Java implementation of HyperBitBit (HBB) algorithm as seen on the presentation
27+
* by Robert Sedgewick:
28+
* <p/>
29+
* https://www.cs.princeton.edu/~rs/talks/AC11-Cardinality.pdf
30+
* <p/>
31+
* HBB aims to beat HyperLogLog.
32+
* From the talk, on practical data:
33+
* - HyperBitBit, for N < 2^64,
34+
* - Uses 128 + 6 bits. (in this implementation case 128 + 8)
35+
* - Estimates cardinality within 10% of the actual.
36+
* <p/>
37+
* The algorithm still need some improvements.
38+
* - If you insert twice the same element the structure can change (not as in HLL)
39+
* - For small cardinalities it does not work AT ALL.
40+
* - The constatn 5.4 used in the cardinality estimation formula should be refined
41+
* with real world applications feedback
42+
* <p/>
43+
* Even so, HyperBitBit has the necessary characteristics to become
44+
* a better algorithm than HyperLogLog:
45+
* - Makes one pass through the stream.
46+
* - Uses a few dozen machine instructions per value
47+
* - Uses a few hundred bits
48+
* - Achieves 10% relative accuracy or better
49+
* <p/>
50+
* Any feedback to improve the algorithm in its weak points will be welcome.
51+
* <p/>
52+
*/
53+
54+
public class HyperBitBit implements ICardinality {
55+
56+
int lgN;
57+
long sketch;
58+
long sketch2;
59+
60+
/**
61+
* Create a new HyperBitBit instance.
62+
*
63+
* Remember that it does not work well for small cardinalities!
64+
*/
65+
public HyperBitBit() {
66+
lgN = 5;
67+
sketch = 0;
68+
sketch2 = 0;
69+
}
70+
71+
@Override
72+
public boolean offer(Object o) {
73+
final long x = MurmurHash.hash64(o);
74+
return offerHashed(x);
75+
}
76+
77+
@Override
78+
public boolean offerHashed(long hashedLong) {
79+
long k = (hashedLong << 58) >> 58;
80+
// Calculate the position of the leftmost 1-bit.
81+
int r = Long.numberOfLeadingZeros(hashedLong >> 6) - 6;
82+
83+
boolean modified = false;
84+
85+
if (r > lgN) {
86+
modified = true;
87+
sketch = sketch | 1L << k;
88+
}
89+
if (r > lgN+1) {
90+
modified = true;
91+
sketch2 = sketch2 | 1L << k;
92+
}
93+
if (Long.bitCount(sketch) > 31) {
94+
modified = true;
95+
sketch = sketch2;
96+
sketch2 = 0;
97+
++lgN;
98+
}
99+
100+
return modified;
101+
}
102+
103+
@Override
104+
public boolean offerHashed(int hashedInt) {
105+
throw new UnsupportedOperationException();
106+
}
107+
108+
@Override
109+
public long cardinality() {
110+
double exponent = lgN + 5.4 + Long.bitCount(sketch)/32.0;
111+
return (long) Math.pow(2, exponent);
112+
}
113+
114+
@Override
115+
public int sizeof() {
116+
return 0;
117+
}
118+
119+
@Override
120+
public byte[] getBytes() throws IOException {
121+
return new byte[0];
122+
}
123+
124+
@Override
125+
public ICardinality merge(ICardinality... estimators) throws CardinalityMergeException {
126+
throw new HyperBitBitMergeException("Cannot merge estimators of HyperBitBit class");
127+
}
128+
129+
@SuppressWarnings("serial")
130+
static class HyperBitBitMergeException extends CardinalityMergeException {
131+
public HyperBitBitMergeException(String message) {
132+
super(message);
133+
}
134+
}
135+
}

src/test/java/com/clearspring/analytics/stream/cardinality/TestICardinality.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void testICardinality() {
7070

7171
static int se = 0;
7272

73-
protected static Object streamElement(int i) {
73+
public static Object streamElement(int i) {
7474
return Long.toHexString(prng.nextLong());
7575
//return se++;
7676
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright (C) 2011 Clearspring Technologies, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.clearspring.experimental.stream.cardinality;
18+
19+
import com.clearspring.analytics.stream.cardinality.TestICardinality;
20+
21+
import org.junit.Test;
22+
import org.junit.Ignore;
23+
import static org.junit.Assert.assertTrue;
24+
25+
26+
public class TestHyperBitBit {
27+
// This test are "in progress" until HyperBitBit has better known error bounds
28+
// Right now they will no fail ever (@ignore)
29+
30+
31+
@Test
32+
@Ignore
33+
public void testSimpleHighCardinality() {
34+
int size = 10000000;
35+
int max_repetitions = 5;
36+
double errors_mean = 0;
37+
for (int repetitions = 0; repetitions < max_repetitions; ++repetitions) {
38+
long start = System.currentTimeMillis();
39+
HyperBitBit hyperBitBit = new HyperBitBit();
40+
41+
for (int i = 0; i < size; i++) {
42+
hyperBitBit.offer(TestICardinality.streamElement(i));
43+
}
44+
System.out.println("time: " + (System.currentTimeMillis() - start));
45+
long estimate = hyperBitBit.cardinality();
46+
double err = Math.abs(estimate - size) / (double) size;
47+
errors_mean += (err/max_repetitions);
48+
System.out.println(err);
49+
}
50+
System.out.println("This value should be less than 0.25: " + errors_mean);
51+
assertTrue(errors_mean < 0.1);
52+
53+
}
54+
55+
@Test
56+
@Ignore
57+
public void testMultipleOrderedHighCardinality() {
58+
int size = 10000000;
59+
60+
long start = System.currentTimeMillis();
61+
62+
HyperBitBit hyperBitBit = new HyperBitBit();
63+
64+
for (int i = 0; i < size; i++) {
65+
hyperBitBit.offer(i);
66+
hyperBitBit.offer(i);
67+
hyperBitBit.offer(i);
68+
hyperBitBit.offer(i);
69+
}
70+
71+
System.out.println("time: " + (System.currentTimeMillis() - start));
72+
long estimate = hyperBitBit.cardinality();
73+
double err = Math.abs(estimate - size) / (double) size;
74+
System.out.println(err);
75+
System.out.println("This value should be less than 0.2: " + err);
76+
assertTrue(err < 0.1);
77+
}
78+
79+
@Test
80+
@Ignore
81+
public void testMultipleUnorderedHighCardinality() {
82+
int size = 10000000;
83+
84+
long start = System.currentTimeMillis();
85+
86+
HyperBitBit hyperBitBit = new HyperBitBit();
87+
88+
for (int i = 0; i < size; i++) {
89+
hyperBitBit.offer(i);
90+
}
91+
92+
for (int i = 0; i < size; i++) {
93+
hyperBitBit.offer(i);
94+
}
95+
96+
for (int i = 0; i < size; i++) {
97+
hyperBitBit.offer(i);
98+
}
99+
100+
System.out.println("time: " + (System.currentTimeMillis() - start));
101+
long estimate = hyperBitBit.cardinality();
102+
double err = Math.abs(estimate - size) / (double) size;
103+
System.out.println(err);
104+
105+
System.out.println("This value should be less than 0.2: " + err);
106+
assertTrue(err < 0.1);
107+
}
108+
}

0 commit comments

Comments
 (0)