aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego <diegolparra@gmail.com>2018-09-11 21:22:42 -0300
committerDiego <diegolparra@gmail.com>2018-09-11 21:22:42 -0300
commit3ca5064a72179290521d72d695a67b32e9bf3439 (patch)
tree85e5dcc90186439d228f83d7f65e848eb6753928
parent794fbf02664ac8c31072d8b955d897901f1f22e0 (diff)
downloadKamon-3ca5064a72179290521d72d695a67b32e9bf3439.tar.gz
Kamon-3ca5064a72179290521d72d695a67b32e9bf3439.tar.bz2
Kamon-3ca5064a72179290521d72d695a67b32e9bf3439.zip
Improvements in ThreadLocalStorage
-rw-r--r--build.sbt13
-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, 120 insertions, 9 deletions
diff --git a/build.sbt b/build.sbt
index 3d77510f..d553d3ed 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.4",
@@ -74,3 +74,14 @@ 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",
+ resolvers += Resolver.mavenLocal,
+ 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..96588798
--- /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 ScopeWithKey: 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(ScopeWithKey)
+ TLS.current()
+ scope.close()
+ TLS.current()
+ }
+
+ @Benchmark
+ @BenchmarkMode(Array(Mode.AverageTime))
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Fork
+ def fastThreadLocal: Context = {
+ val scope = FTLS.store(ScopeWithKey)
+ 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 12e0ca58..d2c4e57e 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 4f4e6cbb..f116142d 100644
--- a/kamon-core/src/main/scala/kamon/context/Storage.scala
+++ b/kamon-core/src/main/scala/kamon/context/Storage.scala
@@ -15,6 +15,8 @@
package kamon.context
+import kamon.context.Storage.ThreadLocal.ContextHolder
+
trait Storage {
def current(): Context
def store(context: Context): Storage.Scope
@@ -27,28 +29,45 @@ object Storage {
def close(): Unit
}
-
+ /**
+ * Wrapper that implements optimized {@link ThreadLocal} access pattern ideal for heavily used
+ * ThreadLocals.
+ *
+ * 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().
+ *
+ * Important: this thread local will live in ThreadLocalMap forever, so use with care.
+ */
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[ContextHolder]() {
+ override def initialValue() = new ContextHolder(Context.Empty)
}
override def current(): Context =
- tls.get()
+ get()
override def store(context: Context): Scope = {
val newContext = context
- val previousContext = tls.get()
- tls.set(newContext)
+ val previousContext = get()
+ set(newContext)
new Scope {
override def context: Context = newContext
- override def close(): Unit = tls.set(previousContext)
+ override def close(): Unit = set(previousContext)
}
}
+
+ private def get():Context =
+ tls.get().value
+
+ private def set(value:Context) : Unit =
+ tls.get().value = value
}
object ThreadLocal {
def apply(): ThreadLocal = new ThreadLocal()
+
+ final class ContextHolder(var value:Context)
}
} \ 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")