Skip to content

Reduce performance overhead of map lookups in Invoker.invoked() #202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

JoshRosen
Copy link
Contributor

Invoker.invoked() is showing up as a performance hotspot in my tests. For full details of my investigation, see #201.

In my profiling, half of the time in Invoker.invoked() was spent computing hashCodes when indexing into the ids map, which is a ThreadSafeMap[(String, Int), Any]. This PR replaces the single-level map by a two-level ThreadSafeMap[String, ThreadSafeMap[Int, Any]] map. This isn't much more space because the String part of the key is usually constant and it's not a whole lot of extra lookup time either because the outer map typically has at most one element.

After making this change I no longer saw map lookups as a bottleneck; instead, all of the time shifted to flush() calls (which I plan to address in a separate PR; see discussion at #201).

I also made a small micro-optimization to avoid unnecessary int boxing and implicit StringBuilder construction.

threadFiles.set(files)
}
val writer = files.getOrElseUpdate(dataDir, new FileWriter(measurementFile(dataDir), true))
writer.append(id.toString + '\n').flush()
writer.append(Integer.toString(id)).append("\n").flush()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure that id.toString boxes here, so Integer.toString(...) saves that overhead.

The key change on this line is to replace the string concatenation with two separate .append() calls. The concatenation ends up implicitly constructing a StringBuilder.

@@ -31,18 +35,27 @@ object Invoker {
// times since for coverage we only care about 1 or more, (it just slows things down to
// do it more than once), anything we can do to help is good. This helps especially with code
// that is executed many times quickly, eg tight loops.
if (!ids.contains(dataDir, id)) {
if (!dataDirToIds.contains(dataDir)) {
// Guard against SI-7943: "TrieMap method getOrElseUpdate is not thread-safe".
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that this was also calling getOrElseUpdate on a TrieMap down in files.getOrElseUpdate, but that case wasn't an issue because that map is used only in a single thread (since it comes from a non-inheritable ThreadLocal).

To help make this clearer in the code, I went ahead and used a plain mutable.HashMap at that other site.

@sksamuel sksamuel merged commit 5e2a8ca into scoverage:master Jul 5, 2018
@sksamuel
Copy link
Member

sksamuel commented Jul 5, 2018

👍

@JoshRosen JoshRosen deleted the Invoker-map-lookup-performance-improvements branch August 24, 2018 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants