Skip to content

Commit 43c480f

Browse files
committed
utils to CFUtil; + InternalIterable
1 parent 47afca2 commit 43c480f

File tree

6 files changed

+159
-89
lines changed

6 files changed

+159
-89
lines changed

src/main/java/com/trivago/fastutilconcurrentwrapper/PrimitiveConcurrentMap.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.trivago.fastutilconcurrentwrapper;
22

3+
import com.trivago.fastutilconcurrentwrapper.util.CFUtil;
34
import com.trivago.fastutilconcurrentwrapper.util.CloseableLock;
45
import com.trivago.fastutilconcurrentwrapper.util.CloseableReadWriteLock;
56
import it.unimi.dsi.fastutil.Function;
@@ -73,14 +74,14 @@ public void clear () {
7374
}
7475

7576
protected int getBucket (long key) {
76-
return PrimitiveKeyMap.bucket(key, locks.length);
77+
return CFUtil.bucket(key, locks.length);
7778
}
7879

7980
protected int getBucket (int key) {
80-
return PrimitiveKeyMap.bucket(key, locks.length);// Integer.hashCode(key) == key
81+
return CFUtil.bucket(key, locks.length);// Integer.hashCode(key) == key
8182
}
8283

8384
protected int getBucket (Object key) {
84-
return PrimitiveKeyMap.bucket(key, locks.length);
85+
return CFUtil.bucket(key, locks.length);
8586
}
8687
}
Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.trivago.fastutilconcurrentwrapper;
22

3-
import it.unimi.dsi.fastutil.HashCommon;
4-
import jakarta.validation.constraints.Positive;
5-
import org.jspecify.annotations.Nullable;
6-
73
/**
84
@see it.unimi.dsi.fastutil.Function
95
*/
@@ -14,47 +10,4 @@ public interface PrimitiveKeyMap {
1410
boolean isEmpty ();
1511

1612
void clear ();
17-
18-
/** @see #bucket */
19-
static int bucket (int hashOrKey, @Positive int bucketSize) {
20-
return Math.abs(HashCommon.mix(hashOrKey) % bucketSize);
21-
}
22-
23-
/** @see #bucket */
24-
static int bucket (@Nullable Object object4hashCode, @Positive int bucketSize) {
25-
return object4hashCode != null ? bucket(object4hashCode.hashCode(), bucketSize)
26-
: 0;
27-
}
28-
29-
/**
30-
Get positive, quite `random` bucket index between 0 and bucketSize-1 for any key
31-
Fast. Safe for negative keys (including Long.MIN_VALUE, Integer.MIN_VALUE)
32-
33-
FastUtil has ❌ HashCommon#mix(long), but we use ✅ Long.hashCode + mix(int) because:
34-
35-
mix(1L) ≠ mix(1) → it is against common knowledge and expectations
36-
mix(1L) ≠ mix(Long.valueOf(1L)) → 😱
37-
38-
@see it.unimi.dsi.fastutil.HashCommon#mix(long)
39-
@see it.unimi.dsi.fastutil.HashCommon#mix(int)
40-
@see Long#hashCode(long)
41-
*/
42-
static int bucket (long hashOrKey, @Positive int bucketSize) {
43-
//× return (int) Math.abs(HashCommon.mix(hashOrKey) % bucketSize);
44-
int intHash = Long.hashCode(hashOrKey);
45-
return Math.abs(HashCommon.mix(intHash) % bucketSize);
46-
}
47-
48-
/**
49-
* Combined two 32-bit keys into a 64-bit compound.
50-
51-
https://github.com/aeron-io/agrona/blob/master/agrona/src/main/java/org/agrona/collections/Hashing.java
52-
53-
* @param keyHi to make the upper bits
54-
* @param keyLo to make the lower bits
55-
* @return the compound key
56-
*/
57-
static long compoundKey (int keyHi, final int keyLo) {
58-
return ((long)keyHi << 32) | (keyLo & 0xFfFf_FfFfL);
59-
}
6013
}

