From 2a2a21ec59e4903d48b7f56ffc78a9f183e99813 Mon Sep 17 00:00:00 2001 From: belliottsmith Date: Mon, 9 Jun 2014 01:18:46 +0100 Subject: [PATCH] Introduce FastThreadLocal which uses an EnumMap and a predefined fixed set of possible thread locals Motivation: Provide a faster ThreadLocal implementation Modification: Add a "FastThreadLocal" which uses an EnumMap and a predefined fixed set of possible thread locals (all of the static instances created by netty) that is around 10-20% faster than standard ThreadLocal in my benchmarks (and can be seen having an effect in the direct PooledByteBufAllocator benchmark that uses the DEFAULT ByteBufAllocator which uses this FastThreadLocal, as opposed to normal instantiations that do not, and in the new RecyclableArrayList benchmark); Result: Improved performance --- .../netty/buffer/PooledByteBufAllocator.java | 4 +- .../handler/codec/http/CookieEncoderUtil.java | 4 +- .../codec/http/HttpHeaderDateFormat.java | 4 +- .../ThreadLocalMarshallerProvider.java | 3 +- .../ThreadLocalUnmarshallerProvider.java | 3 +- .../main/java/io/netty/util/CharsetUtil.java | 6 +- .../src/main/java/io/netty/util/Recycler.java | 12 +- .../netty/util/concurrent/DefaultPromise.java | 3 +- .../util/concurrent/DefaultThreadFactory.java | 7 +- .../netty/util/internal/FastThreadLocal.java | 112 ++++++++++++++++++ .../util/internal/FastThreadLocalThread.java | 82 +++++++++++++ .../util/internal/TypeParameterMatcher.java | 4 +- .../util/FingerprintTrustManagerFactory.java | 3 +- .../ssl/util/SimpleTrustManagerFactory.java | 4 +- .../buffer/ByteBufAllocatorBenchmark.java | 12 ++ .../RecyclableArrayListBenchmark.java | 40 +++++++ .../util/AbstractMicrobenchmark.java | 16 ++- .../netty/channel/ChannelHandlerAdapter.java | 4 +- .../io/netty/channel/local/LocalChannel.java | 3 +- 19 files changed, 302 insertions(+), 24 deletions(-) create mode 100644 common/src/main/java/io/netty/util/internal/FastThreadLocal.java create mode 100644 common/src/main/java/io/netty/util/internal/FastThreadLocalThread.java create mode 100644 microbench/src/test/java/io/netty/microbench/internal/RecyclableArrayListBenchmark.java diff --git a/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java b/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java index d5e6980dac..20f7506159 100644 --- a/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java +++ b/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java @@ -17,6 +17,7 @@ package io.netty.buffer; import io.netty.util.ThreadDeathWatcher; +import io.netty.util.internal.FastThreadLocal; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.SystemPropertyUtil; import io.netty.util.internal.logging.InternalLogger; @@ -28,7 +29,6 @@ import java.util.concurrent.atomic.AtomicInteger; public class PooledByteBufAllocator extends AbstractByteBufAllocator { private static final InternalLogger logger = InternalLoggerFactory.getInstance(PooledByteBufAllocator.class); - private static final int DEFAULT_NUM_HEAP_ARENA; private static final int DEFAULT_NUM_DIRECT_ARENA; @@ -273,7 +273,7 @@ public class PooledByteBufAllocator extends AbstractByteBufAllocator { threadCache.free(); } - final class PoolThreadLocalCache extends ThreadLocal { + final class PoolThreadLocalCache extends FastThreadLocal { private final AtomicInteger index = new AtomicInteger(); private boolean initialized; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoderUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoderUtil.java index 1fba709e38..57e2c261b4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoderUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/CookieEncoderUtil.java @@ -16,9 +16,11 @@ package io.netty.handler.codec.http; +import io.netty.util.internal.FastThreadLocal; + final class CookieEncoderUtil { - static final ThreadLocal buffer = new ThreadLocal() { + static final ThreadLocal buffer = new FastThreadLocal() { @Override public StringBuilder get() { StringBuilder buf = super.get(); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java index b94abd8b62..c70de41da5 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderDateFormat.java @@ -15,6 +15,8 @@ */ package io.netty.handler.codec.http; +import io.netty.util.internal.FastThreadLocal; + import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; @@ -38,7 +40,7 @@ final class HttpHeaderDateFormat extends SimpleDateFormat { private final SimpleDateFormat format2 = new HttpHeaderDateFormatObsolete2(); private static final ThreadLocal dateFormatThreadLocal = - new ThreadLocal() { + new FastThreadLocal() { @Override protected HttpHeaderDateFormat initialValue() { return new HttpHeaderDateFormat(); diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallerProvider.java b/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallerProvider.java index a3ca6f67b7..a64f5b8595 100644 --- a/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallerProvider.java +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalMarshallerProvider.java @@ -17,6 +17,7 @@ package io.netty.handler.codec.marshalling; import io.netty.channel.ChannelHandlerContext; +import io.netty.util.internal.FastThreadLocal; import org.jboss.marshalling.Marshaller; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.MarshallingConfiguration; @@ -27,7 +28,7 @@ import org.jboss.marshalling.MarshallingConfiguration; * many small {@link Object}'s and your actual Thread count is not to big */ public class ThreadLocalMarshallerProvider implements MarshallerProvider { - private final ThreadLocal marshallers = new ThreadLocal(); + private final ThreadLocal marshallers = new FastThreadLocal(); private final MarshallerFactory factory; private final MarshallingConfiguration config; diff --git a/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalUnmarshallerProvider.java b/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalUnmarshallerProvider.java index 77b278d624..2c31903ef1 100644 --- a/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalUnmarshallerProvider.java +++ b/codec/src/main/java/io/netty/handler/codec/marshalling/ThreadLocalUnmarshallerProvider.java @@ -17,6 +17,7 @@ package io.netty.handler.codec.marshalling; import io.netty.channel.ChannelHandlerContext; +import io.netty.util.internal.FastThreadLocal; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.MarshallingConfiguration; import org.jboss.marshalling.Unmarshaller; @@ -27,7 +28,7 @@ import org.jboss.marshalling.Unmarshaller; * many small {@link Object}'s. */ public class ThreadLocalUnmarshallerProvider implements UnmarshallerProvider { - private final ThreadLocal unmarshallers = new ThreadLocal(); + private final ThreadLocal unmarshallers = new FastThreadLocal(); private final MarshallerFactory factory; private final MarshallingConfiguration config; diff --git a/common/src/main/java/io/netty/util/CharsetUtil.java b/common/src/main/java/io/netty/util/CharsetUtil.java index d11c07f6b1..d2c20df35d 100644 --- a/common/src/main/java/io/netty/util/CharsetUtil.java +++ b/common/src/main/java/io/netty/util/CharsetUtil.java @@ -15,6 +15,8 @@ */ package io.netty.util; +import io.netty.util.internal.FastThreadLocal; + import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; @@ -61,7 +63,7 @@ public final class CharsetUtil { public static final Charset US_ASCII = Charset.forName("US-ASCII"); private static final ThreadLocal> encoders = - new ThreadLocal>() { + new FastThreadLocal>() { @Override protected Map initialValue() { return new IdentityHashMap(); @@ -69,7 +71,7 @@ public final class CharsetUtil { }; private static final ThreadLocal> decoders = - new ThreadLocal>() { + new FastThreadLocal>() { @Override protected Map initialValue() { return new IdentityHashMap(); diff --git a/common/src/main/java/io/netty/util/Recycler.java b/common/src/main/java/io/netty/util/Recycler.java index ad115c42e1..3d96036154 100644 --- a/common/src/main/java/io/netty/util/Recycler.java +++ b/common/src/main/java/io/netty/util/Recycler.java @@ -23,6 +23,8 @@ import io.netty.util.internal.logging.InternalLoggerFactory; import java.util.IdentityHashMap; import java.util.Map; +import io.netty.util.internal.FastThreadLocal; + /** * Light-weight object pool based on a thread-local stack. * @@ -54,8 +56,7 @@ public abstract class Recycler { } private final int maxCapacity; - - private final ThreadLocal> threadLocal = new ThreadLocal>() { + private final ThreadLocal> threadLocal = new FastThreadLocal>() { @Override protected Stack initialValue() { return new Stack(Recycler.this, Thread.currentThread(), maxCapacity); @@ -66,11 +67,8 @@ public abstract class Recycler { this(DEFAULT_MAX_CAPACITY); } - protected Recycler(int maxCapacity) { - if (maxCapacity <= 0) { - maxCapacity = 0; - } - this.maxCapacity = maxCapacity; + public Recycler(int maxCapacity) { + this.maxCapacity = Math.max(0, maxCapacity); } public final T get() { diff --git a/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java b/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java index c478627b32..c975e419d6 100644 --- a/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java +++ b/common/src/main/java/io/netty/util/concurrent/DefaultPromise.java @@ -17,6 +17,7 @@ package io.netty.util.concurrent; import io.netty.util.Signal; import io.netty.util.internal.EmptyArrays; +import io.netty.util.internal.FastThreadLocal; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.logging.InternalLogger; @@ -35,7 +36,7 @@ public class DefaultPromise extends AbstractFuture implements Promise { InternalLoggerFactory.getInstance(DefaultPromise.class.getName() + ".rejectedExecution"); private static final int MAX_LISTENER_STACK_DEPTH = 8; - private static final ThreadLocal LISTENER_STACK_DEPTH = new ThreadLocal() { + private static final ThreadLocal LISTENER_STACK_DEPTH = new FastThreadLocal() { @Override protected Integer initialValue() { return 0; diff --git a/common/src/main/java/io/netty/util/concurrent/DefaultThreadFactory.java b/common/src/main/java/io/netty/util/concurrent/DefaultThreadFactory.java index 3142a1b029..94a89654b8 100644 --- a/common/src/main/java/io/netty/util/concurrent/DefaultThreadFactory.java +++ b/common/src/main/java/io/netty/util/concurrent/DefaultThreadFactory.java @@ -16,6 +16,7 @@ package io.netty.util.concurrent; +import io.netty.util.internal.FastThreadLocalThread; import io.netty.util.internal.StringUtil; import java.util.Locale; @@ -98,7 +99,7 @@ public class DefaultThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { - Thread t = new Thread(r, prefix + nextId.incrementAndGet()); + Thread t = newThread(r, prefix + nextId.incrementAndGet()); try { if (t.isDaemon()) { if (!daemon) { @@ -118,4 +119,8 @@ public class DefaultThreadFactory implements ThreadFactory { } return t; } + + protected Thread newThread(Runnable r, String name) { + return new FastThreadLocalThread(r, name); + } } diff --git a/common/src/main/java/io/netty/util/internal/FastThreadLocal.java b/common/src/main/java/io/netty/util/internal/FastThreadLocal.java new file mode 100644 index 0000000000..7e0aec53a4 --- /dev/null +++ b/common/src/main/java/io/netty/util/internal/FastThreadLocal.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.util.internal; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A special {@link ThreadLocal} which is operating over a predefined array, so it always operate in O(1) when called + * from a {@link FastThreadLocalThread}. This permits less indirection and offers a slight performance improvement, + * so is useful when invoked frequently. + * + * The fast path is only possible on threads that extend FastThreadLocalThread, as this class + * stores the necessary state. Access by any other kind of thread falls back to a regular ThreadLocal + * + * @param + */ +public class FastThreadLocal extends ThreadLocal { + static final Object EMPTY = new Object(); + + private static final AtomicInteger NEXT_INDEX = new AtomicInteger(0); + private final ThreadLocal fallback = new ThreadLocal() { + @Override + protected V initialValue() { + return FastThreadLocal.this.initialValue(); + } + }; + private final int index; + + public FastThreadLocal() { + index = NEXT_INDEX.getAndIncrement(); + if (index < 0) { + NEXT_INDEX.decrementAndGet(); + throw new IllegalStateException("Maximal number (" + Integer.MAX_VALUE + ") of FastThreadLocal exceeded"); + } + } + + /** + * Set the value for the current thread + */ + @Override + public void set(V value) { + Thread thread = Thread.currentThread(); + if (!(thread instanceof FastThreadLocalThread)) { + fallback.set(value); + return; + } + FastThreadLocalThread fastThread = (FastThreadLocalThread) thread; + Object[] lookup = fastThread.lookup; + if (index >= lookup.length) { + lookup = fastThread.expandArray(index); + } + lookup[index] = value; + } + + /** + * Sets the value to uninitialized; a proceeding call to get() will trigger a call to initialValue() + */ + @Override + public void remove() { + Thread thread = Thread.currentThread(); + if (!(thread instanceof FastThreadLocalThread)) { + fallback.remove(); + return; + } + Object[] lookup = ((FastThreadLocalThread) thread).lookup; + if (index >= lookup.length) { + return; + } + lookup[index] = EMPTY; + } + + /** + * @return the current value for the current thread + */ + @Override + @SuppressWarnings("unchecked") + public V get() { + Thread thread = Thread.currentThread(); + if (!(thread instanceof FastThreadLocalThread)) { + return fallback.get(); + } + FastThreadLocalThread fastThread = (FastThreadLocalThread) thread; + + Object[] lookup = fastThread.lookup; + Object v; + if (index >= lookup.length) { + v = initialValue(); + lookup = fastThread.expandArray(index); + lookup[index] = v; + } else { + v = lookup[index]; + if (v == EMPTY) { + v = initialValue(); + lookup[index] = v; + } + } + return (V) v; + } +} diff --git a/common/src/main/java/io/netty/util/internal/FastThreadLocalThread.java b/common/src/main/java/io/netty/util/internal/FastThreadLocalThread.java new file mode 100644 index 0000000000..dd7d2acd8a --- /dev/null +++ b/common/src/main/java/io/netty/util/internal/FastThreadLocalThread.java @@ -0,0 +1,82 @@ +/* +* Copyright 2014 The Netty Project +* +* The Netty Project 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 io.netty.util.internal; + +import java.util.Arrays; + +/** + * To utilise the {@link FastThreadLocal} fast-path, all threads accessing a {@link FastThreadLocal} must extend this + * class. + */ +public class FastThreadLocalThread extends Thread { + + Object[] lookup = newArray(); + + public FastThreadLocalThread() { } + + public FastThreadLocalThread(Runnable target) { + super(target); + } + + public FastThreadLocalThread(ThreadGroup group, Runnable target) { + super(group, target); + } + + public FastThreadLocalThread(String name) { + super(name); + } + + public FastThreadLocalThread(ThreadGroup group, String name) { + super(group, name); + } + + public FastThreadLocalThread(Runnable target, String name) { + super(target, name); + } + + public FastThreadLocalThread(ThreadGroup group, Runnable target, String name) { + super(group, target, name); + } + + public FastThreadLocalThread(ThreadGroup group, Runnable target, String name, long stackSize) { + super(group, target, name, stackSize); + } + + private static Object[] newArray() { + Object[] array = new Object[32]; + Arrays.fill(array, FastThreadLocal.EMPTY); + return array; + } + + Object[] expandArray(int length) { + int newCapacity = lookup.length; + do { + // double capacity until it is big enough + newCapacity <<= 1; + + if (newCapacity < 0) { + throw new IllegalStateException(); + } + + } while (length > newCapacity); + + Object[] array = new Object[newCapacity]; + System.arraycopy(lookup, 0, array, 0, lookup.length); + Arrays.fill(array, lookup.length, array.length, FastThreadLocal.EMPTY); + lookup = array; + return lookup; + } +} diff --git a/common/src/main/java/io/netty/util/internal/TypeParameterMatcher.java b/common/src/main/java/io/netty/util/internal/TypeParameterMatcher.java index ab9a676b45..d507f5c5ba 100644 --- a/common/src/main/java/io/netty/util/internal/TypeParameterMatcher.java +++ b/common/src/main/java/io/netty/util/internal/TypeParameterMatcher.java @@ -31,7 +31,7 @@ public abstract class TypeParameterMatcher { private static final Object TEST_OBJECT = new Object(); private static final ThreadLocal, TypeParameterMatcher>> getCache = - new ThreadLocal, TypeParameterMatcher>>() { + new FastThreadLocal, TypeParameterMatcher>>() { @Override protected Map, TypeParameterMatcher> initialValue() { return new IdentityHashMap, TypeParameterMatcher>(); @@ -69,7 +69,7 @@ public abstract class TypeParameterMatcher { } private static final ThreadLocal, Map>> findCache = - new ThreadLocal, Map>>() { + new FastThreadLocal, Map>>() { @Override protected Map, Map> initialValue() { return new IdentityHashMap, Map>(); diff --git a/handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java index 218ae1a48b..bf1d3cde49 100644 --- a/handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java +++ b/handler/src/main/java/io/netty/handler/ssl/util/FingerprintTrustManagerFactory.java @@ -19,6 +19,7 @@ package io.netty.handler.ssl.util; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.internal.EmptyArrays; +import io.netty.util.internal.FastThreadLocal; import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; @@ -67,7 +68,7 @@ public final class FingerprintTrustManagerFactory extends SimpleTrustManagerFact private static final int SHA1_BYTE_LEN = 20; private static final int SHA1_HEX_LEN = SHA1_BYTE_LEN * 2; - private static final ThreadLocal tlmd = new ThreadLocal() { + private static final ThreadLocal tlmd = new FastThreadLocal() { @Override protected MessageDigest initialValue() { try { diff --git a/handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java b/handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java index b05e506ec9..7ac372a12f 100644 --- a/handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java +++ b/handler/src/main/java/io/netty/handler/ssl/util/SimpleTrustManagerFactory.java @@ -16,6 +16,8 @@ package io.netty.handler.ssl.util; +import io.netty.util.internal.FastThreadLocal; + import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; @@ -43,7 +45,7 @@ public abstract class SimpleTrustManagerFactory extends TrustManagerFactory { * To work around this issue, we use an ugly hack which uses a {@link ThreadLocal}. */ private static final ThreadLocal CURRENT_SPI = - new ThreadLocal() { + new FastThreadLocal() { @Override protected SimpleTrustManagerFactorySpi initialValue() { return new SimpleTrustManagerFactorySpi(); diff --git a/microbench/src/test/java/io/netty/microbench/buffer/ByteBufAllocatorBenchmark.java b/microbench/src/test/java/io/netty/microbench/buffer/ByteBufAllocatorBenchmark.java index aa361aff13..9158855f85 100644 --- a/microbench/src/test/java/io/netty/microbench/buffer/ByteBufAllocatorBenchmark.java +++ b/microbench/src/test/java/io/netty/microbench/buffer/ByteBufAllocatorBenchmark.java @@ -83,4 +83,16 @@ public class ByteBufAllocatorBenchmark extends AbstractMicrobenchmark { } pooledDirectBuffers[idx] = pooledAllocator.directBuffer(size); } + + @GenerateMicroBenchmark + public void defaultPooledHeapAllocAndFree() { + ByteBuf buffer = PooledByteBufAllocator.DEFAULT.heapBuffer(size); + buffer.release(); + } + + @GenerateMicroBenchmark + public void defaultPooledDirectAllocAndFree() { + ByteBuf buffer = PooledByteBufAllocator.DEFAULT.directBuffer(size); + buffer.release(); + } } diff --git a/microbench/src/test/java/io/netty/microbench/internal/RecyclableArrayListBenchmark.java b/microbench/src/test/java/io/netty/microbench/internal/RecyclableArrayListBenchmark.java new file mode 100644 index 0000000000..e10599fed5 --- /dev/null +++ b/microbench/src/test/java/io/netty/microbench/internal/RecyclableArrayListBenchmark.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project 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 io.netty.microbench.internal; + +import io.netty.microbench.util.AbstractMicrobenchmark; +import io.netty.util.internal.RecyclableArrayList; +import org.openjdk.jmh.annotations.GenerateMicroBenchmark; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Threads; + +/** + * This class benchmarks different allocators with different allocation sizes. + */ +@Threads(4) +@Measurement(iterations = 10, batchSize = 100) +public class RecyclableArrayListBenchmark extends AbstractMicrobenchmark { + + @Param({ "00000", "00256", "01024", "04096", "16384", "65536" }) + public int size; + + @GenerateMicroBenchmark + public void recycleSameThread() { + RecyclableArrayList list = RecyclableArrayList.newInstance(size); + list.recycle(); + } +} diff --git a/microbench/src/test/java/io/netty/microbench/util/AbstractMicrobenchmark.java b/microbench/src/test/java/io/netty/microbench/util/AbstractMicrobenchmark.java index 82adf78133..d9ab838e7c 100644 --- a/microbench/src/test/java/io/netty/microbench/util/AbstractMicrobenchmark.java +++ b/microbench/src/test/java/io/netty/microbench/util/AbstractMicrobenchmark.java @@ -16,6 +16,7 @@ package io.netty.microbench.util; import io.netty.util.ResourceLeakDetector; +import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.internal.SystemPropertyUtil; import org.junit.Test; import org.openjdk.jmh.annotations.Fork; @@ -29,6 +30,9 @@ import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.io.File; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * Base class for all JMH benchmarks. @@ -43,11 +47,21 @@ public class AbstractMicrobenchmark { protected static final int DEFAULT_MEASURE_ITERATIONS = 10; protected static final int DEFAULT_FORKS = 2; + public static final class HarnessExecutor extends ThreadPoolExecutor { + public HarnessExecutor(int maxThreads, String prefix) { + super(0, maxThreads, 1L, TimeUnit.DAYS, new SynchronousQueue(), + new DefaultThreadFactory(prefix)); + System.out.println("Using harness executor"); + } + } + protected static final String[] JVM_ARGS = { "-server", "-dsa", "-da", "-ea:io.netty...", "-Xms768m", "-Xmx768m", "-XX:MaxDirectMemorySize=768m", "-XX:+AggressiveOpts", "-XX:+UseBiasedLocking", "-XX:+UseFastAccessorMethods", "-XX:+UseStringCache", "-XX:+OptimizeStringConcat", - "-XX:+HeapDumpOnOutOfMemoryError", "-Dio.netty.noResourceLeakDetection" + "-XX:+HeapDumpOnOutOfMemoryError", "-Dio.netty.noResourceLeakDetection", + "-Dharness.executor=CUSTOM", + "-Dharness.executor.class=io.netty.microbench.util.AbstractMicrobenchmark$HarnessExecutor" }; static { diff --git a/transport/src/main/java/io/netty/channel/ChannelHandlerAdapter.java b/transport/src/main/java/io/netty/channel/ChannelHandlerAdapter.java index 1f9496562e..b1518451e4 100644 --- a/transport/src/main/java/io/netty/channel/ChannelHandlerAdapter.java +++ b/transport/src/main/java/io/netty/channel/ChannelHandlerAdapter.java @@ -16,6 +16,8 @@ package io.netty.channel; +import io.netty.util.internal.FastThreadLocal; + import java.util.Map; import java.util.WeakHashMap; @@ -33,7 +35,7 @@ public abstract class ChannelHandlerAdapter implements ChannelHandler { * See #2289. */ private static final ThreadLocal, Boolean>> SHARABLE_CACHE = - new ThreadLocal, Boolean>>() { + new FastThreadLocal, Boolean>>() { @Override protected Map, Boolean> initialValue() { // Start with small capacity to keep memory overhead as low as possible. diff --git a/transport/src/main/java/io/netty/channel/local/LocalChannel.java b/transport/src/main/java/io/netty/channel/local/LocalChannel.java index ccf44bd9b4..12305a8b47 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalChannel.java +++ b/transport/src/main/java/io/netty/channel/local/LocalChannel.java @@ -28,6 +28,7 @@ import io.netty.channel.EventLoop; import io.netty.channel.SingleThreadEventLoop; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.SingleThreadEventExecutor; +import io.netty.util.internal.FastThreadLocal; import java.net.SocketAddress; import java.nio.channels.AlreadyConnectedException; @@ -48,7 +49,7 @@ public class LocalChannel extends AbstractChannel { private static final ChannelMetadata METADATA = new ChannelMetadata(false); private static final int MAX_READER_STACK_DEPTH = 8; - private static final ThreadLocal READER_STACK_DEPTH = new ThreadLocal() { + private static final ThreadLocal READER_STACK_DEPTH = new FastThreadLocal() { @Override protected Integer initialValue() { return 0;