29
29
import java .util .Objects ;
30
30
import java .util .TimeZone ;
31
31
import java .util .concurrent .atomic .AtomicReference ;
32
- import java .util .stream .Collectors ;
32
+ import java .util .stream .Stream ;
33
33
import org .apache .logging .log4j .core .time .Instant ;
34
34
import org .apache .logging .log4j .core .time .MutableInstant ;
35
+ import org .apache .logging .log4j .util .BiConsumer ;
35
36
import org .apache .logging .log4j .util .Strings ;
36
37
import org .jspecify .annotations .Nullable ;
37
38
@@ -141,7 +142,7 @@ private static InstantPatternFormatter createFormatter(
141
142
142
143
// Sequence the pattern and create associated formatters
143
144
final List <PatternSequence > sequences = sequencePattern (pattern , precisionThreshold );
144
- final List < InstantPatternFormatter > formatters = sequences .stream ()
145
+ final InstantPatternFormatter [] formatters = sequences .stream ()
145
146
.map (sequence -> {
146
147
final InstantPatternFormatter formatter = sequence .createFormatter (locale , timeZone );
147
148
final boolean constant = sequence .isConstantForDurationOf (precisionThreshold );
@@ -161,9 +162,9 @@ public void formatTo(final StringBuilder buffer, final Instant instant) {
161
162
}
162
163
};
163
164
})
164
- .collect ( Collectors . toList () );
165
+ .toArray ( InstantPatternFormatter []:: new );
165
166
166
- switch (formatters .size () ) {
167
+ switch (formatters .length ) {
167
168
168
169
// If found an empty pattern, return an empty formatter
169
170
case 0 :
@@ -176,27 +177,44 @@ public void formatTo(final StringBuilder buffer, final Instant instant) {
176
177
177
178
// If extracted a single formatter, return it as is
178
179
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
+ };
180
194
181
195
// Combine all extracted formatters into one
182
196
default :
183
- final ChronoUnit precision = formatters . stream ( )
197
+ final ChronoUnit precision = Stream . of ( formatters )
184
198
.map (InstantFormatter ::getPrecision )
185
199
.min (Comparator .comparing (ChronoUnit ::getDuration ))
186
200
.get ();
187
201
return new AbstractFormatter (pattern , locale , timeZone , precision ) {
188
202
@ Override
189
203
public void formatTo (final StringBuilder buffer , final Instant instant ) {
190
204
// 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 ] ;
193
207
formatter .formatTo (buffer , instant );
194
208
}
195
209
}
196
210
};
197
211
}
198
212
}
199
213
214
+ private static ChronoUnit min (ChronoUnit left , ChronoUnit right ) {
215
+ return left .getDuration ().compareTo (right .getDuration ()) < 0 ? left : right ;
216
+ }
217
+
200
218
static List <PatternSequence > sequencePattern (final String pattern , final ChronoUnit precisionThreshold ) {
201
219
List <PatternSequence > sequences = sequencePattern (pattern );
202
220
return mergeFactories (sequences , precisionThreshold );
@@ -672,6 +690,10 @@ private static String removePadding(final String content) {
672
690
673
691
static class SecondPatternSequence extends PatternSequence {
674
692
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
+
675
697
private final boolean printSeconds ;
676
698
private final String separator ;
677
699
private final int fractionalDigits ;
@@ -711,30 +733,38 @@ private static void formatSeconds(StringBuilder buffer, Instant instant) {
711
733
}
712
734
713
735
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 ;
723
741
// 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 ));
727
746
}
728
747
}
729
748
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
+
730
758
@ Override
731
759
InstantPatternFormatter createFormatter (Locale locale , TimeZone timeZone ) {
760
+ final BiConsumer <StringBuilder , Instant > fractionDigitsFormatter =
761
+ fractionalDigits == 3 ? SecondPatternSequence ::formatMillis : this ::formatFractionalDigits ;
732
762
if (!printSeconds ) {
733
763
return new AbstractFormatter (pattern , locale , timeZone , precision ) {
734
764
@ Override
735
765
public void formatTo (StringBuilder buffer , Instant instant ) {
736
766
buffer .append (separator );
737
- formatFractionalDigits (buffer , instant );
767
+ fractionDigitsFormatter . accept (buffer , instant );
738
768
}
739
769
};
740
770
}
@@ -752,7 +782,7 @@ public void formatTo(StringBuilder buffer, Instant instant) {
752
782
public void formatTo (StringBuilder buffer , Instant instant ) {
753
783
formatSeconds (buffer , instant );
754
784
buffer .append (separator );
755
- formatFractionalDigits (buffer , instant );
785
+ fractionDigitsFormatter . accept (buffer , instant );
756
786
}
757
787
};
758
788
}
0 commit comments