diff options
-rw-r--r-- | build.sbt | 12 | ||||
-rw-r--r-- | kamon-core-bench/src/main/scala/kamon/bench/ThreadLocalStorageBenchmark.scala | 81 | ||||
-rw-r--r-- | kamon-core-tests/src/test/scala/kamon/context/ThreadLocalStorageSpec.scala | 1 | ||||
-rw-r--r-- | kamon-core/src/main/scala/kamon/context/Storage.scala | 33 | ||||
-rw-r--r-- | project/plugins.sbt | 1 |
5 files changed, 116 insertions, 12 deletions
@@ -17,7 +17,7 @@ lazy val kamon = (project in file(".")) .settings(moduleName := "kamon") .settings(noPublishing: _*) - .aggregate(core, testkit, coreTests) + .aggregate(core, testkit, coreTests, coreBench) val commonSettings = Seq( scalaVersion := "2.12.6", @@ -74,3 +74,13 @@ lazy val coreTests = (project in file("kamon-core-tests")) "ch.qos.logback" % "logback-classic" % "1.2.2" % "test" ) ).dependsOn(testkit) + + +lazy val coreBench = (project in file("kamon-core-bench")) + .enablePlugins(JmhPlugin) + .settings( + moduleName := "kamon-core-bench", + fork in Test := true) + .settings(noPublishing: _*) + .settings(commonSettings: _*) + .dependsOn(core)
\ No newline at end of file diff --git a/kamon-core-bench/src/main/scala/kamon/bench/ThreadLocalStorageBenchmark.scala b/kamon-core-bench/src/main/scala/kamon/bench/ThreadLocalStorageBenchmark.scala new file mode 100644 index 00000000..e67a913e --- /dev/null +++ b/kamon-core-bench/src/main/scala/kamon/bench/ThreadLocalStorageBenchmark.scala @@ -0,0 +1,81 @@ +/* ========================================================================================= + * Copyright © 2013-2018 the kamon project <http://kamon.io/> + * + * Licensed 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 kamon.bench + +import java.util.concurrent.TimeUnit + +import kamon.context.Storage.Scope +import kamon.context.{Context, Key, Storage} +import org.openjdk.jmh.annotations._ + +@State(Scope.Benchmark) +class ThreadLocalStorageBenchmark { + + val TestKey: Key[Int] = Key.local("test-key", 0) + val ContextWithKey: Context = Context.create().withKey(TestKey, 43) + + val TLS: Storage = new OldThreadLocal + val FTLS: Storage = new Storage.ThreadLocal + + + @Benchmark + @BenchmarkMode(Array(Mode.AverageTime)) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Fork + def currentThreadLocal: Context = { + val scope = TLS.store(ContextWithKey) + TLS.current() + scope.close() + TLS.current() + } + + @Benchmark + @BenchmarkMode(Array(Mode.AverageTime)) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @Fork + def fastThreadLocal: Context = { + val scope = FTLS.store(ContextWithKey) + FTLS.current() + scope.close() + FTLS.current() + } +} + + +class OldThreadLocal extends Storage { + private val tls = new java.lang.ThreadLocal[Context]() { + override def initialValue(): Context = Context.Empty + } + + override def current(): Context = + tls.get() + + override def store(context: Context): Scope = { + val newContext = context + val previousContext = tls.get() + tls.set(newContext) + + new Scope { + override def context: Context = newContext + override def close(): Unit = tls.set(previousContext) + } + } +} + +object OldThreadLocal { + def apply(): OldThreadLocal = new OldThreadLocal() +}
\ No newline at end of file diff --git a/kamon-core-tests/src/test/scala/kamon/context/ThreadLocalStorageSpec.scala b/kamon-core-tests/src/test/scala/kamon/context/ThreadLocalStorageSpec.scala index d039388d..8b94ac0c 100644 --- a/kamon-core-tests/src/test/scala/kamon/context/ThreadLocalStorageSpec.scala +++ b/kamon-core-tests/src/test/scala/kamon/context/ThreadLocalStorageSpec.scala @@ -45,7 +45,6 @@ class ThreadLocalStorageSpec extends WordSpec with Matchers { TLS.current() shouldBe Context.Empty } - } val TLS: Storage = new Storage.ThreadLocal diff --git a/kamon-core/src/main/scala/kamon/context/Storage.scala b/kamon-core/src/main/scala/kamon/context/Storage.scala index 7bbca111..2b409592 100644 --- a/kamon-core/src/main/scala/kamon/context/Storage.scala +++ b/kamon-core/src/main/scala/kamon/context/Storage.scala @@ -27,23 +27,36 @@ object Storage { def close(): Unit } - + /** + * Wrapper that implements optimized {@link ThreadLocal} access pattern ideal for heavily used + * ThreadLocals. + * + * <p> It is faster to use a mutable holder object and always perform ThreadLocal.get() and never use + * ThreadLocal.set(), because the value is more likely to be found in the ThreadLocalMap direct hash + * slot and avoid the slow path ThreadLocalMap.getEntryAfterMiss(). + * + * <p> Credit to @trask from the FastThreadLocal in glowroot. + * + * <p> One small change is that we don't use an kamon-defined holder object as that would prevent class unloading. + * + * */ class ThreadLocal extends Storage { - private val tls = new java.lang.ThreadLocal[Context]() { - override def initialValue(): Context = Context.Empty + private val tls = new java.lang.ThreadLocal[Array[AnyRef]]() { + override def initialValue(): Array[AnyRef] = + Array(Context.Empty) } override def current(): Context = - tls.get() + tls.get()(0).asInstanceOf[Context] - override def store(context: Context): Scope = { - val newContext = context - val previousContext = tls.get() - tls.set(newContext) + override def store(newContext: Context): Scope = { + val ref = tls.get() + val previousContext = ref(0) + ref(0) = newContext new Scope { override def context: Context = newContext - override def close(): Unit = tls.set(previousContext) + override def close(): Unit = ref(0) = previousContext } } } @@ -51,4 +64,4 @@ object Storage { object ThreadLocal { def apply(): ThreadLocal = new ThreadLocal() } -}
\ No newline at end of file +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 323e4f2f..3192be2f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,5 @@ resolvers += Resolver.bintrayIvyRepo("kamon-io", "sbt-plugins") addSbtPlugin("io.kamon" % "kamon-sbt-umbrella" % "0.0.15") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.3") |