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

Optimise Apply[{ Either, \/ }].apply2 #1958

Merged
merged 6 commits into from
Aug 14, 2018
Merged

Optimise Apply[{ Either, \/ }].apply2 #1958

merged 6 commits into from
Aug 14, 2018

Conversation

fommil
Copy link
Contributor

@fommil fommil commented Aug 13, 2018

close #1956

Running this perf test

jsonformat/jmh:run -i 5 -wi 5 -f1 -t1 -w1 -r1 .*Geo.*decodeManual*

from https://gitlab.com/fommil/scalaz-deriving

BEFORE this change

Benchmark                               Mode  Cnt      Score     Error  Units
GeoJSONBenchmarks.decodeManualError    thrpt    5  25727.854 ± 263.755  ops/s
GeoJSONBenchmarks.decodeManualSuccess  thrpt    5  26943.126 ± 230.573  ops/s

AFTER this change

GeoJSONBenchmarks.decodeManualError    thrpt    5  70845.254 ± 890.444  ops/s
GeoJSONBenchmarks.decodeManualSuccess  thrpt    5  84736.783 ± 473.980  ops/s

and WITH custom short circution impl of traverseDisjunction from #1956 (which is used in the repo's master... back out that change to run the above tests)

GeoJSONBenchmarks.decodeManualError    thrpt    5  586733.762 ±  6389.296  ops/s
GeoJSONBenchmarks.decodeManualSuccess  thrpt    5  110961.246 ±  468.384  ops/s

async-profiler alloc tracing shows that the \/- allocations are quite heavy. I guess that's to be expected.

@jdegoes
Copy link
Member

jdegoes commented Aug 13, 2018

@fommil Awesome work. Would be great to do this for bind too.

@fommil
Copy link
Contributor Author

fommil commented Aug 13, 2018

@jdegoes done, that actually has a non-negligible impact on my perf tests which aren't even testing for this!

[info] Benchmark                               Mode  Cnt      Score     Error  Units
[info] GeoJSONBenchmarks.decodeManualError    thrpt    5  74902.151 ± 488.520  ops/s
[info] GeoJSONBenchmarks.decodeManualSuccess  thrpt    5  88754.010 ± 759.063  ops/s

@fommil
Copy link
Contributor Author

fommil commented Aug 13, 2018

https://typelevel.org/blog/2014/11/10/why_is_adt_pattern_matching_allowed.html#adts-are-ok-to-go suggests that these optimisations are unneeded. My perf tests suggest otherwise on 2.12...

@edmundnoble
Copy link
Contributor

You have quite a few "optimizations" here, not just touching apply2. Is it clear which are actually responsible for speedup, and why?

@hobwekiva hobwekiva added the scalaz7 Scalaz 7.x label Aug 13, 2018
@fommil
Copy link
Contributor Author

fommil commented Aug 13, 2018

@edmundnoble the numbers cited are just the \/.apply2, everything else seems to have improved things a little bit and are just opportunistic while I'm in the area.

case \/-(a) => \/-(g(a))
case b @ -\/(_) => b
case \/-(b) => \/-(g(b))
case a => a.asInstanceOf[A \/ D]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

faster because there is no check in the second case

@fommil
Copy link
Contributor Author

fommil commented Aug 13, 2018

@xuwei-k what do you think?

def traverseDisjunction[E, B](f: A => E \/ B): E \/ IList[B] = \/-(
map { a =>
f(a) match {
case -\/(err) => return -\/(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

write a tailrec function instead of using exceptions?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also map runs in the opposite direction:

   def map[B](f: A => B): IList[B] = reverse.reverseMap(f)

It starts from the end, so your traverse is not the same as the usual one.
^^ a perfect example why side-effects are evil ;)

Copy link
Contributor

@hobwekiva hobwekiva Aug 13, 2018

Choose a reason for hiding this comment

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

def traverse[E, A, B](l: List[A], f: A => E Either B): E Either List[B] = {
  @annotation.tailrec def go(l: List[A], r: List[B]): E Either List[B] =
  l match {
    case a :: as =>
      f(a) match {
        case Right(b) => go(as, b :: r)
        case Left(e) => Left(e)
      }
    case Nil => Right(r.reverse)
  }
  go(l, Nil)
}

println(
  traverse(
    List(1, 2, 3, 4), 
    (a : Int) => if (a % 2 == 0) Left(a) else Right(a)))

@fommil fommil merged commit c07e7ed into scalaz:series/7.2.x Aug 14, 2018
@fommil fommil deleted the optimise-disjunction-apply branch August 14, 2018 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scalaz7 Scalaz 7.x
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants