From 3ad55eb83915259dc9faef1a379ccd39fb35ec6f Mon Sep 17 00:00:00 2001 From: Xiaoyan Lin Date: Mon, 21 Mar 2016 18:06:05 -0700 Subject: [PATCH] Speed up the slow path of FastThreadLocal Motivation: The current slow path of FastThreadLocal is much slower than JDK ThreadLocal. See #4418 Modifications: - Add FastThreadLocalSlowPathBenchmark for the flow path of FastThreadLocal - Add final to speed up the slow path of FastThreadLocal Result: The slow path of FastThreadLocal is improved. --- .../util/internal/InternalThreadLocalMap.java | 24 +----- .../UnpaddedInternalThreadLocalMap.java | 2 +- ... => FastThreadLocalFastPathBenchmark.java} | 12 ++- .../FastThreadLocalSlowPathBenchmark.java | 79 +++++++++++++++++++ .../util/AbstractMicrobenchmark.java | 44 +++++------ 5 files changed, 110 insertions(+), 51 deletions(-) rename microbench/src/main/java/io/netty/microbench/concurrent/{FastThreadLocalBenchmark.java => FastThreadLocalFastPathBenchmark.java} (87%) create mode 100644 microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalSlowPathBenchmark.java diff --git a/common/src/main/java/io/netty/util/internal/InternalThreadLocalMap.java b/common/src/main/java/io/netty/util/internal/InternalThreadLocalMap.java index dbd4b3d6bd..25bf537556 100644 --- a/common/src/main/java/io/netty/util/internal/InternalThreadLocalMap.java +++ b/common/src/main/java/io/netty/util/internal/InternalThreadLocalMap.java @@ -41,18 +41,10 @@ public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap public static InternalThreadLocalMap getIfSet() { Thread thread = Thread.currentThread(); - InternalThreadLocalMap threadLocalMap; if (thread instanceof FastThreadLocalThread) { - threadLocalMap = ((FastThreadLocalThread) thread).threadLocalMap(); - } else { - ThreadLocal slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; - if (slowThreadLocalMap == null) { - threadLocalMap = null; - } else { - threadLocalMap = slowThreadLocalMap.get(); - } + return ((FastThreadLocalThread) thread).threadLocalMap(); } - return threadLocalMap; + return slowThreadLocalMap.get(); } public static InternalThreadLocalMap get() { @@ -74,11 +66,6 @@ public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap private static InternalThreadLocalMap slowGet() { ThreadLocal slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; - if (slowThreadLocalMap == null) { - UnpaddedInternalThreadLocalMap.slowThreadLocalMap = - slowThreadLocalMap = new ThreadLocal(); - } - InternalThreadLocalMap ret = slowThreadLocalMap.get(); if (ret == null) { ret = new InternalThreadLocalMap(); @@ -92,15 +79,12 @@ public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap if (thread instanceof FastThreadLocalThread) { ((FastThreadLocalThread) thread).setThreadLocalMap(null); } else { - ThreadLocal slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; - if (slowThreadLocalMap != null) { - slowThreadLocalMap.remove(); - } + slowThreadLocalMap.remove(); } } public static void destroy() { - slowThreadLocalMap = null; + slowThreadLocalMap.remove(); } public static int nextVariableIndex() { diff --git a/common/src/main/java/io/netty/util/internal/UnpaddedInternalThreadLocalMap.java b/common/src/main/java/io/netty/util/internal/UnpaddedInternalThreadLocalMap.java index c75ac40ab5..2d0bb6cd15 100644 --- a/common/src/main/java/io/netty/util/internal/UnpaddedInternalThreadLocalMap.java +++ b/common/src/main/java/io/netty/util/internal/UnpaddedInternalThreadLocalMap.java @@ -32,7 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ class UnpaddedInternalThreadLocalMap { - static ThreadLocal slowThreadLocalMap; + static final ThreadLocal slowThreadLocalMap = new ThreadLocal(); static final AtomicInteger nextIndex = new AtomicInteger(); /** Used by {@link FastThreadLocal} */ diff --git a/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalBenchmark.java b/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalFastPathBenchmark.java similarity index 87% rename from microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalBenchmark.java rename to microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalFastPathBenchmark.java index 2993a08be5..c0073fb655 100644 --- a/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalBenchmark.java +++ b/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalFastPathBenchmark.java @@ -24,11 +24,11 @@ import org.openjdk.jmh.annotations.Threads; import java.util.Random; /** - * This class benchmarks different allocators with different allocation sizes. + * This class benchmarks the fast path of FastThreadLocal and the JDK ThreadLocal. */ @Threads(4) @Measurement(iterations = 10, batchSize = 100) -public class FastThreadLocalBenchmark extends AbstractMicrobenchmark { +public class FastThreadLocalFastPathBenchmark extends AbstractMicrobenchmark { private static final Random rand = new Random(); @@ -39,19 +39,17 @@ public class FastThreadLocalBenchmark extends AbstractMicrobenchmark { static { for (int i = 0; i < jdkThreadLocals.length; i ++) { + final int num = rand.nextInt(); jdkThreadLocals[i] = new ThreadLocal() { @Override protected Integer initialValue() { - return rand.nextInt(); + return num; } }; - } - - for (int i = 0; i < fastThreadLocals.length; i ++) { fastThreadLocals[i] = new FastThreadLocal() { @Override protected Integer initialValue() { - return rand.nextInt(); + return num; } }; } diff --git a/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalSlowPathBenchmark.java b/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalSlowPathBenchmark.java new file mode 100644 index 0000000000..20d414aa1f --- /dev/null +++ b/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalSlowPathBenchmark.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 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.concurrent; + +import io.netty.microbench.util.AbstractMicrobenchmark; +import io.netty.util.concurrent.FastThreadLocal; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Threads; + +import java.util.Random; + +/** + * This class benchmarks the slow path of FastThreadLocal and the JDK ThreadLocal. + */ +@Threads(4) +@Measurement(iterations = 10, batchSize = 100) +public class FastThreadLocalSlowPathBenchmark extends AbstractMicrobenchmark { + + private static final Random rand = new Random(); + + @SuppressWarnings("unchecked") + private static final ThreadLocal[] jdkThreadLocals = new ThreadLocal[128]; + @SuppressWarnings("unchecked") + private static final FastThreadLocal[] fastThreadLocals = new FastThreadLocal[jdkThreadLocals.length]; + + static { + for (int i = 0; i < jdkThreadLocals.length; i ++) { + final int num = rand.nextInt(); + jdkThreadLocals[i] = new ThreadLocal() { + @Override + protected Integer initialValue() { + return num; + } + }; + fastThreadLocals[i] = new FastThreadLocal() { + @Override + protected Integer initialValue() { + return num; + } + }; + } + } + + public FastThreadLocalSlowPathBenchmark() { + super(false, true); + } + + @Benchmark + public int jdkThreadLocalGet() { + int result = 0; + for (ThreadLocal i: jdkThreadLocals) { + result += i.get(); + } + return result; + } + + @Benchmark + public int fastThreadLocal() { + int result = 0; + for (FastThreadLocal i: fastThreadLocals) { + result += i.get(); + } + return result; + } +} diff --git a/microbench/src/main/java/io/netty/microbench/util/AbstractMicrobenchmark.java b/microbench/src/main/java/io/netty/microbench/util/AbstractMicrobenchmark.java index eea5b16e6f..7c9dcf2d1e 100644 --- a/microbench/src/main/java/io/netty/microbench/util/AbstractMicrobenchmark.java +++ b/microbench/src/main/java/io/netty/microbench/util/AbstractMicrobenchmark.java @@ -32,17 +32,6 @@ import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; public class AbstractMicrobenchmark extends AbstractMicrobenchmarkBase { protected static final int DEFAULT_FORKS = 2; - protected static final String[] JVM_ARGS; - - static { - final String[] customArgs = { - "-Xms768m", "-Xmx768m", "-XX:MaxDirectMemorySize=768m", "-Djmh.executor=CUSTOM", - "-Djmh.executor.class=io.netty.microbench.util.AbstractMicrobenchmark$HarnessExecutor" }; - - JVM_ARGS = new String[BASE_JVM_ARGS.length + customArgs.length]; - System.arraycopy(BASE_JVM_ARGS, 0, JVM_ARGS, 0, BASE_JVM_ARGS.length); - System.arraycopy(customArgs, 0, JVM_ARGS, BASE_JVM_ARGS.length, customArgs.length); - } public static final class HarnessExecutor extends ThreadPoolExecutor { public HarnessExecutor(int maxThreads, String prefix) { @@ -52,27 +41,36 @@ public class AbstractMicrobenchmark extends AbstractMicrobenchmarkBase { } } - private final boolean disableAssertions; - private String[] jvmArgsWithNoAssertions; + private final String[] jvmArgs; public AbstractMicrobenchmark() { - this(false); + this(false, false); } public AbstractMicrobenchmark(boolean disableAssertions) { - this.disableAssertions = disableAssertions; + this(disableAssertions, false); + } + + public AbstractMicrobenchmark(boolean disableAssertions, boolean disableHarnessExecutor) { + final String[] customArgs; + if (disableHarnessExecutor) { + customArgs = new String[]{"-Xms768m", "-Xmx768m", "-XX:MaxDirectMemorySize=768m"}; + } else { + customArgs = new String[]{"-Xms768m", "-Xmx768m", "-XX:MaxDirectMemorySize=768m", "-Djmh.executor=CUSTOM", + "-Djmh.executor.class=io.netty.microbench.util.AbstractMicrobenchmark$HarnessExecutor"}; + } + String[] jvmArgs = new String[BASE_JVM_ARGS.length + customArgs.length]; + System.arraycopy(BASE_JVM_ARGS, 0, jvmArgs, 0, BASE_JVM_ARGS.length); + System.arraycopy(customArgs, 0, jvmArgs, BASE_JVM_ARGS.length, customArgs.length); + if (disableAssertions) { + jvmArgs = removeAssertions(jvmArgs); + } + this.jvmArgs = jvmArgs; } @Override protected String[] jvmArgs() { - if (!disableAssertions) { - return JVM_ARGS; - } - - if (jvmArgsWithNoAssertions == null) { - jvmArgsWithNoAssertions = removeAssertions(JVM_ARGS); - } - return jvmArgsWithNoAssertions; + return jvmArgs; } @Override