Skip to content

Commit 387146a

Browse files
authored
Added ByteString benchmarks (#194)
Added benchmarks covering the following functions: - indexOf - lastIndexOf - startsWith - endsWith - hashCode - compareTo - equals Fixes #153
1 parent 3ffd474 commit 387146a

File tree

2 files changed

+297
-0
lines changed

2 files changed

+297
-0
lines changed

benchmarks/src/commonMain/kotlin/BufferOps.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package kotlinx.io.benchmarks
77

88
import kotlinx.benchmark.*
99
import kotlinx.io.*
10+
import kotlinx.io.bytestring.ByteString
1011

1112
@State(Scope.Benchmark)
1213
abstract class BufferRWBenchmarkBase {
@@ -384,3 +385,33 @@ open class BufferReadNewByteArray : BufferRWBenchmarkBase() {
384385
return buffer.readByteArray(size)
385386
}
386387
}
388+
389+
@State(Scope.Benchmark)
390+
open class IndexOfByteString {
391+
@Param("1024:2", "8192:2", "10000:2", "10000:8")
392+
var params: String = "<buffer size>:<bytestring size>"
393+
394+
private var buffer = Buffer()
395+
private var byteString = ByteString()
396+
397+
@Setup
398+
fun setup() {
399+
val paramsParsed = params.split(':').map { it.toInt() }.toIntArray()
400+
require(paramsParsed.size == 2)
401+
402+
val bufferSize = paramsParsed[0]
403+
val bsSize = paramsParsed[1]
404+
byteString = ByteString(ByteArray(bsSize) { 0x42 })
405+
406+
for (idx in 0 until bufferSize) {
407+
if (idx % bsSize == 0) {
408+
buffer.writeByte(0)
409+
} else {
410+
buffer.writeByte(0x42)
411+
}
412+
}
413+
}
414+
415+
@Benchmark
416+
fun benchmark() = buffer.indexOf(byteString)
417+
}
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/*
2+
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.io.bytestring.benchmarks
7+
8+
import kotlinx.benchmark.*
9+
import kotlinx.io.bytestring.*
10+
import kotlin.math.min
11+
12+
const val TARGET_BYTE: Byte = 42
13+
14+
@State(Scope.Benchmark)
15+
abstract class IndexOfByteBenchmarkBase {
16+
@Param("128:-1", "128:0", "128:127", "128:63")
17+
var params: String = "<byte string size>:<target byte offset, or -1>"
18+
19+
protected var byteString = ByteString()
20+
21+
@Setup
22+
fun setupByteString() {
23+
val paramsParsed = params.split(':').map { it.toInt() }.toIntArray()
24+
require(paramsParsed.size == 2)
25+
val size = paramsParsed[0]
26+
val targetByteIndex = paramsParsed[1]
27+
28+
29+
require(targetByteIndex == -1 || targetByteIndex in 0 until size)
30+
val data = ByteArray(size)
31+
if (targetByteIndex >= 0) {
32+
data[targetByteIndex] = TARGET_BYTE
33+
}
34+
byteString = ByteString(data)
35+
}
36+
}
37+
38+
@State(Scope.Benchmark)
39+
open class IndexOfByteBenchmark : IndexOfByteBenchmarkBase() {
40+
@Benchmark
41+
fun benchmark() = byteString.indexOf(TARGET_BYTE)
42+
}
43+
44+
@State(Scope.Benchmark)
45+
open class LastIndexOfByteBenchmark : IndexOfByteBenchmarkBase() {
46+
@Benchmark
47+
fun benchmark() = byteString.lastIndexOf(TARGET_BYTE)
48+
}
49+
50+
@State(Scope.Benchmark)
51+
abstract class IndexOfByteStringBase {
52+
@Param("128:8:-1", "128:129:0", "128:8:0", "128:8:120", "128:8:63")
53+
var params: String = "<byte string size>:<target byte string size>:<target byte string offset, or -1>"
54+
55+
protected var byteString = ByteString()
56+
57+
protected var targetByteString = ByteString()
58+
59+
@Setup
60+
fun setupByteString() {
61+
val paramsParsed = params.split(':').map { it.toInt() }.toIntArray()
62+
require(paramsParsed.size == 3)
63+
val size = paramsParsed[0]
64+
val patternLength = paramsParsed[1]
65+
val targetValueOffset = paramsParsed[2]
66+
67+
require(size > 0)
68+
require(targetValueOffset == -1 || targetValueOffset in 0 until size)
69+
70+
val data = ByteArray(size)
71+
if (targetValueOffset != -1) {
72+
for (idx in targetValueOffset until min(size, targetValueOffset + patternLength)) {
73+
data[idx] = TARGET_BYTE
74+
}
75+
}
76+
byteString = ByteString(data)
77+
78+
targetByteString = ByteString(ByteArray(patternLength) { TARGET_BYTE })
79+
}
80+
}
81+
82+
@State(Scope.Benchmark)
83+
open class IndexOfByteStringBenchmark : IndexOfByteStringBase() {
84+
@Benchmark
85+
fun benchmark() = byteString.indexOf(targetByteString)
86+
}
87+
88+
@State(Scope.Benchmark)
89+
open class LastIndexOfByteStringBenchmark : IndexOfByteStringBase() {
90+
@Benchmark
91+
fun benchmark() = byteString.lastIndexOf(targetByteString)
92+
}
93+
94+
@State(Scope.Benchmark)
95+
abstract class IndexOfByteStringWithRepeatedMismatchBase {
96+
@Param("128:8:2", "128:8:7")
97+
var params: String = "<string length>:<pattern length>:<mismatch stride>"
98+
99+
protected var byteString = ByteString()
100+
101+
protected var targetByteString = ByteString()
102+
103+
@Setup
104+
fun setup() {
105+
val paramsParsed = params.split(':').map { it.toInt() }.toIntArray()
106+
require(paramsParsed.size == 3)
107+
val size = paramsParsed[0]
108+
val patternLength = paramsParsed[1]
109+
val stride = paramsParsed[2]
110+
require(size > 0)
111+
require(patternLength > 0)
112+
require(stride in 1 until patternLength)
113+
114+
val data = ByteArray(size) { TARGET_BYTE }
115+
val pattern = ByteArray(patternLength) { TARGET_BYTE }
116+
for (idx in data.indices) {
117+
if (idx % stride == 0) {
118+
data[idx] = 0
119+
}
120+
}
121+
byteString = ByteString(data)
122+
targetByteString = ByteString(pattern)
123+
}
124+
}
125+
126+
@State(Scope.Benchmark)
127+
open class IndexOfByteStringWithRepeatedMismatch : IndexOfByteStringWithRepeatedMismatchBase() {
128+
@Benchmark
129+
fun benchmark() = byteString.indexOf(targetByteString)
130+
}
131+
132+
@State(Scope.Benchmark)
133+
open class LastIndexOfByteStringWithRepeatedMismatch : IndexOfByteStringWithRepeatedMismatchBase() {
134+
@Benchmark
135+
fun benchmark() = byteString.lastIndexOf(targetByteString)
136+
}
137+
138+
@State(Scope.Benchmark)
139+
140+
abstract class StartsWithBenchmarkBase {
141+
protected abstract fun getRawParams(): String
142+
143+
protected var byteString = ByteString()
144+
145+
protected var targetByteString = ByteString()
146+
147+
@Setup
148+
fun setup() {
149+
val paramsParsed = getRawParams().split(':').map { it.toInt() }.toIntArray()
150+
require(paramsParsed.size == 3)
151+
val size = paramsParsed[0]
152+
val patternLength = paramsParsed[1]
153+
val mismatchOffset = paramsParsed[2]
154+
require(size > 0)
155+
require(patternLength > 0)
156+
require(mismatchOffset == -1 || mismatchOffset in (0 until size))
157+
158+
val data = ByteArray(size)
159+
val prefix = ByteArray(patternLength)
160+
if (mismatchOffset != -1) {
161+
data[mismatchOffset] = TARGET_BYTE
162+
}
163+
byteString = ByteString(data)
164+
targetByteString = ByteString(prefix)
165+
}
166+
}
167+
168+
@State(Scope.Benchmark)
169+
open class StartsWithBenchmark : StartsWithBenchmarkBase() {
170+
@Param("128:8:-1", "128:8:0", "128:8:7")
171+
var params: String = "<string length>:<prefix/suffix length>:<mismatch offset, or -1>"
172+
173+
override fun getRawParams(): String = params
174+
175+
@Benchmark
176+
fun benchmark() = byteString.startsWith(targetByteString)
177+
}
178+
179+
@State(Scope.Benchmark)
180+
open class EndsWithBenchmark : StartsWithBenchmarkBase() {
181+
@Param("128:8:-1", "128:8:127", "128:8:120")
182+
var params: String = "<string length>:<prefix/suffix length>:<mismatch offset, or -1>"
183+
184+
override fun getRawParams(): String = params
185+
186+
@Benchmark
187+
fun benchmark() = byteString.endsWith(targetByteString)
188+
}
189+
190+
@State(Scope.Benchmark)
191+
192+
abstract class ByteStringComparisonBenchmarkBase {
193+
@Param("128")
194+
var length: Int = 0
195+
196+
@Param("-1", "63")
197+
var mismatchOffset = 0
198+
199+
protected var stringA = ByteString()
200+
protected var stringB = ByteString()
201+
202+
@Setup
203+
fun setup() {
204+
require(length > 0)
205+
require(mismatchOffset == -1 || mismatchOffset in 0 until length)
206+
207+
stringA = ByteString(ByteArray(length))
208+
stringB = ByteString(ByteArray(length).apply {
209+
if (mismatchOffset != -1) {
210+
this[mismatchOffset] = TARGET_BYTE
211+
}
212+
})
213+
}
214+
}
215+
216+
@State(Scope.Benchmark)
217+
open class CompareBenchmark : ByteStringComparisonBenchmarkBase() {
218+
@Benchmark
219+
fun benchmark() = stringA.compareTo(stringB)
220+
}
221+
222+
@State(Scope.Benchmark)
223+
open class EqualsBenchmark : ByteStringComparisonBenchmarkBase() {
224+
@Param("true", "false")
225+
var useHashCode: Boolean = false
226+
227+
@Setup
228+
fun computeHashCodes() {
229+
if (useHashCode) {
230+
stringA.hashCode()
231+
stringB.hashCode()
232+
}
233+
}
234+
235+
@Benchmark
236+
fun benchmark() = stringA == stringB
237+
}
238+
239+
@State(Scope.Benchmark)
240+
open class ByteStringHashCode {
241+
@Param("8", "128")
242+
var size: Int = 0
243+
244+
@Param("true", "false")
245+
var recomputeOnEveryCall: Boolean = false
246+
247+
private var byteString = ByteString()
248+
249+
@Setup
250+
fun setupByteString() {
251+
require(size > 0) { "Invalid byte string size: $size" }
252+
val ba = ByteArray(size)
253+
if (recomputeOnEveryCall) {
254+
ba[0] = -31
255+
check(ba.contentHashCode() == 0) { "Hash code is non zero" }
256+
} else {
257+
check(ba.contentHashCode() != 0) { "Hash code is zero" }
258+
}
259+
260+
261+
byteString = ByteString(ba)
262+
}
263+
264+
@Benchmark
265+
fun benchmark(): Int = byteString.hashCode()
266+
}

0 commit comments

Comments
 (0)