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

Speeding up Scala 3 Writing macros #440

Merged
merged 16 commits into from
Feb 6, 2023
Merged

Speeding up Scala 3 Writing macros #440

merged 16 commits into from
Feb 6, 2023

Conversation

lihaoyi
Copy link
Member

@lihaoyi lihaoyi commented Feb 1, 2023

Fixes #389 and #388

This PR moves a bunch of logic from runtime to compile time, and optimizes whatever runtime logic remains. It also consolidates the logic with the Scala 2 logic as much as possible, e.g. re-using writeSnippet to manage writes and CaseObjectContext/HugeCaseObjectContext to manage validation and error reporting during a read

Running some ad-hoc benchmarks on my laptop, ./mill bench.jvm.run, it gives a significant speedup on reads and writes, and brings it close to the Scala 2.13 numbers (higher is better):

Benchmark (5000ms) Scala 3 Before Scala 3 After Scala 2
upickleDefault Read 637 1065 1403
upickleDefault Write 839 1452 1549
upickleDefaultByteArray Read 582 1172 1126
upickleDefaultByteArray Write 847 1218 1277
upickleDefaultBinary Read 925 3853 3844
upickleDefaultBinary Write 1252 3117 3666
upickleDefaultCached Read 620 1300 1412
upickleDefaultCached Write 829 1555 1588
upickleDefaultByteArrayCached Read 575 1182 1095
upickleDefaultByteArrayCached Write 838 1223 1297
upickleDefaultBinaryCached Read 928 3825 3885
upickleDefaultBinaryCached Write 1266 2907 3674

Note that the generated code, especially for reads, is still not as optimized as the Scala 2 versions:

  1. I couldn't figure out how to generate an anonymous class with typed fields in Scala 3, so I'm putting things in an Array[Any]
  2. I couldn't figure out how to generate match statements, so I generated a Map[K, V] and look it up at runtime.

Fixing this issues and moving the reading logic into the macro should also be possible, but can happen in a separate PR

All existing tests pass. Added a regression test for the recursive Scala 3 scenario that hangs on master. Also moved a bunch of AdvancedTests into the shared folder now that they work

@lihaoyi lihaoyi changed the title WIP Speeding up Scala 3 macros Speeding up Scala 3 Writing macros Feb 1, 2023
Comment on lines +283 to +284
w: Any,
value: Any) = {
Copy link
Member Author

Choose a reason for hiding this comment

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

Had to make these un-typed because I couldn't figure out how to make the Scala 3 macro quotes/splices play nicely with the path dependent prefix.Writer type. A bit gross but shouldn't affect perf or correctness

mappedArgsI: String,
w: Writer[V],
value: V) = {
def writeSnippet[R, V](objectAttributeKeyWriteMap: CharSequence => CharSequence,
Copy link
Member Author

Choose a reason for hiding this comment

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

I couldn't figure out how to subclass the path dependent prefix.CaseW type in the macro, so I had to instantiate it in the inline part first and then call into the macro after. That means the call to writeSnippet is no longer from the subclass, and it thus can no longer be protected

case m: Mirror.SumOf[T] =>
inline compiletime.erasedValue[T] match {
case _: scala.reflect.Enum =>
val valueOf = macros.enumValueOf[T]
Copy link

@bishabosha bishabosha Feb 1, 2023

Choose a reason for hiding this comment

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

this won't work for ADT enums, the solution here is to assert all cases are singleton:

summonAll[Tuple.Map[m.MirroredElemTypes, [S] =>> S <:< Singleton]] // assert all singleton

maybe there is a more erased way to do this

@lihaoyi
Copy link
Member Author

lihaoyi commented Feb 2, 2023

I'll leave this over the weekend, and if there aren't any more comments I'll merge it then. All the existing/new/newly-enabled tests pass, and the benchmarks demonstrate the non-functional improvements, so I can't have broken anything too badly

The issue noticed by @bishabosha is a limitation in existing code, which is why it was not picked up by any tests (since it never worked). Fixing that can come in a follow up

Copy link
Member

@lefou lefou left a comment

Choose a reason for hiding this comment

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

This looks good to me, although I'm myself not experienced in Scala 3 Macros. But tests pass and benchmark looks promising.

After we merge this, I'd like to cut a next milestone release 3.0.0-M2 to get this changed version tested in as many places as possible.

@lefou lefou added this to the 3.0.0-M2 milestone Feb 5, 2023
@lihaoyi lihaoyi merged commit 0f87fae into main Feb 6, 2023
@lefou lefou deleted the scala-3-macros branch February 6, 2023 06:25
@lefou
Copy link
Member

lefou commented Feb 6, 2023

There seem to be issues with this change. E.g. in Ammonite, the integration tests don't succeed.

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.

Scala 3 serialization macros are very inefficient compared to Scala 2
3 participants