Skip to content

Commit 2bb9a5b

Browse files
authored
Merge pull request #74 from phearnot/byte-array-compare-speedup
Use UnsignedBytes comparator from Guava
2 parents 83babe0 + 486e9f3 commit 2bb9a5b

File tree

2 files changed

+90
-15
lines changed

2 files changed

+90
-15
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package scorex.benchmarks
2+
3+
import java.util.concurrent.TimeUnit
4+
5+
import com.google.common.primitives.Shorts
6+
import org.openjdk.jmh.annotations._
7+
import org.openjdk.jmh.infra.Blackhole
8+
import scorex.utils.ByteArray
9+
10+
import scala.util.Random
11+
12+
13+
object ByteArrayComparePerformance {
14+
15+
def compare(buffer1: Array[Byte], buffer2: Array[Byte]): Int = if (buffer1 sameElements buffer2) {
16+
0
17+
} else {
18+
val end1: Int = if (buffer1.length < buffer2.length) buffer1.length else buffer2.length
19+
var i: Int = 0
20+
while (i < end1) {
21+
val a: Int = buffer1(i) & 0xff
22+
val b: Int = buffer2(i) & 0xff
23+
if (a != b) {
24+
return a - b
25+
}
26+
i = i + 1
27+
}
28+
buffer1.length - buffer2.length
29+
}
30+
31+
trait BenchmarkState {
32+
def vectors: IndexedSeq[Array[Byte]]
33+
}
34+
35+
@State(Scope.Thread)
36+
class Random32Bytes extends BenchmarkState {
37+
val vectors: IndexedSeq[Array[Byte]] = (0 to 1000).map { _ =>
38+
val bs = new Array[Byte](32)
39+
Random.nextBytes(bs)
40+
bs
41+
}
42+
}
43+
44+
@State(Scope.Thread)
45+
class WorstCase32Bytes extends BenchmarkState {
46+
val vectors: IndexedSeq[Array[Byte]] = (0 to 1000).map { i =>
47+
new Array[Byte](30) ++ Shorts.toByteArray(i.toShort)
48+
}
49+
}
50+
51+
def compareWithUnsignedBytes(state: BenchmarkState, bh: Blackhole): Unit =
52+
state.vectors.indices.init foreach { i =>
53+
bh.consume(ByteArray.compare(state.vectors(i), state.vectors(i + 1)))
54+
}
55+
56+
def legacyCompare(state: BenchmarkState, bh: Blackhole): Unit =
57+
state.vectors.indices.init foreach { i =>
58+
bh.consume(compare(state.vectors(i), state.vectors(i + 1)))
59+
}
60+
}
61+
62+
63+
@BenchmarkMode(Array(Mode.Throughput))
64+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
65+
@Fork(1)
66+
class ByteArrayComparePerformance {
67+
68+
import ByteArrayComparePerformance._
69+
70+
@Benchmark
71+
def compareWithUnsignedBytesRandom(state: Random32Bytes, bh: Blackhole): Unit =
72+
compareWithUnsignedBytes(state, bh)
73+
74+
@Benchmark
75+
def legacyCompareRandom(state: Random32Bytes, bh: Blackhole): Unit =
76+
legacyCompare(state, bh)
77+
78+
@Benchmark
79+
def compareWithUnsignedBytesWorstCase(state: WorstCase32Bytes, bh: Blackhole): Unit =
80+
compareWithUnsignedBytes(state, bh)
81+
82+
@Benchmark
83+
def legacyCompareWorstCase(state: WorstCase32Bytes, bh: Blackhole): Unit =
84+
legacyCompare(state, bh)
85+
86+
}

src/main/scala/scorex/utils/ByteArray.scala

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
11
package scorex.utils
22

3+
import com.google.common.primitives.UnsignedBytes
4+
35
object ByteArray {
46

5-
def compare(buffer1: Array[Byte], buffer2: Array[Byte]): Int = if (buffer1 sameElements buffer2) {
6-
0
7-
} else {
8-
val end1: Int = if (buffer1.length < buffer2.length) buffer1.length else buffer2.length
9-
var i: Int = 0
10-
while (i < end1) {
11-
val a: Int = buffer1(i) & 0xff
12-
val b: Int = buffer2(i) & 0xff
13-
if (a != b) {
14-
return a - b
15-
}
16-
i = i + 1
17-
}
18-
buffer1.length - buffer2.length
19-
}
7+
def compare(buffer1: Array[Byte], buffer2: Array[Byte]): Int =
8+
UnsignedBytes.lexicographicalComparator().compare(buffer1, buffer2)
209

2110
def concat(seq: Traversable[Array[Byte]]): Array[Byte] = {
2211
val length: Int = seq.map(_.length).sum

0 commit comments

Comments
 (0)