-
Notifications
You must be signed in to change notification settings - Fork 157
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
chore: Make use of pattern matching on singleton type instead. #1026
Conversation
Build is failing |
This comment was marked as outdated.
This comment was marked as outdated.
I just tried it. I forgot about scala/bug#12569 which I think it's just necessary to That is some kind of pattern matching bug, but it's "just a warning". |
Aliasing it and hiding the type seems to work without nowarn.
The stdlib examples all "cache" the canonical |
Hmm, with all of the workarounds not sure if this is that much better than whats currently there |
case _ => throw new IllegalStateException() // won't happen, compiler exhaustiveness check pleaser | ||
case _: NotApplied.type => super.onUpstreamFailure(ex) | ||
case t: Throwable => super.onUpstreamFailure(t) | ||
case _ => throw new IllegalStateException() // won't happen, compiler exhaustiveness check pleaser |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better which cover the error case. @mdedetrich
@som-snytt what about
|
@mdedetrich @som-snytt cc, performance is the same as BTW, the sbt-jmh issue on Windows been verified fixed, thanks. And I will run this bench again when I find time. |
Jmh/run -i 10 -wi 10 -f1 -t1 .CollectBenchmark.
|
@mdedetrich @pjfanning ping, what's your option on this, I tested seems the performance is the same as my previous commit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[info] Benchmark Mode Cnt Score Error Units
[info] CollectBenchmark.benchNewCollect thrpt 3 9147229.404 ± 255711.579 ops/s
[info] CollectBenchmark.benchOldCollect thrpt 3 8889505.583 ± 985315.585 ops/s
[info] CollectBenchmark.oldIFACMPNE thrpt 3 8899066.666 ± 271187.167 ops/s
Just a quick bench, looks great.
But the next benchmark was totally difference:
[info] Benchmark Mode Cnt Score Error Units
[info] CollectBenchmark.benchNewCollect thrpt 3 9174780.179 ± 828160.581 ops/s
[info] CollectBenchmark.benchOldCollect thrpt 3 9178338.944 ± 320020.649 ops/s
[info] CollectBenchmark.oldIFACMPNE thrpt 3 9120740.869 ± 101541.057 ops/s
@@ -43,18 +44,17 @@ private[pekko] final class CollectWhile[In, Out](pf: PartialFunction[In, Out]) e | |||
private lazy val decider = inheritedAttributes.mandatoryAttribute[SupervisionStrategy].decider | |||
import Collect.NotApplied | |||
|
|||
@nowarn("msg=Any") | |||
override final def onPush(): Unit = | |||
try { | |||
// 1. `applyOrElse` is faster than (`pf.isDefinedAt` and then `pf.apply`) | |||
// 2. using reference comparing here instead of pattern matching can generate less and quicker bytecode, | |||
// eg: just a simple `IF_ACMPNE`, and you can find the same trick in `Collect` operator. | |||
// If you interest, you can check the associated PR for this change and the | |||
// current implementation of `scala.collection.IterableOnceOps.collectFirst`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will these comments be outdated after this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the it's still apply, maybe drop the :
// If you interest, you can check the associated PR for this change and the current implementation of
scala.collection.IterableOnceOps.collectFirst
.
} else { | ||
push(out, result.asInstanceOf[Out]) | ||
pf.applyOrElse(grab(in), NotApplied) match { | ||
case _: NotApplied.type => completeStage() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not match on the singleton instance instead? In any case, it seems uncommon to match on the singleton type.
It's not even easy to say if matching on the type or the reference is supposed to be faster in general, and maybe the Scala compiler should make the right choice. (On one hand, reference comparison is as fast as it gets, on the other hand, getting the singleton instance reference is at least a static field lookup which might not be faster than comparing types, would depend on the JIT compiler).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not match on the singleton instance instead? In any case, it seems uncommon to match on the singleton type.
I am under the impression it only works if you match on the type and not on the instance, @som-snytt can you confirm?
By only works I mean producing the optimal bytecode which was the point of the change in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdedetrich @jrudolph I think just as @som-snytt said , the current version should generate less bytecode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The clunky pattern is just to avoid a clunky eq
.
You might use a local val to avoid "cost of obtaining the value".
This is like pattern match on List with Nil case, where match on ::
first, then just case nil
. It's nice if you have a type to check.
An example where just "obtaining the value" is expensive is NoSymbol
in the compiler, because of the "cake". There are edits trying to choose between eq
and ==
but that is swamped by the fetch. It is outweighed by style concerns.
If you can measure a difference reliably, then of course you are equipped to choose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL, thanks for the explanations. I see how matching on the instance needs to call equals
, especially if the scrutinee is of Any
type (though, this could be optimized away, if you know that the singleton does not override equals
?. Somewhat surprising to see that for this type match the reference comparison is preferred over a checkcast
(though, of course, I don't think any of this matters).
final results:
|
After carefully bench and diff the bytecode, I think this one should be ready to go. |
More info: @som-snytt point out in https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#type-patterns see singleton.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm from my side, would also wait for another review from someone else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
More info: @som-snytt point out in https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#type-patterns see singleton.
Thanks @som-snytt for this.
continue : #983