Skip to content

Improve performance of Parallel by overriding map2 to avoid tupling #2155

Description

@djspiewak

This is a bit of low-hanging fruit, but we currently define Parallel[F] given GenSpawn[F, _] in terms of the following construction:

      final override def map2[A, B, Z](fa: ParallelF[F, A], fb: ParallelF[F, B])(
          f: (A, B) => Z): ParallelF[F, Z] =
        ParallelF(
          F.both(ParallelF.value(fa), ParallelF.value(fb)).map { case (a, b) => f(a, b) }
        )

That's cool but there's a lot of unnecessary boxing in here, both in both and in racePair underneath. We can actually do a lot better given GenConcurrent[F, _], which will be the case quite a lot of the time in practical code. However, rather than creating some sort of sneaky implicit prioritization thing, I think the right approach here is probably to add a mapBoth function (feel free to bikeshed the name) to GenSpawn, then override it in GenConcurrent with a more efficient implementation in terms of Fiber and Deferred.

Overall this probably sounds a lot more intimidating of an enhancement than it actually is. Tldr, add the following to GenSpawn:

def mapBoth[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => F[C]): F[C] = ???

Delegate the implementation to both in GenSpawn, but then override that implementation in GenConcurrent with something implemented in terms of Fiber and Deferred. The results should be a noticeable improvement in performance of the par operators.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions