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

Optimize UnwrapArray parser by caching JValue ClassTag #547

Merged
merged 4 commits into from
Jun 26, 2023
Merged

Optimize UnwrapArray parser by caching JValue ClassTag #547

merged 4 commits into from
Jun 26, 2023

Conversation

agourlay
Copy link
Contributor

While using the UnwrapArray parsing mode, I can see a very large amount of java.lang.ClassValue calls.

classtag

This accounts for almost 30% of CPU time for very large inputs.

This PR proposes to cache an implicit ClassTag value for the JValue in order to speed things up.

I was unfortunately not able to validate the fix as I have issues producing snapshots locally.

@@ -81,6 +82,7 @@ sealed abstract class JValue {

object JValue {
implicit val facade: Facade[JValue] = JawnFacade
implicit val classTag: ClassTag[JValue] = ClassTag(classOf[JValue])
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I wonder if it's worth making this lazy?

Copy link
Contributor Author

@agourlay agourlay Jun 17, 2023

Choose a reason for hiding this comment

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

What are you trying to optimize? The memory consumption?
On the other hand the UnwrapArray parser would have to repeatedly go through the synchronized access.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Load time. Companion object gets initialized the moment the type gets used so I thought it might be worth it to defer reflection call until the code path hits it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds reasonable 👍

I made it lazy.

@@ -81,6 +82,7 @@ sealed abstract class JValue {

object JValue {
implicit val facade: Facade[JValue] = JawnFacade
implicit lazy val classTag: ClassTag[JValue] = ClassTag(classOf[JValue])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks @agourlay !

Copy link
Member

@rossabaker rossabaker left a comment

Choose a reason for hiding this comment

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

I like it, but one question since I'm a public API minimalist...

@@ -81,6 +82,7 @@ sealed abstract class JValue {

object JValue {
implicit val facade: Facade[JValue] = JawnFacade
implicit lazy val classTag: ClassTag[JValue] = ClassTag(classOf[JValue])
Copy link
Member

Choose a reason for hiding this comment

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

Complete nitpick, but does this need to be public? Seems like an implementation detail.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally legit nitpick, I made it private in c61b951

I had to add a scope to the private field otherwise the compilation fails with

[error] /home/agourlay/Workspace/jawn/ast/shared/src/main/scala/jawn/ast/JawnFacade.scala:56:55: lazy value classTag in object JValue cannot be accessed in object org.typelevel.jawn.ast.JValue
[error]  Access to protected lazy value classTag not permitted because
[error]  enclosing <$anon: org.typelevel.jawn.FContext.NoIndexFContext[org.typelevel.jawn.ast.JValue]> is not a subclass of
[error]  object JValue in package ast where target is defined
[error]       def finish(): JValue = JArray(vs.toArray(JValue.classTag))

@rossabaker rossabaker merged commit 1249411 into typelevel:main Jun 26, 2023
@@ -81,6 +82,7 @@ sealed abstract class JValue {

object JValue {
implicit val facade: Facade[JValue] = JawnFacade
implicit private[jawn] lazy val classTag: ClassTag[JValue] = ClassTag(classOf[JValue])
Copy link
Member

Choose a reason for hiding this comment

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

note that lazy adds some additional synchronization overhead compared to a vanilla val, which is why we have unsafe lazies in Scala 3.

Copy link
Member

Choose a reason for hiding this comment

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

I suppose it could be rewritten as a null init and probably squeeze out a few more femtoseconds.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Re: val vs lazy val, I didn't confirm with a benchmark, but my speculation was that val would regress the load time for users who just see JValue as type signature but do not hit the UnwrapArray parser.
#547 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Right, sure :) basically we are trading off a one-time load penalty for a penalty on every access.

@agourlay
Copy link
Contributor Author

Thank you very much for the super fast release 👏

I was able to validate that I get a nice performance boost from this change.

@agourlay agourlay deleted the cache-JValue-ClassTag branch June 27, 2023 09:40
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.

4 participants