This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/master by this push:
     new 791bf1e  LOG4J2-3393 Improve JsonTemplateLayout performance. (#797)
791bf1e is described below

commit 791bf1e81e49c867b6f4119d03c372e4f1b51638
Author: Volkan Yazıcı <[email protected]>
AuthorDate: Mon Mar 21 10:48:41 2022 +0100

    LOG4J2-3393 Improve JsonTemplateLayout performance. (#797)
    
    * Replace hardcoded `8 * 1024` private value used for `byteBufferSize`
      in `StringBuilderEncoder()` ctor with
      `Constants.ENCODER_BYTE_BUFFER_SIZE`.
    
    * Replace `StringBuilderEncoder` (uses an internal thread-local) and
      `LockingStringBuilderEncoder` (uses locks) with custom
      `JsonTemplateLayout.StringBuilderEncoder` class implementing
      `Encoder<StringBuilder>` interface. This was made possible by making
      `TextEncoderHelper.encodeText()` public. This makes use of JTL
      recyclers and provides better allocation performance. For instance,
      if JTL is configured with thread-local recycler, each encoding
      request will trigger a single TLA, whereas previously it needed to
      do two: one for `JsonTemplateLayout.Context` and
      `StringBuilderEncoder`.
    
    * Add `CharSequence` specialization in `MessageResolver`.
    
    * Improve troubleshooting experience by replacing lambdas with classes
      in `TemplateResolvers`.
    
    * Improve locality and branching in `TemplateResolvers`.
    
    * Improve JMH tests.
    
    * Thanks so much to Carter Kozak for being the awesome sparring
      partner.
---
 .github/workflows/benchmark.yml                    |   4 +-
 .../log4j/core/layout/StringBuilderEncoder.java    |   9 +-
 .../log4j/core/layout/TextEncoderHelper.java       |   6 +-
 .../layout/template/json/JsonTemplateLayout.java   | 109 +++---
 .../template/json/resolver/MessageResolver.java    |   8 +-
 .../template/json/resolver/PatternResolver.java    |   2 +-
 .../template/json/resolver/TemplateResolvers.java  | 390 +++++++++++++++------
 .../layout/template/json/util/JsonWriter.java      |  28 +-
 .../json/BlackHoleByteBufferDestination.java       |  12 +-
 .../layout/template/json/util/JsonWriterTest.java  |   2 +-
 log4j-perf/pom.xml                                 |   4 +-
 .../template/json/JsonTemplateLayoutBenchmark.java | 110 +++---
 .../json/JsonTemplateLayoutBenchmarkState.java     |  64 ++--
 13 files changed, 493 insertions(+), 255 deletions(-)

diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index 6f9de13..58eacb8 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -67,8 +67,8 @@ jobs:
         jdk: [ 11, 17 ]
         concurrency: [ 1, 8 ]
         jmhCommand:
-          - "-t $CONCURRENCY -f 3 -wi 3 -w 10s -i 4 -r 20s -prof gc -rf json 
-rff results-layout-jtl.json '.*JsonTemplateLayoutBenchmark.*'"
-          - "-t $CONCURRENCY -f 3 -wi 3 -w 10s -i 4 -r 20s -prof gc -rf json 
-rff results-util-instant-format.json '.*InstantFormatBenchmark.*'"
+          - "-t $CONCURRENCY -f 3 -wi 3 -w 10s -i 4 -r 20s -prof gc -prof 
perfnorm -rf json -rff results-layout-jtl.json 
'.*JsonTemplateLayoutBenchmark.*Jtl4EcsLayout'"
+          - "-t $CONCURRENCY -f 3 -wi 3 -w 10s -i 4 -r 20s -prof gc -prof 
perfnorm -rf json -rff results-util-instant-format.json 
'.*InstantFormatBenchmark.*'"
 
     steps:
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
index fb02393..9af3316 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
@@ -31,7 +31,6 @@ import org.apache.logging.log4j.status.StatusLogger;
  */
 public class StringBuilderEncoder implements Encoder<StringBuilder> {
 
-    private static final int DEFAULT_BYTE_BUFFER_SIZE = 8 * 1024;
     /**
      * This ThreadLocal uses raw and inconvenient Object[] to store three 
heterogeneous objects (CharEncoder, CharBuffer
      * and ByteBuffer) instead of a custom class, because it needs to contain 
JDK classes, no custom (Log4j) classes.
@@ -49,7 +48,7 @@ public class StringBuilderEncoder implements 
Encoder<StringBuilder> {
     private final int byteBufferSize;
 
     public StringBuilderEncoder(final Charset charset) {
-        this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE, 
DEFAULT_BYTE_BUFFER_SIZE);
+        this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE, 
Constants.ENCODER_BYTE_BUFFER_SIZE);
     }
 
     public StringBuilderEncoder(final Charset charset, final int 
charBufferSize, final int byteBufferSize) {
@@ -67,7 +66,7 @@ public class StringBuilderEncoder implements 
Encoder<StringBuilder> {
             final ByteBuffer byteBuffer = (ByteBuffer) threadLocalState[2];
             TextEncoderHelper.encodeText(charsetEncoder, charBuffer, 
byteBuffer, source, destination);
         } catch (final Exception ex) {
-            logEncodeTextException(ex, source, destination);
+            logEncodeTextException(ex, source);
             TextEncoderHelper.encodeTextFallBack(charset, source, destination);
         }
     }
@@ -90,8 +89,8 @@ public class StringBuilderEncoder implements 
Encoder<StringBuilder> {
         return threadLocalState;
     }
 
-    private void logEncodeTextException(final Exception ex, final 
StringBuilder text,
-            final ByteBufferDestination destination) {
+    private static void logEncodeTextException(final Exception ex, final 
StringBuilder text) {
         StatusLogger.getLogger().error("Recovering from 
StringBuilderEncoder.encode('{}') error: {}", text, ex, ex);
     }
+
 }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java
index 327386f..4191f57 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java
@@ -48,11 +48,9 @@ public class TextEncoderHelper {
      * @param byteBuf thread-local buffer to temporarily hold converted bytes 
before copying them to the destination
      * @param text the text to convert and write to the destination
      * @param destination the destination to write the bytes to
-     * @throws CharacterCodingException if conversion failed
      */
-    static void encodeText(final CharsetEncoder charsetEncoder, final 
CharBuffer charBuf, final ByteBuffer byteBuf,
-            final StringBuilder text, final ByteBufferDestination destination)
-            throws CharacterCodingException {
+    public static void encodeText(final CharsetEncoder charsetEncoder, final 
CharBuffer charBuf, final ByteBuffer byteBuf,
+            final StringBuilder text, final ByteBufferDestination destination) 
{
         charsetEncoder.reset();
         if (text.length() > charBuf.capacity()) {
             encodeChunkedText(charsetEncoder, charBuf, byteBuf, text, 
destination);
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
index dc0c94e..d3792fd 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
@@ -24,18 +24,10 @@ import 
org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.layout.ByteBufferDestination;
 import org.apache.logging.log4j.core.layout.Encoder;
-import org.apache.logging.log4j.core.layout.LockingStringBuilderEncoder;
-import org.apache.logging.log4j.core.layout.StringBuilderEncoder;
+import org.apache.logging.log4j.core.layout.TextEncoderHelper;
 import org.apache.logging.log4j.core.util.Constants;
 import org.apache.logging.log4j.core.util.StringEncoder;
-import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
-import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactories;
-import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory;
-import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptor;
-import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptors;
-import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverStringSubstitutor;
-import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver;
-import 
org.apache.logging.log4j.layout.template.json.resolver.TemplateResolvers;
+import org.apache.logging.log4j.layout.template.json.resolver.*;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import org.apache.logging.log4j.layout.template.json.util.Recycler;
 import org.apache.logging.log4j.layout.template.json.util.RecyclerFactory;
@@ -44,9 +36,14 @@ import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Strings;
 
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -71,8 +68,7 @@ public class JsonTemplateLayout implements StringLayout {
 
     private final Recycler<Context> contextRecycler;
 
-    // The class and fields are visible for tests.
-    static final class Context implements AutoCloseable {
+    private static final class Context implements AutoCloseable {
 
         final JsonWriter jsonWriter;
 
@@ -204,19 +200,57 @@ public class JsonTemplateLayout implements StringLayout {
             final JsonWriter jsonWriter) {
         return () -> {
             final JsonWriter clonedJsonWriter = jsonWriter.clone();
-            final Encoder<StringBuilder> encoder = 
createStringBuilderEncoder(charset);
+            final Encoder<StringBuilder> encoder = new 
StringBuilderEncoder(charset);
             return new Context(clonedJsonWriter, encoder);
         };
     }
 
-    private static Encoder<StringBuilder> createStringBuilderEncoder(
-            final Charset charset) {
-        if (Constants.ENABLE_DIRECT_ENCODERS) {
-            return Constants.ENABLE_THREADLOCALS
-                    ? new StringBuilderEncoder(charset)
-                    : new LockingStringBuilderEncoder(charset);
+    /**
+     * {@link org.apache.logging.log4j.core.layout.StringBuilderEncoder} clone 
replacing thread-local allocations with instance fields.
+     */
+    private static final class StringBuilderEncoder implements 
Encoder<StringBuilder> {
+
+        private final Charset charset;
+
+        private final CharsetEncoder charsetEncoder;
+
+        private final CharBuffer charBuffer;
+
+        private final ByteBuffer byteBuffer;
+
+        private StringBuilderEncoder(final Charset charset) {
+            this.charset = charset;
+            this.charsetEncoder = charset
+                    .newEncoder()
+                    .onMalformedInput(CodingErrorAction.REPLACE)
+                    .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            this.charBuffer = 
CharBuffer.allocate(Constants.ENCODER_CHAR_BUFFER_SIZE);
+            this.byteBuffer = 
ByteBuffer.allocate(Constants.ENCODER_BYTE_BUFFER_SIZE);
         }
-        return null;
+
+        @Override
+        public void encode(
+                final StringBuilder source,
+                final ByteBufferDestination destination) {
+            try {
+                TextEncoderHelper.encodeText(charsetEncoder, charBuffer, 
byteBuffer, source, destination);
+            } catch (final Exception error) {
+                fallbackEncode(charset, source, destination, error);
+            }
+        }
+
+        private /* for JIT-ergonomics: */ static void fallbackEncode(
+                final Charset charset,
+                final StringBuilder source,
+                final ByteBufferDestination destination,
+                final Exception error) {
+            StatusLogger
+                    .getLogger()
+                    .error("TextEncoderHelper.encodeText() failure", error);
+            final byte[] bytes = source.toString().getBytes(charset);
+            destination.writeBytes(bytes, 0, bytes.length);
+        }
+
     }
 
     @Override
@@ -227,42 +261,42 @@ public class JsonTemplateLayout implements StringLayout {
 
     @Override
     public String toSerializable(final LogEvent event) {
-        final Context context = acquireContext();
+
+        // Acquire a context.
+        final Recycler<Context> contextRecycler = this.contextRecycler;
+        final Context context = contextRecycler.acquire();
         final JsonWriter jsonWriter = context.jsonWriter;
         final StringBuilder stringBuilder = jsonWriter.getStringBuilder();
+
+        // Render the JSON.
         try {
             eventResolver.resolve(event, jsonWriter);
             stringBuilder.append(eventDelimiter);
             return stringBuilder.toString();
-        } finally {
+        }
+
+        // Release the context.
+        finally {
             contextRecycler.release(context);
         }
+
     }
 
     @Override
     public void encode(final LogEvent event, final ByteBufferDestination 
destination) {
 
         // Acquire a context.
-        final Context context = acquireContext();
+        final Recycler<Context> contextRecycler = this.contextRecycler;
+        final Context context = contextRecycler.acquire();
         final JsonWriter jsonWriter = context.jsonWriter;
         final StringBuilder stringBuilder = jsonWriter.getStringBuilder();
         final Encoder<StringBuilder> encoder = context.encoder;
 
+        // Render & write the JSON.
         try {
-
-            // Render the JSON.
             eventResolver.resolve(event, jsonWriter);
             stringBuilder.append(eventDelimiter);
-
-            // Write to the destination.
-            if (encoder == null) {
-                final String eventJson = stringBuilder.toString();
-                final byte[] eventJsonBytes = StringEncoder.toBytes(eventJson, 
charset);
-                destination.writeBytes(eventJsonBytes, 0, 
eventJsonBytes.length);
-            } else {
-                encoder.encode(stringBuilder, destination);
-            }
-
+            encoder.encode(stringBuilder, destination);
         }
 
         // Release the context.
@@ -272,11 +306,6 @@ public class JsonTemplateLayout implements StringLayout {
 
     }
 
-    // Visible for tests.
-    Context acquireContext() {
-        return contextRecycler.acquire();
-    }
-
     @Override
     public byte[] getFooter() {
         return null;
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java
index 2975e66..59afef0 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageResolver.java
@@ -134,10 +134,10 @@ public final class MessageResolver implements 
EventResolver {
             jsonWriter.writeObjectStart();
             jsonWriter.writeObjectKey(fallbackKey);
         }
-        if (message instanceof StringBuilderFormattable) {
-            final StringBuilderFormattable formattable =
-                    (StringBuilderFormattable) message;
-            jsonWriter.writeString(formattable);
+        if (message instanceof CharSequence) {
+            jsonWriter.writeString((CharSequence) message);
+        } else if (message instanceof StringBuilderFormattable) {
+            jsonWriter.writeString((StringBuilderFormattable) message);
         } else {
             final String formattedMessage = message.getFormattedMessage();
             jsonWriter.writeString(formattedMessage);
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java
index 5eddb0f..83f87d1 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PatternResolver.java
@@ -20,10 +20,10 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.layout.PatternLayout;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.util.BiConsumer;
 import org.apache.logging.log4j.util.Strings;
 
 import java.util.Optional;
+import java.util.function.BiConsumer;
 
 /**
  * Resolver delegating to {@link PatternLayout}.
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java
index 7390091..71a9c4d 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TemplateResolvers.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Main class for compiling {@link TemplateResolver}s from a template.
@@ -50,30 +51,44 @@ public final class TemplateResolvers {
     }
 
     private static final TemplateResolver<?> EMPTY_ARRAY_RESOLVER =
-            new UnresolvableTemplateResolver() {
-                @Override
-                public void resolve(final Object value, final JsonWriter 
jsonWriter) {
-                    jsonWriter.writeArrayStart();
-                    jsonWriter.writeArrayEnd();
-                }
-            };
+            new EmptyArrayResolver();
+
+    private static final class EmptyArrayResolver
+            extends UnresolvableTemplateResolver {
+
+        @Override
+        public void resolve(final Object value, final JsonWriter jsonWriter) {
+            jsonWriter.writeArrayStart();
+            jsonWriter.writeArrayEnd();
+        }
+
+    }
 
     private static final TemplateResolver<?> EMPTY_OBJECT_RESOLVER =
-            new UnresolvableTemplateResolver() {
-                @Override
-                public void resolve(final Object value, final JsonWriter 
jsonWriter) {
-                    jsonWriter.writeObjectStart();
-                    jsonWriter.writeObjectEnd();
-                }
-            };
+            new EmptyObjectResolver();
 
-    private static final TemplateResolver<?> NULL_RESOLVER =
-            new UnresolvableTemplateResolver() {
-                @Override
-                public void resolve(final Object value, final JsonWriter 
jsonWriter) {
-                    jsonWriter.writeNull();
-                }
-            };
+    private static final class EmptyObjectResolver
+            extends UnresolvableTemplateResolver {
+
+        @Override
+        public void resolve(final Object value, final JsonWriter jsonWriter) {
+            jsonWriter.writeObjectStart();
+            jsonWriter.writeObjectEnd();
+        }
+
+    }
+
+    private static final TemplateResolver<?> NULL_RESOLVER = new 
NullResolver();
+
+    private static final class NullResolver
+            extends UnresolvableTemplateResolver {
+
+        @Override
+        public void resolve(final Object value, final JsonWriter jsonWriter) {
+            jsonWriter.writeNull();
+        }
+
+    }
 
     public static <V, C extends TemplateResolverContext<V, C>> 
TemplateResolver<V> ofTemplate(
             final C context,
@@ -145,7 +160,7 @@ public final class TemplateResolvers {
             final C context,
             final List<Object> list) {
 
-        // Create resolver for each children.
+        // Create resolver for each child.
         final List<TemplateResolver<V>> itemResolvers = list
                 .stream()
                 .map(item -> {
@@ -167,7 +182,20 @@ public final class TemplateResolvers {
         }
 
         // Create a parent resolver collecting each child resolver execution.
-        return (final V value, final JsonWriter jsonWriter) -> {
+        return new ArrayResolver<>(itemResolvers);
+
+    }
+
+    private static final class ArrayResolver<V> implements TemplateResolver<V> 
{
+
+        private final List<TemplateResolver<V>> itemResolvers;
+
+        private ArrayResolver(final List<TemplateResolver<V>> itemResolvers) {
+            this.itemResolvers = itemResolvers;
+        }
+
+        @Override
+        public void resolve(final V value, final JsonWriter jsonWriter) {
             jsonWriter.writeArrayStart();
             for (int itemResolverIndex = 0;
                  itemResolverIndex < itemResolvers.size();
@@ -175,11 +203,12 @@ public final class TemplateResolvers {
                 if (itemResolverIndex > 0) {
                     jsonWriter.writeSeparator();
                 }
-                final TemplateResolver<V> itemResolver = 
itemResolvers.get(itemResolverIndex);
+                final TemplateResolver<V> itemResolver =
+                        itemResolvers.get(itemResolverIndex);
                 itemResolver.resolve(value, jsonWriter);
             }
             jsonWriter.writeArrayEnd();
-        };
+        }
 
     }
 
@@ -192,6 +221,28 @@ public final class TemplateResolvers {
             return ofResolver(context, map);
         }
 
+        // Collect field resolver contexts.
+        List<FieldResolverContext<V>> fieldResolverContexts =
+                populateFieldResolverMethods(context, map);
+
+        // Short-circuit if the object is empty.
+        final int fieldCount = fieldResolverContexts.size();
+        if (fieldCount == 0) {
+            @SuppressWarnings("unchecked")
+            final TemplateResolver<V> emptyObjectResolver =
+                    (TemplateResolver<V>) EMPTY_OBJECT_RESOLVER;
+            return emptyObjectResolver;
+        }
+
+        // Create the resolver.
+        return new MapResolver<>(fieldResolverContexts);
+
+    }
+
+    private static <V, C extends TemplateResolverContext<V, C>> 
List<FieldResolverContext<V>> populateFieldResolverMethods(
+            final C context,
+            final Map<String, Object> map) {
+
         // Create resolver for each object field.
         final List<String> fieldNames = new ArrayList<>();
         final List<TemplateResolver<V>> fieldResolvers = new ArrayList<>();
@@ -204,15 +255,6 @@ public final class TemplateResolvers {
             }
         });
 
-        // Short-circuit if the object is empty.
-        final int fieldCount = fieldNames.size();
-        if (fieldCount == 0) {
-            @SuppressWarnings("unchecked")
-            final TemplateResolver<V> emptyObjectResolver =
-                    (TemplateResolver<V>) EMPTY_OBJECT_RESOLVER;
-            return emptyObjectResolver;
-        }
-
         // Prepare field names to avoid escape and truncation costs at runtime.
         final List<String> fieldPrefixes = fieldNames
                 .stream()
@@ -225,70 +267,152 @@ public final class TemplateResolvers {
                 })
                 .collect(Collectors.toList());
 
-        return new TemplateResolver<V>() {
-
-            @Override
-            public boolean isResolvable() {
-                // We have already excluded unresolvable ones while collecting
-                // the resolvers. Hence it is safe to return true here.
-                return true;
-            }
-
-            /**
-             * The parent resolver checking if each child is resolvable given
-             * the passed {@code value}.
-             *
-             * This is an optimization to skip the rendering of a parent if all
-             * its children are not resolvable given the passed {@code value}.
-             */
-            @Override
-            public boolean isResolvable(final V value) {
-                for (int fieldIndex = 0; fieldIndex < fieldCount; 
fieldIndex++) {
-                    final TemplateResolver<V> fieldResolver = 
fieldResolvers.get(fieldIndex);
-                    final boolean resolvable = 
fieldResolver.isResolvable(value);
-                    if (resolvable) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-
-            /**
-             * The parent resolver combining all child resolver executions.
-              */
-            @Override
-            public void resolve(final V value, final JsonWriter jsonWriter) {
-                final StringBuilder jsonWriterStringBuilder = 
jsonWriter.getStringBuilder();
-                jsonWriter.writeObjectStart();
-                for (int resolvedFieldCount = 0, fieldIndex = 0; fieldIndex < 
fieldCount; fieldIndex++) {
+        // Collect field resolver contexts.
+        final int fieldCount = fieldNames.size();
+        return IntStream
+                .range(0, fieldCount)
+                .mapToObj(fieldIndex -> {
                     final TemplateResolver<V> fieldResolver = 
fieldResolvers.get(fieldIndex);
-                    final boolean resolvable = 
fieldResolver.isResolvable(value);
-                    if (!resolvable) {
-                        continue;
-                    }
-                    final boolean succeedingEntry = resolvedFieldCount > 0;
+                    final FieldResolverMethod<V> fieldResolverMethod;
                     final boolean flattening = fieldResolver.isFlattening();
                     if (flattening) {
-                        final int initLength = 
jsonWriterStringBuilder.length();
-                        fieldResolver.resolve(value, jsonWriter, 
succeedingEntry);
-                        final boolean resolved = 
jsonWriterStringBuilder.length() > initLength;
-                        if (resolved) {
-                            resolvedFieldCount++;
-                        }
+                        fieldResolverMethod = new 
FlatteningFieldResolverMethod<>(fieldResolver);
                     } else {
-                        if (succeedingEntry) {
-                            jsonWriter.writeSeparator();
-                        }
                         final String fieldPrefix = 
fieldPrefixes.get(fieldIndex);
-                        jsonWriter.writeRawString(fieldPrefix);
-                        fieldResolver.resolve(value, jsonWriter, 
succeedingEntry);
-                        resolvedFieldCount++;
+                        fieldResolverMethod = new 
PrefixedFieldResolverMethod<>(fieldPrefix, fieldResolver);
                     }
+                    return new FieldResolverContext<>(fieldResolver, 
fieldResolverMethod);
+                })
+                .collect(Collectors.toList());
+
+    }
+
+    private static final class FieldResolverContext<V> {
+
+        private final TemplateResolver<V> resolver;
+
+        private final FieldResolverMethod<V> resolverMethod;
+
+        private FieldResolverContext(final TemplateResolver<V> resolver, final 
FieldResolverMethod<V> resolverMethod) {
+            this.resolver = resolver;
+            this.resolverMethod = resolverMethod;
+        }
+
+    }
+
+    @FunctionalInterface
+    private interface FieldResolverMethod<V> {
+
+        boolean resolve(V value, JsonWriter jsonWriter, boolean 
succeedingEntry);
+
+    }
+
+    private static final class FlatteningFieldResolverMethod<V> implements 
FieldResolverMethod<V> {
+
+        private final TemplateResolver<V> fieldResolver;
+
+        private FlatteningFieldResolverMethod(final TemplateResolver<V> 
fieldResolver) {
+            this.fieldResolver = fieldResolver;
+        }
+
+        @Override
+        public boolean resolve(final V value, final JsonWriter jsonWriter, 
final boolean succeedingEntry) {
+            final boolean resolvable = fieldResolver.isResolvable(value);
+            if (!resolvable) {
+                return false;
+            }
+            final StringBuilder jsonWriterStringBuilder = 
jsonWriter.getStringBuilder();
+            final int initLength = jsonWriterStringBuilder.length();
+            fieldResolver.resolve(value, jsonWriter, succeedingEntry);
+            return jsonWriterStringBuilder.length() > initLength;
+        }
+
+    }
+
+    private static final class PrefixedFieldResolverMethod<V> implements 
FieldResolverMethod<V> {
+
+        private final String fieldPrefix;
+
+        private final TemplateResolver<V> fieldResolver;
+
+        private PrefixedFieldResolverMethod(final String fieldPrefix, final 
TemplateResolver<V> fieldResolver) {
+            this.fieldPrefix = fieldPrefix;
+            this.fieldResolver = fieldResolver;
+        }
+
+        @Override
+        public boolean resolve(final V value, final JsonWriter jsonWriter, 
final boolean succeedingEntry) {
+            final boolean resolvable = fieldResolver.isResolvable(value);
+            if (!resolvable) {
+                return false;
+            }
+            if (succeedingEntry) {
+                jsonWriter.writeSeparator();
+            }
+            jsonWriter.writeRawString(fieldPrefix);
+            fieldResolver.resolve(value, jsonWriter, succeedingEntry);
+            return true;
+        }
+
+    }
+
+    private static final class MapResolver<V> implements TemplateResolver<V> {
+
+        private final List<FieldResolverContext<V>> fieldResolverContexts;
+
+        private MapResolver(final List<FieldResolverContext<V>> 
fieldResolverContexts) {
+            this.fieldResolverContexts = fieldResolverContexts;
+        }
+
+        @Override
+        public boolean isResolvable() {
+            // We have already excluded unresolvable ones while collecting
+            // the resolvers; it is safe to return true here.
+            return true;
+        }
+
+        /**
+         * The parent resolver checking if each child is resolvable given
+         * the passed {@code value}.
+         *
+         * This is an optimization to skip the rendering of a parent if all
+         * its children are not resolvable for the given {@code value}.
+         */
+        @Override
+        public boolean isResolvable(final V value) {
+            int fieldCount = fieldResolverContexts.size();
+            // noinspection ForLoopReplaceableByForEach (avoid iterator 
instantiation)
+            for (int fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
+                final TemplateResolver<V> fieldResolver = 
fieldResolverContexts.get(fieldIndex).resolver;
+                final boolean resolvable = fieldResolver.isResolvable(value);
+                if (resolvable) {
+                    return true;
                 }
-                jsonWriter.writeObjectEnd();
             }
+            return false;
+        }
 
-        };
+        /**
+         * The parent resolver combining all child resolver executions.
+         */
+        @Override
+        public void resolve(final V value, final JsonWriter jsonWriter) {
+            jsonWriter.writeObjectStart();
+            int fieldCount = fieldResolverContexts.size();
+            for (int resolvedFieldCount = 0, fieldIndex = 0; fieldIndex < 
fieldCount; fieldIndex++) {
+                FieldResolverContext<V> fieldResolverContext = 
fieldResolverContexts.get(fieldIndex);
+                final boolean resolvable = 
fieldResolverContext.resolver.isResolvable(value);
+                if (!resolvable) {
+                    continue;
+                }
+                final boolean succeedingEntry = resolvedFieldCount > 0;
+                final boolean resolved = 
fieldResolverContext.resolverMethod.resolve(value, jsonWriter, succeedingEntry);
+                if (resolved) {
+                    resolvedFieldCount++;
+                }
+            }
+            jsonWriter.writeObjectEnd();
+        }
 
     }
 
@@ -344,18 +468,14 @@ public final class TemplateResolvers {
                             contextJsonWriter.use(() ->
                                     
contextJsonWriter.writeString(replacedText));
                     // Create a resolver dedicated to the escaped replacement.
-                    return (final V value, final JsonWriter jsonWriter) ->
-                            jsonWriter.writeRawString(escapedReplacedText);
+                    return new RawStringResolver<>(escapedReplacedText);
                 }
             }
 
             // Otherwise, the unstable substitutor needs to be invoked always 
at
             // runtime.
             else {
-                return (final V value, final JsonWriter jsonWriter) -> {
-                    final String replacedText = substitutor.replace(value, 
fieldValue);
-                    jsonWriter.writeString(replacedText);
-                };
+                return new SubstitutingStringResolver<>(substitutor, 
fieldValue);
             }
 
         }
@@ -365,21 +485,87 @@ public final class TemplateResolvers {
             final String escapedFieldValue =
                     contextJsonWriter.use(() ->
                             contextJsonWriter.writeString(fieldValue));
-            return (final V value, final JsonWriter jsonWriter) ->
-                    jsonWriter.writeRawString(escapedFieldValue);
+            return new RawStringResolver<>(escapedFieldValue);
+        }
+
+    }
+
+    private static final class SubstitutingStringResolver<V>
+            implements TemplateResolver<V> {
+
+        private final TemplateResolverStringSubstitutor<V> substitutor;
+
+        private final String string;
+
+        private SubstitutingStringResolver(
+                final TemplateResolverStringSubstitutor<V> substitutor,
+                final String string) {
+            this.substitutor = substitutor;
+            this.string = string;
+        }
+
+        @Override
+        public void resolve(final V value, final JsonWriter jsonWriter) {
+            final String replacedString = substitutor.replace(value, string);
+            jsonWriter.writeString(replacedString);
+        }
+
+    }
+
+    private static final class RawStringResolver<V>
+            implements TemplateResolver<V> {
+
+        private final String rawString;
+
+        private RawStringResolver(final String rawString) {
+            this.rawString = rawString;
+        }
+
+        @Override
+        public void resolve(final V ignored, final JsonWriter jsonWriter) {
+            jsonWriter.writeRawString(rawString);
         }
 
     }
 
     private static <V> TemplateResolver<V> ofNumber(final Number number) {
-        final String numberString = String.valueOf(number);
-        return (final V ignored, final JsonWriter jsonWriter) ->
-                jsonWriter.writeRawString(numberString);
+        return new NumberResolver<>(number);
+    }
+
+    private static final class NumberResolver<V>
+            implements TemplateResolver<V> {
+
+        private final String numberString;
+
+        private NumberResolver(final Number number) {
+            this.numberString = String.valueOf(number);
+        }
+
+        @Override
+        public void resolve(final V ignored, final JsonWriter jsonWriter) {
+            jsonWriter.writeRawString(numberString);
+        }
+
     }
 
     private static <V> TemplateResolver<V> ofBoolean(final boolean value) {
-        return (final V ignored, final JsonWriter jsonWriter) ->
-                jsonWriter.writeBoolean(value);
+        return new BooleanResolver<>(value);
+    }
+
+    private static final class BooleanResolver<V>
+            implements TemplateResolver<V> {
+
+        private final boolean value;
+
+        private BooleanResolver(final boolean value) {
+            this.value = value;
+        }
+
+        @Override
+        public void resolve(final V ignored, final JsonWriter jsonWriter) {
+            jsonWriter.writeBoolean(value);
+        }
+
     }
 
 }
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java
index 8fe9acf..82d0a7e 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java
@@ -16,7 +16,6 @@
  */
 package org.apache.logging.log4j.layout.template.json.util;
 
-import org.apache.logging.log4j.util.BiConsumer;
 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
 import org.apache.logging.log4j.util.StringBuilderFormattable;
 import org.apache.logging.log4j.util.StringMap;
@@ -27,6 +26,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.BiConsumer;
 
 /**
  * A simple JSON writer with support for common Java data types.
@@ -234,22 +234,24 @@ public final class JsonWriter implements AutoCloseable, 
Cloneable {
         } else {
             writeObjectStart();
             final boolean[] firstEntry = {true};
-            map.forEach((final String key, final Object value) -> {
-                if (key == null) {
-                    throw new IllegalArgumentException("null keys are not 
allowed");
-                }
-                if (firstEntry[0]) {
-                    firstEntry[0] = false;
-                } else {
-                    writeSeparator();
-                }
-                writeObjectKey(key);
-                writeValue(value);
-            });
+            map.forEach(this::writeStringMap, firstEntry);
             writeObjectEnd();
         }
     }
 
+    private void writeStringMap(final String key, final Object value, final 
boolean[] firstEntry) {
+        if (key == null) {
+            throw new IllegalArgumentException("null keys are not allowed");
+        }
+        if (firstEntry[0]) {
+            firstEntry[0] = false;
+        } else {
+            writeSeparator();
+        }
+        writeObjectKey(key);
+        writeValue(value);
+    }
+
     public void writeObject(final IndexedReadOnlyStringMap map) {
         if (map == null) {
             writeNull();
diff --git 
a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/BlackHoleByteBufferDestination.java
 
b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/BlackHoleByteBufferDestination.java
index 3c0f4fb..0fab5ab 100644
--- 
a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/BlackHoleByteBufferDestination.java
+++ 
b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/BlackHoleByteBufferDestination.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.layout.template.json;
 
 import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper;
 
 import java.nio.ByteBuffer;
 
@@ -35,16 +36,23 @@ class BlackHoleByteBufferDestination implements 
ByteBufferDestination {
 
     @Override
     public ByteBuffer drain(final ByteBuffer byteBuffer) {
+        byteBuffer.flip();
+        if (this.byteBuffer != byteBuffer) {
+            this.byteBuffer.clear();
+            this.byteBuffer.put(byteBuffer);
+        }
         byteBuffer.clear();
         return byteBuffer;
     }
 
     @Override
     public void writeBytes(final ByteBuffer byteBuffer) {
-        byteBuffer.clear();
+        ByteBufferDestinationHelper.writeToUnsynchronized(byteBuffer, this);
     }
 
     @Override
-    public void writeBytes(final byte[] buffer, final int offset, final int 
length) {}
+    public void writeBytes(final byte[] buffer, final int offset, final int 
length) {
+        ByteBufferDestinationHelper.writeToUnsynchronized(buffer, offset, 
length, this);
+    }
 
 }
diff --git 
a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
 
b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
index c519f75..5ce6e1f 100644
--- 
a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
+++ 
b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
@@ -18,7 +18,6 @@ package org.apache.logging.log4j.layout.template.json.util;
 
 import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
 import org.apache.logging.log4j.layout.template.json.JacksonFixture;
-import org.apache.logging.log4j.util.BiConsumer;
 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
 import org.apache.logging.log4j.util.SortedArrayStringMap;
 import org.apache.logging.log4j.util.StringBuilderFormattable;
@@ -39,6 +38,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
diff --git a/log4j-perf/pom.xml b/log4j-perf/pom.xml
index 2cd8304..6263e7f 100644
--- a/log4j-perf/pom.xml
+++ b/log4j-perf/pom.xml
@@ -33,7 +33,7 @@
     <log4jParentDir>${basedir}/..</log4jParentDir>
     <docLabel>Apache Log4J Performance Tests</docLabel>
     <projectDir>/log4j-perf</projectDir>
-    <jmh.version>1.21</jmh.version>
+    <jmh.version>1.34</jmh.version>
     <javac.target>1.7</javac.target>
     <uberjar.name>benchmarks</uberjar.name>
     <revapi.skip>true</revapi.skip>
@@ -185,7 +185,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-shade-plugin</artifactId>
-        <version>2.2</version>
+        <version>3.2.4</version>
         <executions>
           <execution>
             <phase>package</phase>
diff --git 
a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmark.java
 
b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmark.java
index 1099625..f7ef08e 100644
--- 
a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmark.java
+++ 
b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmark.java
@@ -28,158 +28,164 @@ import java.util.List;
  * Benchmark suite for various JSON layouts.
  * <p>
  * You can run this test as follows:
- * <pre>
+ * <pre>{@code
  * java \
  *     -jar log4j-perf/target/benchmarks.jar \
  *     -f 2 \
  *     -wi 3 -w 20s \
  *     -i 5 -r 30s \
  *     -prof gc \
+ *     -prof perfnorm \
+ *     -prof "async:libPath=/path/to/libasyncProfiler.so;output=flamegraph" \
  *     -rf json -rff log4j-perf/target/JsonTemplateLayoutBenchmarkResult.json \
  *     ".*JsonTemplateLayoutBenchmark.*"
- * </pre>
+ * }</pre>
+ * </p>
  */
 public class JsonTemplateLayoutBenchmark {
 
     @Benchmark
-    public static int fullJsonTemplateLayout4JsonLayout(
+    public static int fullJtl4JsonLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
-                state.getJsonTemplateLayout4JsonLayout(),
-                state.getFullLogEvents(),
-                state.getByteBufferDestination());
+                state,
+                state.getJtl4JsonLayout(),
+                state.getFullLogEvents());
     }
 
     @Benchmark
-    public static int liteJsonTemplateLayout4JsonLayout(
+    public static int liteJtl4JsonLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
-                state.getJsonTemplateLayout4JsonLayout(),
-                state.getLiteLogEvents(),
-                state.getByteBufferDestination());
+                state,
+                state.getJtl4JsonLayout(),
+                state.getLiteLogEvents());
     }
 
     @Benchmark
-    public static int fullJsonTemplateLayout4EcsLayout(
+    public static int fullJtl4EcsLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
-                state.getJsonTemplateLayout4EcsLayout(),
-                state.getFullLogEvents(),
-                state.getByteBufferDestination());
+                state,
+                state.getJtl4EcsLayout(),
+                state.getFullLogEvents());
     }
 
     @Benchmark
-    public static int liteJsonTemplateLayout4EcsLayout(
+    public static int liteJtl4EcsLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
-                state.getJsonTemplateLayout4EcsLayout(),
-                state.getLiteLogEvents(),
-                state.getByteBufferDestination());
+                state,
+                state.getJtl4EcsLayout(),
+                state.getLiteLogEvents());
     }
 
     @Benchmark
-    public static int fullJsonTemplateLayout4GelfLayout(
+    public static int fullJtl4GelfLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
-                state.getJsonTemplateLayout4GelfLayout(),
-                state.getFullLogEvents(),
-                state.getByteBufferDestination());
+                state,
+                state.getJtl4GelfLayout(),
+                state.getFullLogEvents());
     }
 
     @Benchmark
-    public static int liteJsonTemplateLayout4GelfLayout(
+    public static int liteJtl4GelfLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
-                state.getJsonTemplateLayout4GelfLayout(),
-                state.getLiteLogEvents(),
-                state.getByteBufferDestination());
+                state,
+                state.getJtl4GelfLayout(),
+                state.getLiteLogEvents());
     }
 
     @Benchmark
     public static int fullDefaultJsonLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getDefaultJsonLayout(),
-                state.getFullLogEvents(),
-                state.getByteBufferDestination());
+                state.getFullLogEvents());
     }
 
     @Benchmark
     public static int liteDefaultJsonLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getDefaultJsonLayout(),
