Open SethTisue opened 11 months ago
Would be nice to have indeed
For bigger projects that would require things like fs2
, is the toolkit used over the alternatives from the same stack ?
If you depend on Toolkit and other libraries too, it all just ends up piled into your classpath and your application code is free to use or ignore or whatever it wants.
For bigger projects that would require things like
fs2
, is the toolkit used over the alternatives from the same stack ?
@iusildra see also the typelevel toolkit.
I feel like something like parallel collections would be nice, but probably not parallel collections themselves.
IIRC the codebase was pretty old, had a lot of weird non-idiomatic/no-longer-idiomatic APIs (e.g. configuring things via mutability?), wasn't super well maintained, and didn't get widely adopted even after 10+ years.
AFAIK the recommendation for years about using scala-parallel-collections
was "Don't", so unless that has changed for some reason it feels very weird to bundle it with the toolkit
@lihaoyi I am not using toolkit and I am not likely to use it in a near future, but I am using parallel collections in my application to parallelize a few inner loops processing thousands of items. If I am not to use parallel collections, what should I use instead?
recommendation for years about using scala-parallel-collections was "Don't"
Could you provide some link? I do not see any kind of warning in https://docs.scala-lang.org/overviews/parallel-collections/overview.html. It says:
The hope was, and still is, that implicit parallelism behind a collections abstraction will bring reliable parallel execution one step closer to the workflow of mainstream developers.
I'll answer this point by point.
IIRC the codebase was pretty old
I would describe it as mature and well-established. No one has needed to write a competitor, because this one does the job. I don't see why it matters how old the code is unless there is actually something wrong with the code.
had a lot of weird non-idiomatic/no-longer-idiomatic APIs (e.g. configuring things via mutability?)
I acknowledge that the configuring the thread pool usage is a little weird, but it's rare that anyone doing Toolkit-y, script-y type things would need to configure it at all. Most of the time you just want to do some stuff in parallel and it does that. Easy things easy, harder things possible.
wasn't super well maintained
It doesn't need any maintenance. There aren't any open bugs to speak of.
didn't get widely adopted even after 10+ years.
No? There are 3500 hits at https://github.com/search?q=scala-parallel-collections&type=code .
You might have this impression if people aren't talking about it, but sometimes there isn't a lot to say about a workhorse library like this.
AFAIK the recommendation for years about using scala-parallel-collections was "Don't"
I agree with Ondrej. There is no such recommendation.
One aspect of the parallel collections that did have a negative reputation was how much they deepened and complicated the collections hierarchy when they were introduced. But we fixed that in Scala 2.13, when the parallel collections were re-engineered, became a separate library, and stopped being intertwined with serial collections.
From the toolkit introduction:
The Toolkit supports:
- Scala 3 and Scala 2
- JVM, Scala.js, and Scala Native
I was under the impression that only libraries supporting Scala.js and Scala Native would be included, which I don't think is the case for the parallel collections.
Although I guess one could publish them for Scala.js and Scala Native 0.4.x with dummy implementations that just wrap the sequential collections.
(As an aside, I do use the parallel collections a lot on scala-cli scripts, so I'm excited to see this landing)
One aspect of the parallel collections that did have a negative reputation was how much they deepened and complicated the collections hierarchy when they were introduced. But we fixed that in Scala 2.13, when the parallel collections were re-engineered, became a separate library, and stopped being intertwined with serial collections.
This may be where my impression came from. I must admit, I don't have any concrete objections here, other than a vague feeling. Perhaps it's no longer a problem since it's been modularized.
How do parallel collections play with scala.concurrent.ExecutionContext
? My understanding is that implicit ec: ExecutionContext
is the de-facto standard in way to handle thread pools and executors in Scala, being in the standard library and all.
If parallel collections are now a module, does that mean they are open to modifications? If so, then we should definitely consider sanding off some of the rough edges as part of including it in the toolkit, e.g. replacing the mutable threadpool thing with an implicit ec: ExecutionContext
, and going over the rest of the doc/API/architecture to see if there's anything else we could modernize. This would be similar to what we're doing with uPickle and OS-Lib and others. If we do that, then I have no objection to including it in the toolkit
If I am not to use parallel collections, what should I use instead?
I've generally used scala.concurrent.Future
here:
val foo = items.map(x => Future{doThing(x)}).map(Await.result(_, Duration.Inf))
I admit it's a bit more clunky to use than .par
, but the API can definitely be simplified if necessary, and having a consistent ExecutionContext
interface that all your asynchronous operations use is definitely nice.
Nowadays, there are fs2, zio , pekko/akka streams out there, -1 for including parallel collection
There isn't anything I'm aware of that matches the simplicity of the parallel collections, so I think it's worth including for that reason.
Just to echo Mr Li's impression, but maybe it's just that (as happens) the real-world effectiveness did not match heightened expectations.
As usual, Mr Ichoran's summation is especially succinct and persuasive.
The reply to He-Pin's objection, and one is liable to mix up one's kerrs, is that the toolkit is about simplicity and scope. So an easy-to-use solution of limited scope is desirable.
What about sending a MR to scala/dotty compiler and let the compiler use it first? @som-snytt
@SethTisue submit a poll on reddit and see?
I forgot to say that I checked https://mvnrepository.com/artifact/org.scala-lang.modules/scala-parallel-collections/1.0.4/usages?p=1 but I wasn't sure what it implies.
Released for Scala 3 nullifies any suggestion the project is moribund.
Actually, #14 in this list is impressive: https://mvnrepository.com/open-source/collections
Worth adding that lack of tickets does not directly imply quality, but may imply usability in proportion to the use cases people use it for.
@He-Pin also worth adding that suitability for a limited use case does not imply suitability for others. But one may ask if "inclusion in the toolkit" constitutes an endorsement, and should the user receive further guidance.
I'm sorry this topic missed the recent survey that folks were complaining was already too long. "And before we let you hit submit, please tell us your thoughts on the parallel collections. Yes, that one. Good for noobs who have no opinions about concurrency and/or parallelism? or is par short for paradigmatic?"
@som-snytt Hum, interesting , Zio is on the list too, IIRC, zio has zero dependency.
@SethTisue What's the status of this https://github.com/scala/scala-parallel-collections/issues/22
@SethTisue What's the status of this https://github.com/scala/scala-parallel-collections/issues/22
That's about 2.12 and 2.12 isn't relevant to the Toolkit, which only targets 2.13 and 3
I was under the impression that only libraries supporting Scala.js and Scala Native would be included, which I don't think is the case for the parallel collections. Although I guess one could publish them for Scala.js and Scala Native 0.4.x with dummy implementations that just wrap the sequential collections.
Interesting. I've recorded the suggestion at https://github.com/scala/scala-parallel-collections/issues/251 .
Note that the Toolkit includes os-lib, so that's a major precedent for including something that does stuff that only works on the JVM.
I put a little straw poll up at https://twitter.com/SethTisue/status/1703774958789255199 to see what people think
The point of having Toolkit is to be batteries-included for day-to-day operation, like parsing JSON a la Python, and hopefully set the new and experienced users on the paved path of Scala.
Parallel operation is an interesting one because it is one of the recognizable benefit of adopting functional programming, and to some extent it's the battle ground of "how we do things" in Scala for the last decade, especially in terms of balancing high volume of request and slow/limited IO operations. I feel like, either you're coming from Akka, Typelevel, or even plain ExecutionContext, one consensus is to avoid performing blocking operation without marking it as such.
In other words, the simplicity of parallel collection might do the wrong thing for exactly the kind of use cases that one would want to use parallel collection, like reading many files for file <- files.par
and performing side effects as Seth listed as example. Maybe as they say, the answer is always traverse.
the Scala org has a strong interest in keeping it alive, as it's mentioned in Scala books and such.
There is also this course: https://www.coursera.org/learn/scala-parallel-programming.
IIRC, when I went through the course, it wasn't spelled out too clearly/often that while the parallel collections used to be integrated with the main Scala release, they later got separated out into module https://github.com/scala/scala-parallel-collections.
I see in https://github.com/scala/scala-parallel-collections/blob/439b9c6e7e68c0407d69f7b09074ed03c82271aa/README.md?plain=1#L36 that in older versions of Scala one could just invoke .par on a collection, whereas in later versions, the following import is needed to be able to do that:
import scala.collection.parallel.CollectionConverters._
One thing that might discourage some people from looking further into parallel collections is the sentence in bold in the following passage in https://www.packtpub.com/product/learning-concurrent-programming-in-scala/9781783281411 (1st edition):
Even when you are sure that parallel collections improve program performance, you should think twice before using them. Donald Knuth once coined the phrase Premature optimization is the root of all evil. It is neither desirable nor necessary to use parallel collections wherever possible. In some cases, parallel collections give negligible or no increase in speed. In other situations, they could be speeding up a part of the program that is not the real bottleneck. Before using parallel collections, make sure to investigate which part of the program takes the most time, and whether it is worth parallelizing. The only practical way of doing so is by correctly measuring the running time of the parts of your application. In Chapter 9, Concurrency in Practice, we will introduce a framework called ScalaMeter, which offers a more robust way to measure program performance than what we saw in this chapter.
Not sure if the second edition differs in this respect.
P.S. if there is ever a third edition, updated for Scala 3, I suggested a photo for the front cover: https://twitter.com/philip_schwarz/status/1530584650481127430
parallel-collections
may be useful in scripting and prototyping, as it's often the case that even a simple automation app does a batch of IO operations or processing. If we were to choose a library for this purpose, parallel-collections
would probably make the most sense - it's very conceptually lightweight. It uses Scala's interfaces and introduces a par
method and a few types. That's all, no new concepts. It requires a bit of a deeper look to ensure it's always the case, but it fits well from the perspective of usability and employed approach.
However, it's released only for the JVM. It makes sense, as there is currently no way to do that on other platforms. It will change after the next minor of Scala Native, so we may consider releasing parallel-collections
on Native. If that's done, then two out of three platforms are supported. Nevertheless, for Scala.js, it likely never will be supported. We may skip the Scala.js support as precedence (os-lib) already exists, but that will again require carefully weighing the pros and cons.
Adding a dummy implementation for any platform would do more harm than good. One would expect that .par
actually makes the code parallel. A transparent lack of support may lead to confusion and frustration. It's better not to add it to the classpath altogether.
If we added parallel-collections
before releasing it for the Native, we would break our promise of supporting other Scala platforms. I would heavily argue against that.
One question here: does scala-parallel-collections have any issues around blocking?
e.g. A naive Future
-based approach val foo = items.map(x => Future{doThing(x)}).map(Await.result(_, Duration.Inf))
works fine as a top-level call to parallelize things, but it causes the calling-thread to be blocked while executing. One thread being blocked isn't a big deal, but if naively nested (e.g. if doThing
does its own Future
s and Await.result
inside), that can result in an arbitrarily large number of threads being blocked, which before Loom/VirtualThreads cost 1-4mb each and can add up to a lot of resource footprint quickly when parallelizing a large collection.
When you use .par.map
, it becomes second nature to just treat parallel collections transformations the same way as normal collections transformations. Thus it would be expected that people would started nesting them, intentionally or unintentionally, the same way that normal collection transformations can be nested. Is this a footgun we need to be worried about?
scala-parallel-collections is implemented using java.util.concurrent.ForkJoinPool
@szymon-rd will the new Scala Native support ForkJoinPool
? if so, we have a path forward that wouldn't cause undue delay
@lihaoyi I suspect this answers your question as well. the implementation isn't naive
I don't know if @axel22 sees GitHub notifications, but perhaps he'd like to weigh in, as the first author of https://infoscience.epfl.ch/record/165523/files/techrep.pdf ("On a Generic Parallel Collection Framework", Aleksandar Prokopec, Phil Bawgell, Tiark Rompf, Martin Odersky, June 2011)
That it uses forkjoinpool doesnt really answer my question; Futures can use forkjoin pool too, and still suffer this failure mode. One option is you run out of threads, one option is you spawn more threads and eventually run out of memory because threads are expensive. I'm not aware of any third option apart from Loom virtual threads that are a lot cheaper.
Naive or not, these are hard problems in the design space for concurrency/threading frameworks, so I'd want to know how this stuff works. The fact that someone probably thought very hard about it a decade ago doesn't tell me what tradeoffs they ended up choosing, and thus what pitfalls any user-land code will have to be careful to avoid. A quick skim of the docs didnt pull up anything here, maybe it's not a problem, but it's worth confirming
will the new Scala Native support
ForkJoinPool
Yes, it already does in the 0.5.0 snapshots.
Probably I'm the only one who ported https://github.com/axel22/ScalaDays2012-TrieMap shortly before bed last night. It was a no-brainer and worked the first time in 2023 under WSL, the requisite decade since
Author: Aleksandar Prokopec <axel22@gmail.com>
Date: Mon May 13 17:34:35 2013 +0200
Update version to 2.10.0
I wonder if toolkit includes scala-swing
and how would I know.
the voting here was sort of incidental and what I really hoped is that more folks would come by here (I included the link) and give some more feedback beyond just a vote 🤷 anyway, it does show substantial usage
there are some "dislike/avoid" answers but we don't know what their reasons are. (one possible explanation is that they're might be fs2/Akka/ZIO users who prefer working with a richer API. which is a valid position, but also out of scope for Toolkit)
occasional/casual users are still a use case
some support at https://users.scala-lang.org/t/scala-toolkit-0-2-0-is-out-discussion/9355/4 :
Another good addition would be to make
.par
available in the Toolkit for some quick and dirty parallel computations in the REPL or scripts for everyday usage. Currently I can do it with a using directive in Scala-cli but it’s worth considering adding it to the Toolkit, so it can feel like parallel collections are still part of the standard library like they used to be (it would also help students if used in the online courses).
Yes I support this, especially currently the students of the Parallel Programming course are in dire need of an easy quick way to use .par
along with the now-outdated lecture video code. (I'm telling them to use Scastie or Scala-cli)
Maybe instead of just saying that the aggregate function
is not relevant for sequential collections. Use `foldLeft(z)(seqop) instead., "2.13.0
the Scala Doc for aggregate could also say that aggregate is relevant for parallel collections and it could provide a link to https://github.com/scala/scala-parallel-collections.
@philipschwarz that seems irrelevant to this ticket. but I've opened https://github.com/scala/bug/issues/12900 on it
@SethTisue
@philipschwarz that seems irrelevant to this ticket. but I've opened scala/bug#12900 on it
yes, sorry, I was prompted by the thought that students of the Parallel Programming course (mentioned by @spamegg1) might go to the Scala Doc for the aggregate function and find it confusing that the function is deprecated, and that there is no mention of the parallel collections library, where the function still makes sense.
Thank you for opening the ticket.
I was a bit surprised recently to realize that we didn't include scala-parallel-collections in the Toolkit.
Just now I looked at the old spreadsheet of candidate libraries, assuming I'd see it was considered and rejected or postponed, but I don't even see a spreadsheet entry for it? Was it really never considered?
I think we should include it, or at least discuss including it. It has a lot of things going for it:
scala.
namespaceWhat about the library's usefulness/importance?
For some tasks, it's extremely convenient and can give a large speed benefit. I think it didn't end up being quite as widely used as we'd originally envisioned, but when the unit of work that you need to happen in parallel is large enough, it's super easy to use parallel collections to get a big speedup.
Here's an example. I have a Scala-CLI script that does several hundred independent GitHub API queries. With only this small diff:
I sped up the script from taking 5 minutes to taking less than 30 seconds.