src/main/java/com/trivago/fastutilconcurrentwrapper/longkey/StripedNonBlockingHashMapLong.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.trivago.fastutilconcurrentwrapper.longkey;
22

33
import com.trivago.fastutilconcurrentwrapper.PrimitiveKeyMap;
4+
import com.trivago.fastutilconcurrentwrapper.util.CFUtil;
45
import com.trivago.fastutilconcurrentwrapper.util.PaddedLock;
56
import com.trivago.fastutilconcurrentwrapper.util.SmartIterator;
67
import com.trivago.fastutilconcurrentwrapper.util.SmartLongIterator;
@@ -54,7 +55,7 @@ public StripedNonBlockingHashMapLong (int initialSize, boolean optForSpace, int
5455

5556
/** @see com.google.common.util.concurrent.Striped#get(Object) */
5657
protected PaddedLock write (long key) {
57-
var lock = s[PrimitiveKeyMap.bucket(key, s.length)];
58+
var lock = s[CFUtil.bucket(key, s.length)];
5859
lock.lock();
5960
return lock;
6061
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.trivago.fastutilconcurrentwrapper.util;
2+
3+
import it.unimi.dsi.fastutil.HashCommon;
4+
import jakarta.validation.constraints.Positive;
5+
import org.jspecify.annotations.Nullable;
6+
7+
/**
8+
Concurrent Fast Util
9+
@see */
10+
public final class CFUtil {
11+
public static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
12+
13+
/** @see #bucket */
14+
public static int bucket (int hashOrKey, @Positive int bucketSize) {
15+
return Math.abs(HashCommon.mix(hashOrKey) % bucketSize);
16+
}
17+
18+
/** @see #bucket */
19+
public static int bucket (@Nullable Object object4hashCode, @Positive int bucketSize) {
20+
return object4hashCode != null ? bucket(object4hashCode.hashCode(), bucketSize)
21+
: 0;
22+
}
23+
24+
/**
25+
Get positive, quite `random` bucket index between 0 and bucketSize-1 for any key
26+
Fast. Safe for negative keys (including Long.MIN_VALUE, Integer.MIN_VALUE)
27+
28+
FastUtil has ❌ HashCommon#mix(long), but we use ✅ Long.hashCode + mix(int) because:
29+
30+
mix(1L) ≠ mix(1) → it is against common knowledge and expectations
31+
mix(1L) ≠ mix(Long.valueOf(1L)) → 😱
32+
33+
@see it.unimi.dsi.fastutil.HashCommon#mix(long)
34+
@see it.unimi.dsi.fastutil.HashCommon#mix(int)
35+
@see Long#hashCode(long)
36+
*/
37+
public static int bucket (long hashOrKey, @Positive int bucketSize) {
38+
//× return (int) Math.abs(HashCommon.mix(hashOrKey) % bucketSize);
39+
int intHash = Long.hashCode(hashOrKey);
40+
return Math.abs(HashCommon.mix(intHash) % bucketSize);
41+
}
42+
43+
/**
44+
* Combined two 32-bit keys into a 64-bit compound.
45+
46+
https://github.com/aeron-io/agrona/blob/master/agrona/src/main/java/org/agrona/collections/Hashing.java
47+
48+
* @param keyHi to make the upper bits
49+
* @param keyLo to make the lower bits
50+
* @return the compound key
51+
*/
52+
public static long compoundKey (int keyHi, final int keyLo) {
53+
return ((long)keyHi << 32) | (keyLo & 0xFfFf_FfFfL);
54+
}
55+
56+
private CFUtil (){ throw new AssertionError(); }//new
57+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.trivago.fastutilconcurrentwrapper.util;
2+
3+
import java.util.concurrent.CancellationException;
4+
import java.util.function.Consumer;
5+
6+
/**
7+
JDK {@link Iterable#iterator()} is an external iterator.
8+
{{@link Iterable#forEach(Consumer)}} is an example of an internal iterator.
9+
Both are equivalent with one exception: internal iterator can't stop and remove current element.
10+
InternalIterableCallback helps to solve this problem.
11+
It is hard to implement Iterator for external iteration (it has state, can't have lock).
12+
It is much easier to implement an internal one.
13+
14+
@see java.util.Iterator
15+
@see java.lang.Iterable
16+
@see java.util.Spliterator
17+
@see java.util.function.BiConsumer
18+
*/
19+
@FunctionalInterface
20+
public interface InternalIterable extends AutoCloseable {
21+
22+
CancellationException STOP = new CancellationException("InternalIterable.STOP"){
23+
{
24+
initCause(null);
25+
setStackTrace(CFUtil.EMPTY_STACK_TRACE);
26+
}
27+
@Override public String toString (){ return "CancellationException: "+getMessage(); }
28+
};
29+
30+
IllegalArgumentException REMOVE = new IllegalArgumentException("InternalIterable.REMOVE", null){
31+
{
32+
setStackTrace(CFUtil.EMPTY_STACK_TRACE);
33+
}
34+
@Override public String toString (){ return "IllegalArgumentException: "+getMessage(); }
35+
};
36+
37+
/**
38+
Stop iteration loop
39+
Other interesting alternative would be throwing and exception, e.g: {@link java.util.concurrent.CancellationException}
40+
*/
41+
@Override void close ();
42+
43+
/**
44+
Remove current item
45+
@see java.util.Iterator#remove()
46+
*/
47+
default void remove () {
48+
throw new UnsupportedOperationException("remove");
49+
}
50+
51+
default long index () {
52+
throw new UnsupportedOperationException("index");
53+
}
54+
55+
default long size () {
56+
throw new UnsupportedOperationException("size");
57+
}
58+
}
Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.trivago.fastutilconcurrentwrapper.intkey;
22

33
import com.trivago.fastutilconcurrentwrapper.PrimitiveConcurrentMap;
4-
import com.trivago.fastutilconcurrentwrapper.PrimitiveKeyMap;
4+
import com.trivago.fastutilconcurrentwrapper.util.CFUtil;
55
import org.junit.jupiter.api.Disabled;
66
import org.junit.jupiter.api.Test;
77

@@ -12,37 +12,37 @@
1212
class PrimitiveConcurrentMapTest {
1313
@Test
1414
void _hashInt () {
15-
assertEquals(50_880, PrimitiveKeyMap.bucket(Integer.MIN_VALUE, 100_000));
16-
assertEquals(47_470, PrimitiveKeyMap.bucket(-Integer.MAX_VALUE, 100_000));
17-
assertEquals(74_300, PrimitiveKeyMap.bucket(-100_000, 100_000));
18-
assertEquals(56_431, PrimitiveKeyMap.bucket(-1, 100_000));
19-
assertEquals(0, PrimitiveKeyMap.bucket(0, 100_000));
20-
assertEquals(3_410, PrimitiveKeyMap.bucket(1, 100_000));
21-
assertEquals(82_299, PrimitiveKeyMap.bucket(100_000, 100_000));
22-
assertEquals(59_985, PrimitiveKeyMap.bucket(Integer.MAX_VALUE, 100_000));
15+
assertEquals(50_880, CFUtil.bucket(Integer.MIN_VALUE, 100_000));
16+
assertEquals(47_470, CFUtil.bucket(-Integer.MAX_VALUE, 100_000));
17+
assertEquals(74_300, CFUtil.bucket(-100_000, 100_000));
18+
assertEquals(56_431, CFUtil.bucket(-1, 100_000));
19+
assertEquals(0, CFUtil.bucket(0, 100_000));
20+
assertEquals(3_410, CFUtil.bucket(1, 100_000));
21+
assertEquals(82_299, CFUtil.bucket(100_000, 100_000));
22+
assertEquals(59_985, CFUtil.bucket(Integer.MAX_VALUE, 100_000));
2323
}
2424

2525
@Test
2626
void _hashLong () {
27-
assertEquals(50_880, PrimitiveKeyMap.bucket(Long.MIN_VALUE, 100_000));
28-
assertEquals(50_880, PrimitiveKeyMap.bucket(Integer.MIN_VALUE & 0xFFFFFFFFL, 100_000));
29-
assertEquals(59_985, PrimitiveKeyMap.bucket((long) Integer.MIN_VALUE, 100_000));
30-
assertEquals(47_470, PrimitiveKeyMap.bucket(-Integer.MAX_VALUE & 0xFFFFFFFFL, 100_000));
31-
assertEquals(96_447, PrimitiveKeyMap.bucket((long) -Integer.MAX_VALUE, 100_000));
32-
assertEquals(16_093, PrimitiveKeyMap.bucket(-100_000L, 100_000));
33-
assertEquals(0, PrimitiveKeyMap.bucket(-1L, 100_000));
34-
assertEquals(0, PrimitiveKeyMap.bucket(0L, 100_000));
35-
assertEquals(3_410, PrimitiveKeyMap.bucket(1L, 100_000));
36-
assertEquals(82_299, PrimitiveKeyMap.bucket(100_000L, 100_000));
37-
assertEquals(59_985, PrimitiveKeyMap.bucket((long) Integer.MAX_VALUE, 100_000));
38-
assertEquals(50_880, PrimitiveKeyMap.bucket(Long.MAX_VALUE, 100_000));
27+
assertEquals(50_880, CFUtil.bucket(Long.MIN_VALUE, 100_000));
28+
assertEquals(50_880, CFUtil.bucket(Integer.MIN_VALUE & 0xFFFFFFFFL, 100_000));
29+
assertEquals(59_985, CFUtil.bucket((long) Integer.MIN_VALUE, 100_000));
30+
assertEquals(47_470, CFUtil.bucket(-Integer.MAX_VALUE & 0xFFFFFFFFL, 100_000));
31+
assertEquals(96_447, CFUtil.bucket((long) -Integer.MAX_VALUE, 100_000));
32+
assertEquals(16_093, CFUtil.bucket(-100_000L, 100_000));
33+
assertEquals(0, CFUtil.bucket(-1L, 100_000));
34+
assertEquals(0, CFUtil.bucket(0L, 100_000));
35+
assertEquals(3_410, CFUtil.bucket(1L, 100_000));
36+
assertEquals(82_299, CFUtil.bucket(100_000L, 100_000));
37+
assertEquals(59_985, CFUtil.bucket((long) Integer.MAX_VALUE, 100_000));
38+
assertEquals(50_880, CFUtil.bucket(Long.MAX_VALUE, 100_000));
3939
}
4040

4141
@Test @Disabled("Total checks: 38510819404, time: 8551,564, op/sec: 189_126_936")
4242
void longsAreSame () {
4343
long total = 0, t = System.nanoTime();
4444
for (long i = Long.MIN_VALUE, max = Long.MAX_VALUE - 479001599-9; i < max; i += 479001599){
45-
assertEquals(PrimitiveKeyMap.bucket(Long.valueOf(i), 1_000_000), PrimitiveKeyMap.bucket(i, 1_000_000));
45+
assertEquals(CFUtil.bucket(Long.valueOf(i), 1_000_000), CFUtil.bucket(i, 1_000_000));
4646
total++;
4747
}
4848
t = System.nanoTime() - t;
@@ -51,26 +51,26 @@ void longsAreSame () {
5151

5252
@Test
5353
void _hashObj () {
54-
assertEquals(0, PrimitiveKeyMap.bucket(null, 100_000));
54+
assertEquals(0, CFUtil.bucket(null, 100_000));
5555

56-
assertEquals(50_880, PrimitiveKeyMap.bucket(Long.valueOf(Long.MIN_VALUE), 100_000));
57-
assertEquals(50_880, PrimitiveKeyMap.bucket(Long.valueOf(Integer.MIN_VALUE & 0xFFFFFFFFL), 100_000));
58-
assertEquals(59_985, PrimitiveKeyMap.bucket(Long.valueOf(Integer.MIN_VALUE), 100_000));
59-
assertEquals(47_470, PrimitiveKeyMap.bucket(Long.valueOf(-Integer.MAX_VALUE & 0xFFFFFFFFL), 100_000));
60-
assertEquals(96_447, PrimitiveKeyMap.bucket(Long.valueOf(-Integer.MAX_VALUE), 100_000));
61-
assertEquals(16_093, PrimitiveKeyMap.bucket(Long.valueOf(-100_000L), 100_000));
62-
assertEquals(0, PrimitiveKeyMap.bucket(Long.valueOf(-1L), 100_000));
63-
assertEquals(0, PrimitiveKeyMap.bucket(Long.valueOf(0L), 100_000));
64-
assertEquals(3_410, PrimitiveKeyMap.bucket(Long.valueOf(1L), 100_000));
65-
assertEquals(82_299, PrimitiveKeyMap.bucket(Long.valueOf(100_000L), 100_000));
66-
assertEquals(59_985, PrimitiveKeyMap.bucket(Long.valueOf(Integer.MAX_VALUE), 100_000));
67-
assertEquals(50_880, PrimitiveKeyMap.bucket(Long.valueOf(Long.MAX_VALUE), 100_000));
56+
assertEquals(50_880, CFUtil.bucket(Long.valueOf(Long.MIN_VALUE), 100_000));
57+
assertEquals(50_880, CFUtil.bucket(Long.valueOf(Integer.MIN_VALUE & 0xFFFFFFFFL), 100_000));
58+
assertEquals(59_985, CFUtil.bucket(Long.valueOf(Integer.MIN_VALUE), 100_000));
59+
assertEquals(47_470, CFUtil.bucket(Long.valueOf(-Integer.MAX_VALUE & 0xFFFFFFFFL), 100_000));
60+
assertEquals(96_447, CFUtil.bucket(Long.valueOf(-Integer.MAX_VALUE), 100_000));
61+
assertEquals(16_093, CFUtil.bucket(Long.valueOf(-100_000L), 100_000));
62+
assertEquals(0, CFUtil.bucket(Long.valueOf(-1L), 100_000));
63+
assertEquals(0, CFUtil.bucket(Long.valueOf(0L), 100_000));
64+
assertEquals(3_410, CFUtil.bucket(Long.valueOf(1L), 100_000));
65+
assertEquals(82_299, CFUtil.bucket(Long.valueOf(100_000L), 100_000));
66+
assertEquals(59_985, CFUtil.bucket(Long.valueOf(Integer.MAX_VALUE), 100_000));
67+
assertEquals(50_880, CFUtil.bucket(Long.valueOf(Long.MAX_VALUE), 100_000));
6868
}
6969

7070
@Test
7171
void buildLongKey () {
72-
assertEquals(0, PrimitiveKeyMap.compoundKey(0,0));
73-
assertEquals(Integer.MIN_VALUE & 0xFFFFFFFFL, PrimitiveKeyMap.compoundKey(0,Integer.MIN_VALUE));
74-
assertEquals(0x8000_0000_0000_0000L, PrimitiveKeyMap.compoundKey(Integer.MIN_VALUE,0));
72+
assertEquals(0, CFUtil.compoundKey(0,0));
73+
assertEquals(Integer.MIN_VALUE & 0xFFFFFFFFL, CFUtil.compoundKey(0,Integer.MIN_VALUE));
74+
assertEquals(0x8000_0000_0000_0000L, CFUtil.compoundKey(Integer.MIN_VALUE,0));
7575
}
7676
}

0 commit comments

Comments
 (0)