-                state.getLiteLogEvents(),
-                state.getByteBufferDestination());
+                state.getLiteLogEvents());
     }
 
     @Benchmark
     public static int fullCustomJsonLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getCustomJsonLayout(),
-                state.getFullLogEvents(),
-                state.getByteBufferDestination());
+                state.getFullLogEvents());
     }
 
     @Benchmark
     public static int liteCustomJsonLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getCustomJsonLayout(),
-                state.getLiteLogEvents(),
-                state.getByteBufferDestination());
+                state.getLiteLogEvents());
     }
 
     @Benchmark
     public static int fullEcsLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getEcsLayout(),
-                state.getFullLogEvents(),
-                state.getByteBufferDestination());
+                state.getFullLogEvents());
     }
 
     @Benchmark
     public static int liteEcsLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getEcsLayout(),
-                state.getLiteLogEvents(),
-                state.getByteBufferDestination());
+                state.getLiteLogEvents());
     }
 
     @Benchmark
     public static int fullGelfLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getGelfLayout(),
-                state.getFullLogEvents(),
-                state.getByteBufferDestination());
+                state.getFullLogEvents());
     }
 
     @Benchmark
     public static int liteGelfLayout(
             final JsonTemplateLayoutBenchmarkState state) {
         return benchmark(
+                state,
                 state.getGelfLayout(),
-                state.getLiteLogEvents(),
-                state.getByteBufferDestination());
+                state.getLiteLogEvents());
     }
 
     private static int benchmark(
-            final Layout<String> layout,
-            final List<LogEvent> logEvents,
+            final JsonTemplateLayoutBenchmarkState state,
+            final Layout<?> layout,
+            final List<LogEvent> logEvents) {
+        final int logEventIndex = state.nextLogEventIndex();
+        final LogEvent logEvent = logEvents.get(logEventIndex);
+        return benchmark(layout, logEvent, state.getByteBufferDestination());
+    }
+
+    private static int benchmark(
+            final Layout<?> layout,
+            final LogEvent logEvent,
             final ByteBufferDestination destination) {
-        // noinspection ForLoopReplaceableByForEach (avoid iterator 
instantiation)
-        for (int logEventIndex = 0; logEventIndex < logEvents.size(); 
logEventIndex++) {
-            LogEvent logEvent = logEvents.get(logEventIndex);
-            layout.encode(logEvent, destination);
-        }
         final ByteBuffer byteBuffer = destination.getByteBuffer();
-        final int position = byteBuffer.position();
-        byteBuffer.clear();
-        return position;
+        layout.encode(logEvent, destination);
+        return byteBuffer.position();
     }
 
 }
diff --git 
a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
 
b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
index 0a408a7..8edeac7 100644
--- 
a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
+++ 
b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutBenchmarkState.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.layout.template.json;
 
 import co.elastic.logging.log4j2.EcsLayout;
+import org.apache.logging.log4j.core.Layout;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.DefaultConfiguration;
@@ -34,48 +35,51 @@ import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 
-@State(Scope.Benchmark)
+@State(Scope.Thread)
 public class JsonTemplateLayoutBenchmarkState {
 
     private static final Configuration CONFIGURATION = new 
DefaultConfiguration();
 
     private static final Charset CHARSET = StandardCharsets.UTF_8;
 
+    private static final int LOG_EVENT_COUNT = 1_000;
+
     private final ByteBufferDestination byteBufferDestination;
 
-    private final JsonTemplateLayout jsonTemplateLayout4JsonLayout;
+    private final Layout<?> jtl4JsonLayout;
 
-    private final JsonTemplateLayout jsonTemplateLayout4EcsLayout;
+    private final Layout<?> jtl4EcsLayout;
 
-    private final JsonTemplateLayout jsonTemplateLayout4GelfLayout;
+    private final Layout<?> jtl4GelfLayout;
 
-    private final JsonLayout defaultJsonLayout;
+    private final Layout<?> defaultJsonLayout;
 
-    private final JsonLayout customJsonLayout;
+    private final Layout<?> customJsonLayout;
 
-    private final EcsLayout ecsLayout;
+    private final Layout<?> ecsLayout;
 
-    private final GelfLayout gelfLayout;
+    private final Layout<?> gelfLayout;
 
     private final List<LogEvent> fullLogEvents;
 
     private final List<LogEvent> liteLogEvents;
 
+    private int logEventIndex = 0;
+
     public JsonTemplateLayoutBenchmarkState() {
         this.byteBufferDestination = new BlackHoleByteBufferDestination(1024 * 
512);
-        this.jsonTemplateLayout4JsonLayout = 
createJsonTemplateLayout4JsonLayout();
-        this.jsonTemplateLayout4EcsLayout = 
createJsonTemplateLayout4EcsLayout();
-        this.jsonTemplateLayout4GelfLayout = 
createJsonTemplateLayout4GelfLayout();
+        this.jtl4JsonLayout = createJtl4JsonLayout();
+        this.jtl4EcsLayout = createJtl4EcsLayout();
+        this.jtl4GelfLayout = createJtl4GelfLayout();
         this.defaultJsonLayout = createDefaultJsonLayout();
         this.customJsonLayout = createCustomJsonLayout();
         this.ecsLayout = createEcsLayout();
         this.gelfLayout = createGelfLayout();
-        int logEventCount = 1_000;
-        this.fullLogEvents = 
LogEventFixture.createFullLogEvents(logEventCount);
-        this.liteLogEvents = 
LogEventFixture.createLiteLogEvents(logEventCount);
+        this.fullLogEvents = 
LogEventFixture.createFullLogEvents(LOG_EVENT_COUNT);
+        this.liteLogEvents = 
LogEventFixture.createLiteLogEvents(LOG_EVENT_COUNT);
     }
 
-    private static JsonTemplateLayout createJsonTemplateLayout4JsonLayout() {
+    private static JsonTemplateLayout createJtl4JsonLayout() {
         return JsonTemplateLayout
                 .newBuilder()
                 .setConfiguration(CONFIGURATION)
@@ -85,7 +89,7 @@ public class JsonTemplateLayoutBenchmarkState {
                 .build();
     }
 
-    private static JsonTemplateLayout createJsonTemplateLayout4EcsLayout() {
+    private static JsonTemplateLayout createJtl4EcsLayout() {
         final EventTemplateAdditionalField[] additionalFields =
                 new EventTemplateAdditionalField[]{
                         EventTemplateAdditionalField
@@ -104,7 +108,7 @@ public class JsonTemplateLayoutBenchmarkState {
                 .build();
     }
 
-    private static JsonTemplateLayout createJsonTemplateLayout4GelfLayout() {
+    private static JsonTemplateLayout createJtl4GelfLayout() {
         return JsonTemplateLayout
                 .newBuilder()
                 .setConfiguration(CONFIGURATION)
@@ -173,31 +177,31 @@ public class JsonTemplateLayoutBenchmarkState {
         return byteBufferDestination;
     }
 
-    JsonTemplateLayout getJsonTemplateLayout4JsonLayout() {
-        return jsonTemplateLayout4JsonLayout;
+    Layout<?> getJtl4JsonLayout() {
+        return jtl4JsonLayout;
     }
 
-    JsonTemplateLayout getJsonTemplateLayout4EcsLayout() {
-        return jsonTemplateLayout4EcsLayout;
+    Layout<?> getJtl4EcsLayout() {
+        return jtl4EcsLayout;
     }
 
-    JsonTemplateLayout getJsonTemplateLayout4GelfLayout() {
-        return jsonTemplateLayout4GelfLayout;
+    Layout<?> getJtl4GelfLayout() {
+        return jtl4GelfLayout;
     }
 
-    JsonLayout getDefaultJsonLayout() {
+    Layout<?> getDefaultJsonLayout() {
         return defaultJsonLayout;
     }
 
-    JsonLayout getCustomJsonLayout() {
+    Layout<?> getCustomJsonLayout() {
         return customJsonLayout;
     }
 
-    EcsLayout getEcsLayout() {
+    Layout<?> getEcsLayout() {
         return ecsLayout;
     }
 
-    GelfLayout getGelfLayout() {
+    Layout<?> getGelfLayout() {
         return gelfLayout;
     }
 
@@ -209,4 +213,10 @@ public class JsonTemplateLayoutBenchmarkState {
         return liteLogEvents;
     }
 
+    int nextLogEventIndex() {
+        final int currentLogEventIndex = logEventIndex;
+        logEventIndex = (logEventIndex + 1) % LOG_EVENT_COUNT;
+        return currentLogEventIndex;
+    }
+
 }

Reply via email to