8000 Optimizations for `ZIO#foreach*` methods by kyri-petrou · Pull Request #9365 · zio/zio · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Optimizations for ZIO#foreach* methods #9365

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

Merged
merged 6 commits into from
Dec 17, 2024

Conversation

kyri-petrou
Copy link
Contributor

This PR contains a number of micro-optimizations to improve performance of ZIO.foreach* and ZIO.foreachPar methods.

The optimizations are mostly focused on avoiding conversion of abstract Iterables (which use a List as the concrete type) to specialized collections (such as Arrays and Maps), and avoiding calculating a collection's size multiple times in foreachPar methods (which makes things even worse when the abstract Iterable is a list).

There are 2 optimization which contribute to an improvement of performance to foreach* methods but will likely improve performance elsewhere:

  • Check if an iterable is mutable.ArraySeq[A] (which is what arrays are wrapped in when they're casted to Iterables) in Chunk.fromIterable, so that we can simply copy the underlying array instead of iterating and adding elements to a builder.
  • Avoid calling strategy.unsafeOnQueueEmptySpace in various Queue take* / poll methods when the queue was already empty

@kyri-petrou
Copy link
Contributor Author

Aghh, of course Scala 2.12 doesn't have some of the methods I'm using in this PR for ArraySeq. I'll set to draft while I fix it

@kyri-petrou kyri-petrou marked this pull request as draft December 2, 2024 13:42
case Some(n) => foreachPar(n)(as)(f)
case None => foreachParUnbounded(as)(f)
}
as.size match {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't it be better to use as.knownSize? Iterating a List will be slow due to memory locality, not just because of the element traversal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are other things that depend on having the size of the collection, such as the sizing of an array used within one of the internal methods. So using knownSize won't work because it will return -1 for Lists or other collections that their size is not known. At least with this PR we're ensuring that size is called just once, unlike before that it was being called 2 or 3 times

ZIO.parallelismWith {
case Some(n) => foreachParDiscard(n)(as)(f)
case None => foreachParUnboundedDiscard(as)(f)
ZIO.succeed(as).flatMap { as =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason you need ZIO.succeed + flatMap here vs ZIO.suspendSucceed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just wanted to avoid accidentally using as (the lazy => Iterable[A]) as method arguments, but I think suspendSucceed is probably cleaner so I'll change this


final protected def fromArraySeq[A](seq: mutable.ArraySeq[A]): Chunk[A] = {
val arr = seq.array
Chunk.fromArray(Array.copyAs(arr, arr.length)(seq.elemTag)).asInstanceOf[Chunk[A]]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm very surprised the fromArray method doesn't copy/require Unsafe...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah the Unsafe implicit would actually be good to have. However that wouldn't be binary compatible :(

val iterator = map.iterator
val builder = Map.newBuilder[Key2, Value2]

val ft = f.tupled
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this better than unpacking the tuples manually?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just thought it makes the code cleaner. Changed to manual unpacking as it's more performant

arr(i) = b
i += 1
}
.as(arr)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we have an as operator that doesn't suspend the value?

Suggested change
.as(arr)
.flatMap(_ => Exit.succeed(arr))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I think your suggestion is the best that we can do

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although it wouldn't work without some sort of suspension as arr would otherwise be read before the whileLoop is evaluated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On second thought, I'm going to keep this as is for now and revisit this in a separate PR as there are multiple places that this is occuring

@kyri-petrou kyri-petrou marked this pull request as ready for review December 15, 2024 02:07
@kyri-petrou kyri-petrou requested a review from guizmaii December 16, 2024 13:34
8000
@kyri-petrou kyri-petrou merged commit edc2fbf into zio:series/2.x Dec 17, 2024
18 checks passed
@kyri-petrou kyri-petrou deleted the optimize-zio-foreach-par branch December 17, 2024 01:32
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.

3 participants
0