Skip to content

Commit cbc31d9

Browse files
The performance of creating new datetime formats can be improved (#585)
- Refactor `ParserStructure.concat` to streamline operation merging logic - Add `unconditionalModifications` promotion optimization - Add JMH benchmarks for common datetime formats (ISO, RFC 1123, UTC offset) - Add `ParserStructureConcatenationTest` for concat behavior verification Fixes #571
1 parent 518242b commit cbc31d9

File tree

5 files changed

+590
-55
lines changed

5 files changed

+590
-55
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright 2019-2025 JetBrains s.r.o. and 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+
@file:Suppress("unused")
7+
8+
package kotlinx.datetime
9+
10+
import kotlinx.datetime.format.*
11+
import org.openjdk.jmh.annotations.*
12+
import org.openjdk.jmh.infra.Blackhole
13+
import java.util.concurrent.*
14+
15+
@Warmup(iterations = 20, time = 2)
16+
@Measurement(iterations = 30, time = 2)
17+
@BenchmarkMode(Mode.AverageTime)
18+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
19+
@State(Scope.Benchmark)
20+
@Fork(2)
21+
open class CommonFormats {
22+
23+
@Benchmark
24+
fun buildPythonDateTimeFormat(blackhole: Blackhole) {
25+
val v = LocalDateTime.Format {
26+
year()
27+
char('-')
28+
monthNumber()
29+
char('-')
30+
day()
31+
char(' ')
32+
hour()
33+
char(':')
34+
minute()
35+
optional {
36+
char(':')
37+
second()
38+
optional {
39+
char('.')
40+
secondFraction()
41+
}
42+
}
43+
}
44+
blackhole.consume(v)
45+
}
46+
47+
@Benchmark
48+
fun buildIsoDateTimeFormat(blackhole: Blackhole) {
49+
val format = LocalDateTime.Format {
50+
date(LocalDate.Format {
51+
year()
52+
char('-')
53+
monthNumber()
54+
char('-')
55+
day()
56+
})
57+
alternativeParsing({ char('t') }) { char('T') }
58+
time(LocalTime.Format {
59+
hour()
60+
char(':')
61+
minute()
62+
alternativeParsing({}) {
63+
char(':')
64+
second()
65+
optional {
66+
char('.')
67+
secondFraction(1, 9)
68+
}
69+
}
70+
})
71+
}
72+
blackhole.consume(format)
73+
}
74+
75+
@Benchmark
76+
fun buildFourDigitsUtcOffsetFormat(blackhole: Blackhole) {
77+
val format = UtcOffset.Format {
78+
offsetHours()
79+
offsetMinutesOfHour()
80+
}
81+
blackhole.consume(format)
82+
}
83+
84+
@Benchmark
85+
fun buildRfc1123DateTimeFormat(blackhole: Blackhole) {
86+
val format = DateTimeComponents.Format {
87+
alternativeParsing({
88+
// the day of week may be missing
89+
}) {
90+
dayOfWeek(DayOfWeekNames.ENGLISH_ABBREVIATED)
91+
chars(", ")
92+
}
93+
day(Padding.NONE)
94+
char(' ')
95+
monthName(MonthNames.ENGLISH_ABBREVIATED)
96+
char(' ')
97+
year()
98+
char(' ')
99+
hour()
100+
char(':')
101+
minute()
102+
optional {
103+
char(':')
104+
second()
105+
}
106+
chars(" ")
107+
alternativeParsing({
108+
chars("UT")
109+
}, {
110+
chars("Z")
111+
}) {
112+
optional("GMT") {
113+
offset(UtcOffset.Format {
114+
offsetHours()
115+
offsetMinutesOfHour()
116+
})
117+
}
118+
}
119+
}
120+
blackhole.consume(format)
121+
}
122+
123+
@Benchmark
124+
fun buildIsoDateTimeOffsetFormat(blackhole: Blackhole) {
125+
val format = DateTimeComponents.Format {
126+
date(LocalDate.Format {
127+
year()
128+
char('-')
129+
monthNumber()
130+
char('-')
131+
day()
132+
})
133+
alternativeParsing({
134+
char('t')
135+
}) {
136+
char('T')
137+
}
138+
hour()
139+
char(':')
140+
minute()
141+
char(':')
142+
second()
143+
optional {
144+
char('.')
145+
secondFraction(1, 9)
146+
}
147+
alternativeParsing({
148+
offsetHours()
149+
}) {
150+
offset(UtcOffset.Format {
151+
alternativeParsing({ chars("z") }) {
152+
optional("Z") {
153+
offsetHours()
154+
char(':')
155+
offsetMinutesOfHour()
156+
optional {
157+
char(':')
158+
offsetSecondsOfMinute()
159+
}
160+
}
161+
}
162+
})
163+
}
164+
}
165+
blackhole.consume(format)
166+
}
167+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2019-2025 JetBrains s.r.o. and 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+
@file:Suppress("unused")
7+
8+
package kotlinx.datetime
9+
10+
import kotlinx.datetime.format.alternativeParsing
11+
import kotlinx.datetime.format.char
12+
import org.openjdk.jmh.annotations.*
13+
import org.openjdk.jmh.infra.Blackhole
14+
import java.util.concurrent.TimeUnit
15+
16+
@Warmup(iterations = 10, time = 2)
17+
@Measurement(iterations = 20, time = 2)
18+
@BenchmarkMode(Mode.AverageTime)
19+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
20+
@State(Scope.Benchmark)
21+
@Fork(1)
22+
open class ParallelFormatBenchmark {
23+
24+
@Param("2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12")
25+
var n = 0
26+
27+
@Benchmark
28+
fun formatCreationWithAlternativeParsing(blackhole: Blackhole) {
29+
val format = LocalDateTime.Format {
30+
repeat(n) {
31+
alternativeParsing(
32+
{ monthNumber() },
33+
{ day() },
34+
primaryFormat = { hour() }
35+
)
36+
char('@')
37+
minute()
38+
char('#')
39+
second()
40+
}
41+
}
42+
blackhole.consume(format)
43+
}
44+
45+
@Benchmark
46+
fun formatCreationWithNestedAlternativeParsing(blackhole: Blackhole) {
47+
val format = LocalDateTime.Format {
48+
repeat(n) { index ->
49+
alternativeParsing(
50+
{ monthNumber(); char('-'); day() },
51+
{ day(); char('/'); monthNumber() },
52+
primaryFormat = { year(); char('-'); monthNumber(); char('-'); day() }
53+
)
54+
55+
if (index and 1 == 0) {
56+
alternativeParsing(
57+
{
58+
alternativeParsing(
59+
{ hour(); char(':'); minute() },
60+
{ minute(); char(':'); second() },
61+
primaryFormat = { hour(); char(':'); minute(); char(':'); second() }
62+
)
63+
},
64+
primaryFormat = {
65+
year(); char('-'); monthNumber(); char('-'); day()
66+
char('T')
67+
hour(); char(':'); minute(); char(':'); second()
68+
}
69+
)
70+
}
71+
72+
char('|')
73+
if (index % 3 == 0) {
74+
char('|')
75+
}
76+
77+
if (index and 2 == 0) {
78+
alternativeParsing(
79+
{ char('Z') },
80+
{ char('+'); hour(); char(':'); minute() },
81+
primaryFormat = { char('-'); hour(); char(':'); minute() }
82+
)
83+
}
84+
}
85+
}
86+
blackhole.consume(format)
87+
}
88+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2019-2025 JetBrains s.r.o. and 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+
@file:Suppress("unused")
7+
8+
package kotlinx.datetime
9+
10+
import kotlinx.datetime.format.char
11+
import org.openjdk.jmh.annotations.*
12+
import org.openjdk.jmh.infra.Blackhole
13+
import java.util.concurrent.TimeUnit
14+
15+
@Warmup(iterations = 10, time = 2)
16+
@Measurement(iterations = 20, time = 2)
17+
@BenchmarkMode(Mode.AverageTime)
18+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
19+
@State(Scope.Benchmark)
20+
@Fork(1)
21+
open class SerialFormatBenchmark {
22+
23+
@Param("1", "2", "4", "8", "16", "32", "64", "128", "256", "512", "1024")
24+
var n = 0
25+
26+
@Benchmark
27+
fun largeSerialFormat(blackhole: Blackhole) {
28+
val format = LocalDateTime.Format {
29+
repeat(n) {
30+
char('^')
31+
monthNumber()
32+
char('&')
33+
day()
34+
char('!')
35+
hour()
36+
char('$')
37+
minute()
38+
char('#')
39+
second()
40+
char('@')
41+
}
42+
}
43+
blackhole.consume(format)
44+
}
45+
}

0 commit comments

Comments
 (0)