Skip to content

Commit d677823

Browse files
committed
Changed HashTable.index to DeBruijn's integer log2 calculation (~30% faster)
1 parent 92e6cef commit d677823

File tree

2 files changed

+174
-4
lines changed

2 files changed

+174
-4
lines changed

src/library/scala/collection/mutable/HashTable.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,11 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU
363363
// Note:
364364
// we take the most significant bits of the hashcode, not the lower ones
365365
// this is of crucial importance when populating the table in parallel
366+
// See: https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn for explanation and BitManipulationBenchmark for alternatives
367+
private val exponents = Array(32, 31, 4, 30, 3, 18, 8, 29, 2, 10, 12, 17, 7, 15, 28, 24, 1, 5, 19, 9, 11, 13, 16, 25, 6, 20, 14, 26, 21, 27, 22, 23)
366368
protected final def index(hcode: Int) = {
367-
val ones = table.length - 1
368-
val improved = improve(hcode, seedvalue)
369-
val shifted = (improved >> (32 - java.lang.Integer.bitCount(ones))) & ones
370-
shifted
369+
val exponent = exponents((table.length * 0x077CB531) >>> 27)
370+
(improve(hcode, seedvalue) >>> exponent) & (table.length - 1)
371371
}
372372

373373
protected def initWithContents(c: HashTable.Contents[A, Entry]) = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package scala.collection
2+
3+
import org.openjdk.jmh.annotations._
4+
import org.openjdk.jmh.infra._
5+
import org.openjdk.jmh.runner.IterationType
6+
import benchmark._
7+
import java.util.concurrent.TimeUnit
8+
9+
@BenchmarkMode(Array(Mode.AverageTime))
10+
@Fork(2)
11+
@Threads(1)
12+
@Warmup(iterations = 10)
13+
@Measurement(iterations = 10)
14+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
15+
@State(Scope.Benchmark)
16+
class BitManipulationBenchmark {
17+
val powersOfTwo = Array(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824)
18+
19+
//////////////////////////////////////////////
20+
21+
@Benchmark def withIntegerBitCount(bh: Blackhole) {
22+
for (v <- powersOfTwo) {
23+
val leadingZeros = withIntegerBitCount(v)
24+
// assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)")
25+
bh.consume(leadingZeros)
26+
}
27+
}
28+
29+
private def withIntegerBitCount(v: Int) = Integer.SIZE - Integer.bitCount(v - 1)
30+
31+
//////////////////////////////////////////////
32+
33+
@Benchmark def withIntegerNumberOfLeadingZeros(bh: Blackhole) {
34+
for (v <- powersOfTwo) {
35+
val leadingZeros = withIntegerNumberOfLeadingZeros(v)
36+
// assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)")
37+
bh.consume(leadingZeros)
38+
}
39+
}
40+
41+
private def withIntegerNumberOfLeadingZeros(v: Int) = Integer.numberOfLeadingZeros(v - 1)
42+
43+
//////////////////////////////////////////////
44+
45+
@Benchmark def withLoop(bh: Blackhole) {
46+
for (v <- powersOfTwo) {
47+
val leadingZeros = withLoop(v)
48+
bh.consume(leadingZeros)
49+
}
50+
}
51+
52+
private def withLoop(v: Int): Int = {
53+
var r = Integer.SIZE
54+
var copy = v >> 1
55+
while (copy != 0) {
56+
r -= 1
57+
copy = copy >> 1
58+
}
59+
r
60+
}
61+
62+
//////////////////////////////////////////////
63+
64+
@Benchmark def withMatch(bh: Blackhole) {
65+
for (v <- powersOfTwo) {
66+
val leadingZeros = withMatch(v)
67+
// assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)")
68+
bh.consume(leadingZeros)
69+
}
70+
}
71+
72+
private def withMatch(i: Int) = i match {
73+
case 1 => 32
74+
case 2 => 31
75+
case 4 => 30
76+
case 8 => 29
77+
case 16 => 28
78+
case 32 => 27
79+
case 64 => 26
80+
case 128 => 25
81+
case 256 => 24
82+
case 512 => 23
83+
case 1024 => 22
84+
case 2048 => 21
85+
case 4096 => 20
86+
case 8192 => 19
87+
case 16384 => 18
88+
case 32768 => 17
89+
case 65536 => 16
90+
case 131072 => 15
91+
case 262144 => 14
92+
case 524288 => 13
93+
case 1048576 => 12
94+
case 2097152 => 11
95+
case 4194304 => 10
96+
case 8388608 => 9
97+
case 16777216 => 8
98+
case 33554432 => 7
99+
case 67108864 => 6
100+
case 134217728 => 5
101+
case 268435456 => 4
102+
case 536870912 => 3
103+
case 1073741824 => 2
104+
}
105+
106+
107+
//////////////////////////////////////////////
108+
109+
@Benchmark def with2DeBruijn(bh: Blackhole) {
110+
for (v <- powersOfTwo) {
111+
val leadingZeros = with2DeBruijn(v)
112+
// assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)")
113+
bh.consume(leadingZeros)
114+
}
115+
}
116+
117+
// https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn
118+
private val multiplyDeBruijnBitPosition2 = Array(32, 31, 4, 30, 3, 18, 8, 29, 2, 10, 12, 17, 7, 15, 28, 24, 1, 5, 19, 9, 11, 13, 16, 25, 6, 20, 14, 26, 21, 27, 22, 23)
119+
120+
private def with2DeBruijn(v: Int) = multiplyDeBruijnBitPosition2((v * 0x077CB531) >>> 27)
121+
122+
123+
//////////////////////////////////////////////
124+
125+
@Benchmark def withBinSearch(bh: Blackhole) {
126+
for (v <- powersOfTwo) {
127+
val leadingZeros = withBinSearch(v)
128+
// assert (leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)")
129+
bh.consume(leadingZeros)
130+
}
131+
}
132+
133+
private def withBinSearch(v: Int) =
134+
if (v < 65536) if (v < 256) if (v < 16) if (v < 4) if (v == 1) 32 else 31
135+
else if (v == 4) 30 else 29
136+
else if (v < 64) if (v == 16) 28 else 27
137+
else if (v == 64) 26 else 25
138+
else if (v < 4096) if (v < 1024) if (v == 256) 24 else 23
139+
else if (v == 1024) 22 else 21
140+
else if (v < 16384) if (v == 4096) 20 else 19
141+
else if (v == 16384) 18 else 17
142+
else if (v < 16777216) if (v < 1048576) if (v < 262144) if (v == 65536) 16 else 15
143+
else if (v == 262144) 14 else 13
144+
else if (v < 4194304) if (v == 1048576) 12 else 11
145+
else if (v == 4194304) 10 else 9
146+
else if (v < 268435456) if (v < 67108864) if (v == 16777216) 8 else 7
147+
else if (v == 67108864) 6 else 5
148+
else if (v < 1073741824) if (v == 268435456) 4 else 3
149+
else if (v == 1073741824) 2 else 1
150+
151+
//////////////////////////////////////////////
152+
153+
@Benchmark def withSumBinSearch(bh: Blackhole) {
154+
for (v <- powersOfTwo) {
155+
val leadingZeros = withSumBinSearch(v)
156+
// assert(leadingZeros == withLoop(v), s"$leadingZeros != ${withLoop(v)} ($v)")
157+
bh.consume(leadingZeros)
158+
}
159+
}
160+
161+
private def withSumBinSearch(v: Int): Int = {
162+
var exponent = Integer.SIZE
163+
var remaining = v
164+
if (remaining >= 65536) { remaining >>>= 16; exponent = 16 }
165+
if (remaining >= 256) { remaining >>>= 8; exponent -= 8 }
166+
if (remaining >= 16) { remaining >>>= 4; exponent -= 4 }
167+
if (remaining >= 4) { remaining >>>= 2; exponent -= 2 }
168+
if (remaining >= 2) exponent - 1 else exponent
169+
}
170+
}

0 commit comments

Comments
 (0)