aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Topolnjak <ivantopo@gmail.com>2018-10-13 16:11:35 +0200
committerGitHub <noreply@github.com>2018-10-13 16:11:35 +0200
commitaad6a8ba785622780c3632293bbeb0823a9f0249 (patch)
tree6feab2dce1fbab244395724b3ef5e8db77f2832b
parent36b64d0ece9e69dde6ff1b53042b884dfd94e5d2 (diff)
parentb5ba5c442e8cb4b0818cf5ba317654cb7d0ff8dd (diff)
downloadKamon-aad6a8ba785622780c3632293bbeb0823a9f0249.tar.gz
Kamon-aad6a8ba785622780c3632293bbeb0823a9f0249.tar.bz2
Kamon-aad6a8ba785622780c3632293bbeb0823a9f0249.zip
Merge pull request #553 from kamon-io/thread-local-improvements
Improvements in ThreadLocalStorage
-rw-r--r--build.sbt12
-rw-r--r--kamon-core-bench/src/main/scala/kamon/bench/ThreadLocalStorageBenchmark.scala81
-rw-r--r--kamon-core-tests/src/test/scala/kamon/context/ThreadLocalStorageSpec.scala1
-rw-r--r--kamon-core/src/main/scala/kamon/context/Storage.scala33
-rw-r--r--project/plugins.sbt1
5 files changed, 116 insertions, 12 deletions
diff --git a/build.sbt b/build.sbt
index fcf30972..131626d9 100644
--- a/build.sbt
+++ b/build.sbt
@@ -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")