Skip to content

Commit 0b198fc

Browse files
committed
Improve performance
1 parent cb1a159 commit 0b198fc

File tree

1 file changed

+52
-22
lines changed

1 file changed

+52
-22
lines changed

log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatter.java

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
import java.util.Objects;
3030
import java.util.TimeZone;
3131
import java.util.concurrent.atomic.AtomicReference;
32-
import java.util.stream.Collectors;
32+
import java.util.stream.Stream;
3333
import org.apache.logging.log4j.core.time.Instant;
3434
import org.apache.logging.log4j.core.time.MutableInstant;
35+
import org.apache.logging.log4j.util.BiConsumer;
3536
import org.apache.logging.log4j.util.Strings;
3637
import org.jspecify.annotations.Nullable;
3738

@@ -141,7 +142,7 @@ private static InstantPatternFormatter createFormatter(
141142

142143
// Sequence the pattern and create associated formatters
143144
final List<PatternSequence> sequences = sequencePattern(pattern, precisionThreshold);
144-
final List<InstantPatternFormatter> formatters = sequences.stream()
145+
final InstantPatternFormatter[] formatters = sequences.stream()
145146
.map(sequence -> {
146147
final InstantPatternFormatter formatter = sequence.createFormatter(locale, timeZone);
147148
final boolean constant = sequence.isConstantForDurationOf(precisionThreshold);
@@ -161,9 +162,9 @@ public void formatTo(final StringBuilder buffer, final Instant instant) {
161162
}
162163
};
163164
})
164-
.collect(Collectors.toList());
165+
.toArray(InstantPatternFormatter[]::new);
165166

166-
switch (formatters.size()) {
167+
switch (formatters.length) {
167168

168169
// If found an empty pattern, return an empty formatter
169170
case 0:
@@ -176,27 +177,44 @@ public void formatTo(final StringBuilder buffer, final Instant instant) {
176177

177178
// If extracted a single formatter, return it as is
178179
case 1:
179-
return formatters.get(0);
180+
return formatters[0];
181+
182+
// Profiling shows that unrolling the generic loop boosts performance
183+
case 2:
184+
final InstantPatternFormatter first = formatters[0];
185+
final InstantPatternFormatter second = formatters[1];
186+
return new AbstractFormatter(
187+
pattern, locale, timeZone, min(first.getPrecision(), second.getPrecision())) {
188+
@Override
189+
public void formatTo(StringBuilder buffer, Instant instant) {
190+
first.formatTo(buffer, instant);
191+
second.formatTo(buffer, instant);
192+
}
193+
};
180194

181195
// Combine all extracted formatters into one
182196
default:
183-
final ChronoUnit precision = formatters.stream()
197+
final ChronoUnit precision = Stream.of(formatters)
184198
.map(InstantFormatter::getPrecision)
185199
.min(Comparator.comparing(ChronoUnit::getDuration))
186200
.get();
187201
return new AbstractFormatter(pattern, locale, timeZone, precision) {
188202
@Override
189203
public void formatTo(final StringBuilder buffer, final Instant instant) {
190204
// noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
191-
for (int formatterIndex = 0; formatterIndex < formatters.size(); formatterIndex++) {
192-
final InstantPatternFormatter formatter = formatters.get(formatterIndex);
205+
for (int formatterIndex = 0; formatterIndex < formatters.length; formatterIndex++) {
206+
final InstantPatternFormatter formatter = formatters[formatterIndex];
193207
formatter.formatTo(buffer, instant);
194208
}
195209
}
196210
};
197211
}
198212
}
199213

214+
private static ChronoUnit min(ChronoUnit left, ChronoUnit right) {
215+
return left.getDuration().compareTo(right.getDuration()) < 0 ? left : right;
216+
}
217+
200218
static List<PatternSequence> sequencePattern(final String pattern, final ChronoUnit precisionThreshold) {
201219
List<PatternSequence> sequences = sequencePattern(pattern);
202220
return mergeFactories(sequences, precisionThreshold);
@@ -672,6 +690,10 @@ private static String removePadding(final String content) {
672690

673691
static class SecondPatternSequence extends PatternSequence {
674692

693+
private static final int[] POWERS_OF_TEN = {
694+
100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1
695+
};
696+
675697
private final boolean printSeconds;
676698
private final String separator;
677699
private final int fractionalDigits;
@@ -711,30 +733,38 @@ private static void formatSeconds(StringBuilder buffer, Instant instant) {
711733
}
712734

713735
private void formatFractionalDigits(StringBuilder buffer, Instant instant) {
714-
final int offset = buffer.length();
715-
buffer.setLength(offset + fractionalDigits);
716-
long value = instant.getNanoOfSecond();
717-
int valuePrecision = 9;
718-
// Skip digits beyond the requested precision
719-
while (fractionalDigits < valuePrecision) {
720-
valuePrecision--;
721-
value = value / 10L;
722-
}
736+
int nanos = instant.getNanoOfSecond();
737+
// digits contain the first idx digits.
738+
int digits;
739+
// moreDigits contains the first (idx + 1) digits
740+
int moreDigits = 0;
723741
// Print the digits
724-
while (0 < valuePrecision--) {
725-
buffer.setCharAt(offset + valuePrecision, (char) ('0' + value % 10L));
726-
value = value / 10L;
742+
for (int idx = 0; idx < fractionalDigits; idx++) {
743+
digits = moreDigits;
744+
moreDigits = nanos / POWERS_OF_TEN[idx];
745+
buffer.append((char) ('0' + moreDigits - 10 * digits));
727746
}
728747
}
729748

749+
private static void formatMillis(StringBuilder buffer, Instant instant) {
750+
int ms = instant.getNanoOfSecond() / 1_000_000;
751+
int cs = ms / 10;
752+
int ds = cs / 10;
753+
buffer.append((char) ('0' + ds));
754+
buffer.append((char) ('0' + cs - 10 * ds));
755+
buffer.append((char) ('0' + ms - 10 * cs));
756+
}
757+
730758
@Override
731759
InstantPatternFormatter createFormatter(Locale locale, TimeZone timeZone) {
760+
final BiConsumer<StringBuilder, Instant> fractionDigitsFormatter =
761+
fractionalDigits == 3 ? SecondPatternSequence::formatMillis : this::formatFractionalDigits;
732762
if (!printSeconds) {
733763
return new AbstractFormatter(pattern, locale, timeZone, precision) {
734764
@Override
735765
public void formatTo(StringBuilder buffer, Instant instant) {
736766
buffer.append(separator);
737-
formatFractionalDigits(buffer, instant);
767+
fractionDigitsFormatter.accept(buffer, instant);
738768
}
739769
};
740770
}
@@ -752,7 +782,7 @@ public void formatTo(StringBuilder buffer, Instant instant) {
752782
public void formatTo(StringBuilder buffer, Instant instant) {
753783
formatSeconds(buffer, instant);
754784
buffer.append(separator);
755-
formatFractionalDigits(buffer, instant);
785+
fractionDigitsFormatter.accept(buffer, instant);
756786
}
757787
};
758788
}

0 commit comments

Comments
 (0)