2929import java .util .Objects ;
3030import java .util .TimeZone ;
3131import java .util .concurrent .atomic .AtomicReference ;
32- import java .util .stream .Collectors ;
32+ import java .util .stream .Stream ;
3333import org .apache .logging .log4j .core .time .Instant ;
3434import org .apache .logging .log4j .core .time .MutableInstant ;
35+ import org .apache .logging .log4j .util .BiConsumer ;
3536import org .apache .logging .log4j .util .Strings ;
3637import 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