You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rp...@apache.org on 2015/08/21 19:58:54 UTC

logging-log4j2 git commit: LOG4J2-1097 bugfixes, added unit tests, updated benchmark

Repository: logging-log4j2
Updated Branches:
  refs/heads/master 2ee96915f -> 24e89e7cd


LOG4J2-1097 bugfixes, added unit tests, updated benchmark

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/24e89e7c
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/24e89e7c
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/24e89e7c

Branch: refs/heads/master
Commit: 24e89e7cd1c6909d3871b2a7518a83ae05bb91f7
Parents: 2ee9691
Author: rpopma <rp...@apache.org>
Authored: Sat Aug 22 02:59:00 2015 +0900
Committer: rpopma <rp...@apache.org>
Committed: Sat Aug 22 02:59:00 2015 +0900

----------------------------------------------------------------------
 .../core/util/datetime/CustomTimeFormat.java    |  91 ++++++-----
 .../util/datetime/CustomTimeFormatTest.java     | 154 +++++++++++++++++++
 .../log4j/perf/jmh/TimeFormatBenchmark.java     | 147 +++++++-----------
 3 files changed, 265 insertions(+), 127 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/24e89e7c/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
index 1388a90..07b730f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormat.java
@@ -26,68 +26,72 @@ import java.util.Objects;
 public class CustomTimeFormat {
     /**
      * Enumeration over the supported date/time format patterns.
+     * <p>
+     * Package protected for unit tests.
      */
-    public static enum FixedFormat {
+    static enum FixedFormat {
         /**
          * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
          */
-        ABSOLUTE("HH:mm:ss,SSS", null, ':', 1, ',', 1),
+        ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1),
 
         /**
          * ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
          */
-        ABSOLUTE2("HH:mm:ss.SSS", null, ':', 1, '0', 1),
+        ABSOLUTE2("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1),
 
         /**
          * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
          */
-        COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", ' ', 0, ' ', 0),
+        COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0),
 
         /**
          * DATE time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
          */
-        DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", ':', 1, ',', 1),
+        DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1),
 
         /**
          * DATE time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
          */
-        DATE2("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", ':', 1, '.', 1),
+        DATE2("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1),
 
         /**
          * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
          */
-        DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", ':', 1, ',', 1),
+        DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1),
 
         /**
          * DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
          */
-        DEFAULT2("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", ':', 1, '.', 1),
+        DEFAULT2("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1),
 
         /**
          * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
          */
-        ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", ' ', 0, ',', 1),
+        ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1),
 
         /**
          * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
          */
-        ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", ':', 1, ',', 1), ;
+        ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1), ;
 
         private final String pattern;
         private final String datePattern;
+        private final int escapeCount;
         private final char timeSeparatorChar;
         private final int timeSeparatorLength;
         private final char millisSeparatorChar;
         private final int millisSeparatorLength;
 
-        private FixedFormat(final String pattern, final String datePattern, char timeSeparator, int timeSepLength,
-                char millisSeparator, int millisSepLength) {
+        private FixedFormat(final String pattern, final String datePattern, final int escapeCount,
+                final char timeSeparator, final int timeSepLength, final char millisSeparator, final int millisSepLength) {
             this.timeSeparatorChar = timeSeparator;
             this.timeSeparatorLength = timeSepLength;
             this.millisSeparatorChar = millisSeparator;
             this.millisSeparatorLength = millisSepLength;
             this.pattern = Objects.requireNonNull(pattern);
             this.datePattern = datePattern; // may be null
+            this.escapeCount = escapeCount;
         }
 
         public String getPattern() {
@@ -101,8 +105,8 @@ public class CustomTimeFormat {
         /**
          * Returns the FixedFormat with the name or pattern matching the specified string or {@code null} if not found.
          */
-        public static FixedFormat lookup(String nameOrPattern) {
-            for (FixedFormat type : FixedFormat.values()) {
+        public static FixedFormat lookup(final String nameOrPattern) {
+            for (final FixedFormat type : FixedFormat.values()) {
                 if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) {
                     return type;
                 }
@@ -110,8 +114,12 @@ public class CustomTimeFormat {
             return null;
         }
 
+        public int getLength() {
+            return pattern.length() - escapeCount;
+        }
+
         public int getDatePatternLength() {
-            return getDatePattern() == null ? 0 : getDatePattern().length();
+            return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount;
         }
 
         public FastDateFormat getFastDateFormat() {
@@ -123,7 +131,7 @@ public class CustomTimeFormat {
         if (options == null || options.length == 0 || options.length > 1) {
             return null; // time zone not supported
         }
-        FixedFormat type = FixedFormat.lookup(options[0]);
+        final FixedFormat type = FixedFormat.lookup(options[0]);
         return type == null ? null : new CustomTimeFormat(type);
     }
 
@@ -138,6 +146,12 @@ public class CustomTimeFormat {
 
     private volatile long midnightToday = 0;
     private volatile long midnightTomorrow = 0;
+    // cachedDate does not need to be volatile because
+    // there is a write to a volatile field *after* cachedDate is modified,
+    // and there is a read from a volatile field *before* cachedDate is read.
+    // The Java memory model guarantees that because of the above,
+    // changes to cachedDate in one thread are visible to other threads.
+    // See http://g.oswego.edu/dl/jmm/cookbook.html
     private char[] cachedDate; // may be null
 
     /**
@@ -147,13 +161,13 @@ public class CustomTimeFormat {
      * 
      * @param type the fixed format
      */
-    CustomTimeFormat(FixedFormat type) {
+    CustomTimeFormat(final FixedFormat type) {
         this.type = Objects.requireNonNull(type);
         this.timeSeparatorChar = type.timeSeparatorChar;
         this.timeSeparatorLength = type.timeSeparatorLength;
         this.millisSeparatorChar = type.millisSeparatorChar;
         this.millisSeparatorLength = type.millisSeparatorLength;
-        this.length = type.getPattern().length();
+        this.length = type.getLength();
         this.dateLength = type.getDatePatternLength();
         this.fastDateFormat = type.getFastDateFormat();
     }
@@ -162,7 +176,8 @@ public class CustomTimeFormat {
         return type.getPattern();
     }
 
-    // 21 bytes (allows immediate JVM inlining: < 35 bytes)
+    // Profiling showed this method is important to log4j performance. Modify with care!
+    // 21 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
     private long millisSinceMidnight(final long now) {
         if (now >= midnightTomorrow) {
             updateMidnightMillis(now);
@@ -172,19 +187,15 @@ public class CustomTimeFormat {
 
     private void updateMidnightMillis(final long now) {
 
-        // cachedDate does not need to be volatile because
-        // there is a write to a volatile field *after* cachedDate is modified,
-        // and there is a read from a volatile field *before* cachedDate is read.
-        // The Java memory model guarantees that because of the above,
-        // changes to cachedDate in one thread are visible to other threads.
         updateCachedDate(now);
 
-        midnightToday = calcMidnightMillis(0);
-        midnightTomorrow = calcMidnightMillis(1);
+        midnightToday = calcMidnightMillis(now, 0);
+        midnightTomorrow = calcMidnightMillis(now, 1);
     }
 
-    private long calcMidnightMillis(final int addDays) {
+    static long calcMidnightMillis(final long time, final int addDays) {
         final Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(time);
         cal.set(Calendar.HOUR_OF_DAY, 0);
         cal.set(Calendar.MINUTE, 0);
         cal.set(Calendar.SECOND, 0);
@@ -193,41 +204,45 @@ public class CustomTimeFormat {
         return cal.getTimeInMillis();
     }
 
-    private void updateCachedDate(long now) {
+    private void updateCachedDate(final long now) {
         if (fastDateFormat != null) {
-            StringBuilder result = fastDateFormat.format(now, new StringBuilder());
+            final StringBuilder result = fastDateFormat.format(now, new StringBuilder());
             cachedDate = result.toString().toCharArray();
         }
     }
 
-    // 28 bytes (allows immediate JVM inlining: < 35 bytes)
+    // Profiling showed this method is important to log4j performance. Modify with care!
+    // 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
     public String format(final long time) {
         final char[] result = new char[length];
         int written = format(time, result, 0);
         return new String(result, 0, written);
     }
 
-    // 31 bytes (allows immediate JVM inlining: < 35 bytes)
-    public int format(final long time, final char[] buffer, int startPos) {
+    // Profiling showed this method is important to log4j performance. Modify with care!
+    // 31 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
+    public int format(final long time, final char[] buffer, final int startPos) {
         // Calculate values by getting the ms values first and do then
         // calculate the hour minute and second values divisions.
 
         // Get daytime in ms: this does fit into an int
         // int ms = (int) (time % 86400000);
         final int ms = (int) (millisSinceMidnight(time));
-        writeDate(buffer);
+        writeDate(buffer, startPos);
         return writeTime(ms, buffer, startPos + dateLength) - startPos;
     }
 
-    // 22 bytes (allows immediate JVM inlining: < 35 bytes)
-    private void writeDate(char[] buffer) {
+    // Profiling showed this method is important to log4j performance. Modify with care!
+    // 22 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
+    private void writeDate(final char[] buffer, final int startPos) {
         if (cachedDate != null) {
-            System.arraycopy(cachedDate, 0, buffer, 0, dateLength);
+            System.arraycopy(cachedDate, 0, buffer, startPos, dateLength);
         }
     }
 
-    // 262 bytes (will be inlined when hot enough: < 325 bytes)
-    private int writeTime(int ms, char[] buffer, int pos) {
+    // Profiling showed this method is important to log4j performance. Modify with care!
+    // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux)
+    private int writeTime(int ms, final char[] buffer, int pos) {
         final int hours = ms / 3600000;
         ms -= 3600000 * hours;
 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/24e89e7c/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormatTest.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormatTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormatTest.java
new file mode 100644
index 0000000..c277c5b
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/CustomTimeFormatTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.core.util.datetime;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.core.util.datetime.CustomTimeFormat.FixedFormat;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests the CustomTimeFormat class.
+ */
+public class CustomTimeFormatTest {
+
+    @Test
+    public void testFixedFormat_getDatePatternNullIfNoDateInPattern() {
+        assertNull(FixedFormat.ABSOLUTE.getDatePattern());
+        assertNull(FixedFormat.ABSOLUTE2.getDatePattern());
+    }
+
+    @Test
+    public void testFixedFormat_getDatePatternLengthZeroIfNoDateInPattern() {
+        assertEquals(0, FixedFormat.ABSOLUTE.getDatePatternLength());
+        assertEquals(0, FixedFormat.ABSOLUTE2.getDatePatternLength());
+    }
+
+    @Test
+    public void testFixedFormat_getFastDateFormatNullIfNoDateInPattern() {
+        assertNull(FixedFormat.ABSOLUTE.getFastDateFormat());
+        assertNull(FixedFormat.ABSOLUTE2.getFastDateFormat());
+    }
+
+    @Test
+    public void testFixedFormat_getDatePatternReturnsDatePatternIfExists() {
+        assertEquals("yyyyMMdd", FixedFormat.COMPACT.getDatePattern());
+        assertEquals("yyyy-MM-dd ", FixedFormat.DEFAULT.getDatePattern());
+    }
+
+    @Test
+    public void testFixedFormat_getDatePatternLengthReturnsDatePatternLength() {
+        assertEquals("yyyyMMdd".length(), FixedFormat.COMPACT.getDatePatternLength());
+        assertEquals("yyyy-MM-dd ".length(), FixedFormat.DEFAULT.getDatePatternLength());
+    }
+
+    @Test
+    public void testFixedFormat_getFastDateFormatNonNullIfDateInPattern() {
+        assertNotNull(FixedFormat.COMPACT.getFastDateFormat());
+        assertNotNull(FixedFormat.DEFAULT.getFastDateFormat());
+        assertEquals("yyyyMMdd", FixedFormat.COMPACT.getFastDateFormat().getPattern());
+        assertEquals("yyyy-MM-dd ", FixedFormat.DEFAULT.getFastDateFormat().getPattern());
+    }
+    
+    @Test
+    public void testCreateIfSupported_nonNullIfNameMatches() {
+        for (final CustomTimeFormat.FixedFormat format : CustomTimeFormat.FixedFormat.values()) {
+            final String[] options = {format.name()};
+            assertNotNull(format.name(), CustomTimeFormat.createIfSupported(options));
+        }
+    }
+
+    @Test
+    public void testCreateIfSupported_nonNullIfPatternMatches() {
+        for (final CustomTimeFormat.FixedFormat format : CustomTimeFormat.FixedFormat.values()) {
+            final String[] options = {format.getPattern()};
+            assertNotNull(format.name(), CustomTimeFormat.createIfSupported(options));
+        }
+    }
+
+    @Test
+    public void testCreateIfSupported_nullIfNameDoesNotMatch() {
+        final String[] options = {"DEFAULT3"};
+        assertNull("DEFAULT3", CustomTimeFormat.createIfSupported(options));
+    }
+
+    @Test
+    public void testCreateIfSupported_nullIfPatternDoesNotMatch() {
+        final String[] options = {"y M d H m s"};
+        assertNull("y M d H m s", CustomTimeFormat.createIfSupported(options));
+    }
+
+    @Test
+    public void testCreateIfSupported_nullIfOptionsArrayNull() {
+        assertNull("null", CustomTimeFormat.createIfSupported(null));
+    }
+
+    @Test
+    public void testCreateIfSupported_nullIfOptionsArrayHasTwoElements() {
+        final String[] options = {CustomTimeFormat.FixedFormat.ABSOLUTE.getPattern(), "+08:00"};
+        assertNull("timezone", CustomTimeFormat.createIfSupported(options));
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void testConstructorDisallowsNull() {
+        new CustomTimeFormat(null);
+    }
+
+    @Test
+    public void testGetFormatReturnsConstructorFixedFormatPattern() {
+        final CustomTimeFormat format = new CustomTimeFormat(CustomTimeFormat.FixedFormat.ABSOLUTE);
+        assertSame(CustomTimeFormat.FixedFormat.ABSOLUTE.getPattern(), format.getFormat());
+    }
+
+    @Test
+    public void testFormatLong() {
+        final long start = System.currentTimeMillis();
+        final long end = start + TimeUnit.HOURS.toMillis(25);
+        for (final FixedFormat format : FixedFormat.values()) {
+            final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern());
+            final CustomTimeFormat customTF = new CustomTimeFormat(format);
+            for (long time = start; time < end; time += 12345) {
+                final String actual = customTF.format(time);
+                final String expected = simpleDF.format(new Date(time));
+                assertEquals(format + "/" + time, expected, actual);
+            }
+        }
+    }
+
+    @Test
+    public void testFormatLongCharArrayInt() {
+        final long start = System.currentTimeMillis();
+        final long end = start + TimeUnit.HOURS.toMillis(25);
+        final char[] buffer = new char[128];
+        for (final FixedFormat format : FixedFormat.values()) {
+            final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern());
+            final CustomTimeFormat customTF = new CustomTimeFormat(format);
+            for (long time = start; time < end; time += 12345) {
+                final int length = customTF.format(time, buffer, 23);
+                final String actual = new String(buffer, 23, length);
+                final String expected = simpleDF.format(new Date(time));
+                assertEquals(format + "/" + time, expected, actual);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/24e89e7c/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
----------------------------------------------------------------------
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
index 6813ca1..1d6f4ff 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
@@ -18,12 +18,12 @@
 package org.apache.logging.log4j.perf.jmh;
 
 import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.logging.log4j.core.util.datetime.CustomTimeFormat;
 import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
@@ -38,7 +38,7 @@ import org.openjdk.jmh.annotations.State;
 // ============================== HOW TO RUN THIS TEST: ====================================
 //
 // single thread:
-// java -jar log4j-perf/target/benchmarks.jar ".*TimeFormat.*" -f 1 -wi 5 -i 5
+// java -jar log4j-perf/target/benchmarks.jar ".*TimeFormat.*" -f 1 -wi 5 -i 10
 //
 // multiple threads (for example, 4 threads):
 // java -jar log4j-perf/target/benchmarks.jar ".*TimeFormat.*" -f 1 -wi 5 -i 5 -t 4 -si true
@@ -55,28 +55,29 @@ public class TimeFormatBenchmark {
             return new SimpleDateFormat("HH:mm:ss.SSS");
         }
     };
-    // SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
     FastDateFormat fastDateFormat = FastDateFormat.getInstance("HH:mm:ss.SSS");
+    CustomTimeFormat customTimeFormat = CustomTimeFormat.createIfSupported(new String[]{"ABSOLUTE"});
     volatile long midnightToday = 0;
     volatile long midnightTomorrow = 0;
 
     @State(Scope.Thread)
     public static class BufferState {
-        ByteBuffer buffer = ByteBuffer.allocate(12);
-        StringBuilder stringBuilder = new StringBuilder(12);
+        final ByteBuffer buffer = ByteBuffer.allocate(12);
+        final StringBuilder stringBuilder = new StringBuilder(12);
+        final char[] charArray = new char[12];
     }
 
     private long millisSinceMidnight(final long now) {
         if (now >= midnightTomorrow) {
-            midnightToday = calcMidnightMillis(0);
-            midnightTomorrow = calcMidnightMillis(1);
+            midnightToday = calcMidnightMillis(now, 0);
+            midnightTomorrow = calcMidnightMillis(now, 1);
         }
         return now - midnightToday;
     }
 
-    private long calcMidnightMillis(final int addDays) {
-        // Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UCT"));
+    private long calcMidnightMillis(final long time, final int addDays) {
         final Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(time);
         cal.set(Calendar.HOUR_OF_DAY, 0);
         cal.set(Calendar.MINUTE, 0);
         cal.set(Calendar.SECOND, 0);
@@ -86,98 +87,66 @@ public class TimeFormatBenchmark {
     }
 
     public static void main(final String[] args) {
-        System.out.println(new TimeFormatBenchmark().customBitFiddlingFormatString(new BufferState()));
-        System.out.println(new TimeFormatBenchmark().customFormatString(new BufferState()));
+        System.out.println(new TimeFormatBenchmark().customBitFiddlingReuseCharArray(new BufferState()));
+        System.out.println(new TimeFormatBenchmark().customFormatReuseStringBuilder(new BufferState()));
     }
 
     @Benchmark
     @BenchmarkMode(Mode.SampleTime)
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public void baseline() {
-    }
-
-    @Benchmark
-    @BenchmarkMode(Mode.SampleTime)
-    @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public String simpleDateFormatString() {
+    public String simpleDateFormat() {
         return threadLocalSimpleDateFormat.get().format(new Date());
     }
 
     @Benchmark
     @BenchmarkMode(Mode.SampleTime)
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public int simpleDateFormatBytes(final BufferState state) {
-        final String str = threadLocalSimpleDateFormat.get().format(new Date());
-        final byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
-        state.buffer.clear();
-        state.buffer.put(bytes);
-        return state.buffer.position();
-    }
-
-    @Benchmark
-    @BenchmarkMode(Mode.SampleTime)
-    @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public String fastDateFormatString() {
+    public String fastDateFormatCreateNewStringBuilder() {
         return fastDateFormat.format(new Date());
     }
 
     @Benchmark
     @BenchmarkMode(Mode.SampleTime)
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public int fastDateFormatBytes(final BufferState state) {
-        final String str = fastDateFormat.format(new Date());
-        final byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
-        state.buffer.clear();
-        state.buffer.put(bytes);
-        return state.buffer.position();
+    public String fastDateFormatReuseStringBuilder(final BufferState state) {
+        state.stringBuilder.setLength(0);
+        fastDateFormat.format(new Date(), state.stringBuilder);
+        return new String(state.stringBuilder);
     }
 
     @Benchmark
     @BenchmarkMode(Mode.SampleTime)
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public String customBitFiddlingFormatString(final BufferState state) {
-        state.buffer.clear();
-        fastFormat(System.currentTimeMillis(), state.buffer);
-        return new String(state.buffer.array(), 0, state.buffer.position(), StandardCharsets.UTF_8);
+    public String customBitFiddlingReuseCharArray(final BufferState state) {
+        final int len = formatCharArrayBitFiddling(System.currentTimeMillis(), state.charArray, 0);
+        return new String(state.charArray, 0, len);
     }
 
     @Benchmark
     @BenchmarkMode(Mode.SampleTime)
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public int customBitFiddlingFormatBytes(final BufferState state) {
-        state.buffer.clear();
-        fastFormat(System.currentTimeMillis(), state.buffer);
-        return state.buffer.position();
+    public String customTimeFormatCreateNewCharArray(final BufferState state) {
+        return customTimeFormat.format(System.currentTimeMillis());
     }
 
     @Benchmark
     @BenchmarkMode(Mode.SampleTime)
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public String customFormatString(final BufferState state) {
-        state.stringBuilder.setLength(0);
-        formatText(System.currentTimeMillis(), state.stringBuilder);
-        return new String(state.stringBuilder);
+    public String customTimeFormatReuseCharArray(final BufferState state) {
+        final int len = customTimeFormat.format(System.currentTimeMillis(), state.charArray, 0);
+        return new String(state.charArray, 0, len);
     }
 
     @Benchmark
     @BenchmarkMode(Mode.SampleTime)
     @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public int customFormatStringBuilder(final BufferState state) {
+    public String customFormatReuseStringBuilder(final BufferState state) {
         state.stringBuilder.setLength(0);
-        formatText(System.currentTimeMillis(), state.stringBuilder);
-        return state.stringBuilder.length();
-    }
-
-    @Benchmark
-    @BenchmarkMode(Mode.SampleTime)
-    @OutputTimeUnit(TimeUnit.NANOSECONDS)
-    public int customFormatBytes(final BufferState state) {
-        state.buffer.clear();
-        format(System.currentTimeMillis(), state.buffer);
-        return state.buffer.position();
+        formatStringBuilder(System.currentTimeMillis(), state.stringBuilder);
+        return new String(state.stringBuilder);
     }
 
-    public ByteBuffer fastFormat(final long time, final ByteBuffer buffer) {
+    int formatCharArrayBitFiddling(final long time, final char[] buffer, int pos) {
         // Calculate values by getting the ms values first and do then
         // shave off the hour minute and second values with multiplications
         // and bit shifts instead of simple but expensive divisions.
@@ -199,43 +168,43 @@ public class TimeFormatBenchmark {
         // Hour
         // 13/128 is nearly the same as /10 for values up to 65
         int temp = (hour * 13) >> 7;
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         // Do subtract to get remainder instead of doing % 10
-        buffer.put((byte) (hour - 10 * temp + '0'));
-        buffer.put((byte) ':');
+        buffer[pos++] = ((char) (hour - 10 * temp + '0'));
+        buffer[pos++] = ((char) ':');
 
         // Minute
         // 13/128 is nearly the same as /10 for values up to 65
         temp = (minute * 13) >> 7;
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         // Do subtract to get remainder instead of doing % 10
-        buffer.put((byte) (minute - 10 * temp + '0'));
-        buffer.put((byte) ':');
+        buffer[pos++] = ((char) (minute - 10 * temp + '0'));
+        buffer[pos++] = ((char) ':');
 
         // Second
         // 13/128 is nearly the same as /10 for values up to 65
         temp = (second * 13) >> 7;
-        buffer.put((byte) (temp + '0'));
-        buffer.put((byte) (second - 10 * temp + '0'));
-        buffer.put((byte) '.');
+        buffer[pos++] = ((char) (temp + '0'));
+        buffer[pos++] = ((char) (second - 10 * temp + '0'));
+        buffer[pos++] = ((char) '.');
 
         // Millisecond
         // 41/4096 is nearly the same as /100
         temp = (ms * 41) >> 12;
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         ms -= 100 * temp;
         temp = (ms * 205) >> 11; // 205/2048 is nearly the same as /10
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         ms -= 10 * temp;
-        buffer.put((byte) (ms + '0'));
-        return buffer;
+        buffer[pos++] = ((char) (ms + '0'));
+        return pos;
     }
 
-    public StringBuilder formatText(final long time, final StringBuilder buffer) {
+    StringBuilder formatStringBuilder(final long time, final StringBuilder buffer) {
         // Calculate values by getting the ms values first and do then
         // calculate the hour minute and second values divisions.
 
@@ -287,7 +256,7 @@ public class TimeFormatBenchmark {
         return buffer;
     }
 
-    public ByteBuffer format(final long time, final ByteBuffer buffer) {
+    int formatCharArray(final long time, final char[] buffer, int pos) {
         // Calculate values by getting the ms values first and do then
         // calculate the hour minute and second values divisions.
 
@@ -306,36 +275,36 @@ public class TimeFormatBenchmark {
 
         // Hour
         int temp = hours / 10;
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         // Do subtract to get remainder instead of doing % 10
-        buffer.put((byte) (hours - 10 * temp + '0'));
-        buffer.put((byte) ':');
+        buffer[pos++] = ((char) (hours - 10 * temp + '0'));
+        buffer[pos++] = ((char) ':');
 
         // Minute
         temp = minutes / 10;
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         // Do subtract to get remainder instead of doing % 10
-        buffer.put((byte) (minutes - 10 * temp + '0'));
-        buffer.put((byte) ':');
+        buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
+        buffer[pos++] = ((char) ':');
 
         // Second
         temp = seconds / 10;
-        buffer.put((byte) (temp + '0'));
-        buffer.put((byte) (seconds - 10 * temp + '0'));
-        buffer.put((byte) '.');
+        buffer[pos++] = ((char) (temp + '0'));
+        buffer[pos++] = ((char) (seconds - 10 * temp + '0'));
+        buffer[pos++] = ((char) '.');
 
         // Millisecond
         temp = ms / 100;
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         ms -= 100 * temp;
         temp = ms / 10;
-        buffer.put((byte) (temp + '0'));
+        buffer[pos++] = ((char) (temp + '0'));
 
         ms -= 10 * temp;
-        buffer.put((byte) (ms + '0'));
-        return buffer;
+        buffer[pos++] = ((char) (ms + '0'));
+        return pos;
     }
 }