Skip to content
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

Static Object Optimizations #197

Merged
merged 18 commits into from Jan 9, 2024
Merged

Static Object Optimizations #197

merged 18 commits into from Jan 9, 2024

Conversation

ahirreddy
Copy link
Collaborator

@ahirreddy ahirreddy commented Jan 4, 2024

  • This PR uses the Parser & StaticOptimizer thread local string interner for keys in static objects
  • Similarly, we deduplicate the String -> Boolean map used to determine if a field is static.
  • For static objects we also use immutable.VectorMap (with a JavaWrapper) for the field set.
  • Lastly for the value cache, we size it according to the number of keys in the object this reduces unnecessary up-sizing for large objects, and more importantly removes the large number of sparse maps we previously had for small objects (the default was 16 elements)

Before: 855MB for the parsed file
image (9)

After: 425MB
image (10)

@lihaoyi lihaoyi removed their request for review January 5, 2024 07:37
Copy link
Collaborator

@szeiger szeiger left a comment

Choose a reason for hiding this comment

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

What's the performance impact of these changes? Are we trading memory for speed?

Comment on lines 77 to 80
// This is a dummy benchmark to see how much memory is used by the interpreter.
// You're meant to execute it, and once it prints "sleeping" you can attach yourkit and take a heap
// dump. Because we store the cache, the parsed objects will have strong references - and thus will
// be in the heap dump.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we turn this into a simple command line program that does a gc before and after parsing and then prints the memory usage diff to the console so you can easily retest this for future changes without having to attach a profiler?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done and updated readme.

Comment on lines 21 to 25
// HashMap to deduplicate strings.
private[this] val strings = new mutable.HashMap[String, String]

private[this] val fieldSet = new mutable.HashMap[Val.StaticObjectFieldSet, java.util.LinkedHashMap[String, java.lang.Boolean]]

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this cache separate from the Parser's? Should it be a single cache at the Interpreter level?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed, there's now a single cache at the interpreter level.

@@ -297,15 +298,45 @@ object Val{
}
}

def staticObject(pos: Position, fields: Array[Expr.Member.Field]): Obj = {
final case class StaticObjectFieldSet(keys: Array[String]) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This shouldn't be a case class. Array has no useful equality or toString and you're overriding equals and hashCode.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

@ahirreddy
Copy link
Collaborator Author

Sorry I missed your most important question. The performance impact here was undetectable in the benchmark. I think the fact that the object interning is thread-local and unsynchronized makes it pretty fast.

@@ -3,6 +3,8 @@ package sjsonnet
import java.io.StringWriter
import java.util.concurrent.TimeUnit

import scala.collection.mutable.HashMap
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer to keep mutable types qualified (i.e. only import scala.collection.mutable) everywhere for consistency.

@lihaoyi-databricks lihaoyi-databricks merged commit c462833 into master Jan 9, 2024
1 check passed
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.

None yet

3 participants