--
You received this message because you are subscribed to the Google Groups "scala-internals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-interna...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
1. spliterator, which will return an ordinary java.util.Spliterator2. streamed, which will return a value-class-wrapped java Stream created from that spliterator; it will take one argument which is whether the stream should be run in parallel or not (default value: it should be)
(2) Java streams will have one extension method, called either extended or asScala, which wraps them in that same value class. (I favor extended because it won't _really_ be asScala, at least in the usual sense.)
(3) The value class will contain an asJava method to switch back to the plain unwrapped stream, but otherwise will present all the familiar Scala methods that you expect to find on a TraversableOnce. I do not think I'll be able to make the value class fit into the hierarchy, but you'll have your full set of toIterator etc. methods if you really want to go fully into Scala-land. I plan on implementing even really inefficient methods, but these will be gated by an implicit that you will have to import separately.
Comments are welcome. My goal is to make the Java 8 compatibility layer in Scala the best place to use Java 8 Streams (better than Java), and to make it as easy and performant as possible to switch back and forth between Scala collections and Java 8 streams.
Please use different methods to differentiate sequential from parallel, instead of a boolean parameter. A boolean parameter is a very bad thing for Scala.js, because it cannot decide to eliminate the parallel version.Hi,Here are a couple concerns for Scala.js.
The result will be that the program will not link, because there is no parallelism in Scala.js. With different methods, the reachability analysis of Scala.js can detect that you never call the parallel method, and hence that part of the codebase can be completely ignored, which allows the whole thing to link. (Even better, the parallel versions would be an implicit pimp, in a different artifact.)
With different methods, there's no need for a default. If there must be a default, it should either be sequential, or somehow Scala.js should be able to make it sequential. I am not even sure parallel is the right default on the JVM anyway, but that is out of my area of expertise.
1. spliterator, which will return an ordinary java.util.Spliterator2. streamed, which will return a value-class-wrapped java Stream created from that spliterator; it will take one argument which is whether the stream should be run in parallel or not (default value: it should be)
What's the reason for going with the Java naming in one case, but not in the other?
That seems inconsistent.
(Parallel by default also seems like a questionable choice.)
What's the reasoning behind that wrapping?
(2) Java streams will have one extension method, called either extended or asScala, which wraps them in that same value class. (I favor extended because it won't _really_ be asScala, at least in the usual sense.)
"extended" ... feels like a very non-descriptive name to me.
(3) The value class will contain an asJava method to switch back to the plain unwrapped stream, but otherwise will present all the familiar Scala methods that you expect to find on a TraversableOnce. I do not think I'll be able to make the value class fit into the hierarchy, but you'll have your full set of toIterator etc. methods if you really want to go fully into Scala-land. I plan on implementing even really inefficient methods, but these will be gated by an implicit that you will have to import separately.
What will be the semantics behind this? What's the use-case? Having a Java collection and thinking "these transformations are too fast, let's make them a magnitude slower by converting them to Scala operations" ... (half joking, half serious).
Comments are welcome. My goal is to make the Java 8 compatibility layer in Scala the best place to use Java 8 Streams (better than Java), and to make it as easy and performant as possible to switch back and forth between Scala collections and Java 8 streams.
Don't Scala collections and Java collections serve different use-cases? Scala collections are pleasant to use and Java collections are fast. Hosting one on top of the other will most likely only combine the drawbacks, not the advantages.
On Tue, Feb 24, 2015 at 4:41 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:1. spliterator, which will return an ordinary java.util.Spliterator2. streamed, which will return a value-class-wrapped java Stream created from that spliterator; it will take one argument which is whether the stream should be run in parallel or not (default value: it should be)
What's the reason for going with the Java naming in one case, but not in the other?In one case you get a plain java Spliterator. In the other case, you don't. Hence the different naming.
That seems inconsistent.
(Parallel by default also seems like a questionable choice.)As I mentioned in my response to Sebastien, parallel seems like the primary use case from within Scala.
On Tuesday, February 24, 2015 at 9:49:34 AM UTC-8, Rex Kerr wrote:On Tue, Feb 24, 2015 at 4:41 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:1. spliterator, which will return an ordinary java.util.Spliterator2. streamed, which will return a value-class-wrapped java Stream created from that spliterator; it will take one argument which is whether the stream should be run in parallel or not (default value: it should be)
What's the reason for going with the Java naming in one case, but not in the other?In one case you get a plain java Spliterator. In the other case, you don't. Hence the different naming.That seems inconsistent.
(Parallel by default also seems like a questionable choice.)As I mentioned in my response to Sebastien, parallel seems like the primary use case from within Scala.
Parallel is atrociously slower for most use cases I've run into.
You need a rather large collection for parallel to be worth it. Most collections are small. I feel that parallelism for java streams should be the same for java and scala -- not parallel by default, and explicitly requested when desired. Deviation from what the defaults are between the two will only add extra information that a developer must keep in their head to use them. Getting a stream from a java collection and one from a scala collection should be as similar as possible, in my opinion.
and have a java.util.stream.IntStream out with no extra boxing of the Int values.Incidentally, transparent specialization via value class wrapping and type classes is looking very promising syntactically. For instance, I can already do something likeList("salmon", "herring", "perch").streamed.map(_.length).asJavaWhether this will preserve adequate performance is another issue. Aggressive inlining from scalac could certainly do it, but in its current incarnation I'm a bit doubtful that scalac + JVM will be up to the task.
On Tuesday, February 24, 2015 at 9:49:34 AM UTC-8, Rex Kerr wrote:On Tue, Feb 24, 2015 at 4:41 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:1. spliterator, which will return an ordinary java.util.Spliterator2. streamed, which will return a value-class-wrapped java Stream created from that spliterator; it will take one argument which is whether the stream should be run in parallel or not (default value: it should be)
What's the reason for going with the Java naming in one case, but not in the other?In one case you get a plain java Spliterator. In the other case, you don't. Hence the different naming.That seems inconsistent.
(Parallel by default also seems like a questionable choice.)As I mentioned in my response to Sebastien, parallel seems like the primary use case from within Scala.
Parallel is atrociously slower for most use cases I've run into. You need a rather large collection for parallel to be worth it. Most collections are small. I feel that parallelism for java streams should be the same for java and scala -- not parallel by default, and explicitly requested when desired. Deviation from what the defaults are between the two will only add extra information that a developer must keep in their head to use them. Getting a stream from a java collection and one from a scala collection should be as similar as possible, in my opinion.
On Tue, Feb 24, 2015 at 11:51 AM, Scott Carey <scott...@gmail.com> wrote:
On Tuesday, February 24, 2015 at 9:49:34 AM UTC-8, Rex Kerr wrote:On Tue, Feb 24, 2015 at 4:41 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:1. spliterator, which will return an ordinary java.util.Spliterator2. streamed, which will return a value-class-wrapped java Stream created from that spliterator; it will take one argument which is whether the stream should be run in parallel or not (default value: it should be)
What's the reason for going with the Java naming in one case, but not in the other?In one case you get a plain java Spliterator. In the other case, you don't. Hence the different naming.That seems inconsistent.
(Parallel by default also seems like a questionable choice.)As I mentioned in my response to Sebastien, parallel seems like the primary use case from within Scala.
Parallel is atrociously slower for most use cases I've run into.Well, it's atrociously slower than sequential unless you have heavy lifting needed. However, it is difficult to find a case where Scala's parallel collections are faster than parallel Java streams.
But this raises an interop point that I probably haven't given enough consideration: there will be increasing numbers of Java libraries that expect streams as input, and we want to easily back those with Scala collections.So, I think you're right: sequential should be the default, though parallel should be really easy to ask for (either as a parameter or separate method).You need a rather large collection for parallel to be worth it. Most collections are small. I feel that parallelism for java streams should be the same for java and scala -- not parallel by default, and explicitly requested when desired. Deviation from what the defaults are between the two will only add extra information that a developer must keep in their head to use them. Getting a stream from a java collection and one from a scala collection should be as similar as possible, in my opinion.Indeed, though I don't want to obscure important differences or throw away ease of use unnecessarily.If I can't end up making the specialization transparent, then I'd go for just copying .stream .parallelStream and .spliterator as Java collections do.
--Rex
I'm not sure that trying to hook into these manually specialized isn't a waste of time. They were obsolete before they were even introduced. No idea what the Oracle developers were thinking when they added these classes.
Also, maybe it would be good to know that ScalaBlitz has spliterators, that at least as of year ago were faster than Java's, were specialized and written in scala(+macros, to be honest).
So maybe, instead of doing specialization my hand, you can use the one that we've implemented in ScalaBlitz?
On Wed, Feb 25, 2015 at 4:07 AM, Dmitry Petrashko <darkd...@gmail.com> wrote:Also, maybe it would be good to know that ScalaBlitz has spliterators, that at least as of year ago were faster than Java's, were specialized and written in scala(+macros, to be honest).The "Stealers" in ScalaBlitz are specialized but don't implement any functionality, and don't really fit that well into the Java framework: lots of methods of no help to the Java Spliterators, and Java Spliterators require state flags that cannot be inferred from the methods in Stealer.
getExactSizeIfKnown,
in order to implement this one would need to check if current stealer is PreciseStealer). But not all of Stealer methods can be efficiently implemented in terms of Spliterator.So maybe, instead of doing specialization my hand, you can use the one that we've implemented in ScalaBlitz?Doesn't look like it would make things much easier; use cases are too different.
Also, the coverage of the Scala library isn't very extensive.
It's kind of a shame to maintain two ways of doing essentially the same thing, but I assume that the Java guys need what Spliterators have got, and ScalaBlitz machinery needs what Stealers have got, and it's pretty clunky to combine the two.I'll think about whether it's practical to construct a unified core that can then be decorated as either a Stealer or a Spliterator.
--Rex
Hi Rex,
On Wednesday, 25 February 2015 23:13:01 UTC+1, Rex Kerr wrote:On Wed, Feb 25, 2015 at 4:07 AM, Dmitry Petrashko <darkd...@gmail.com> wrote:Also, maybe it would be good to know that ScalaBlitz has spliterators, that at least as of year ago were faster than Java's, were specialized and written in scala(+macros, to be honest).The "Stealers" in ScalaBlitz are specialized but don't implement any functionality, and don't really fit that well into the Java framework: lots of methods of no help to the Java Spliterators, and Java Spliterators require state flags that cannot be inferred from the methods in Stealer.I assume you mean `characteristics`. They can be easily inferred, as all stealers are collection-specific and know value for every flag from underlying collection(ie if collection is mutable or sorted).
Speaking of methods, all Spliterator methods are implementable in terms of Stealer(the only tricky one isgetExactSizeIfKnown,
in order to implement this one would need to check if current stealer is PreciseStealer). But not all of Stealer methods can be efficiently implemented in terms of Spliterator.
So maybe, instead of doing specialization my hand, you can use the one that we've implemented in ScalaBlitz?Doesn't look like it would make things much easier; use cases are too different.Indeed ScalaBlitz wasn't targeted at interop with JavaStreams. But it seems that Oracle guys either followed what Alex was working on or even had direct discussions with him. Or same ideas were floating in the air :-). So implementing one in terms of other is not that hard. ScalaBlitz here has an advantage of being both transparent for Scala and more performant in case of small collections.
Also, the coverage of the Scala library isn't very extensive.AFAIK, The only commonly used collection that isn't covered is Vector(arrays and ranges are covered, both mutable and immutable maps and sets are covered). It wouldn't be to hard to implement spliterators for other collections like stacks and buffers.
While this isn't indeed a full coverage, that's a wonderful starting ground.
It's kind of a shame to maintain two ways of doing essentially the same thing, but I assume that the Java guys need what Spliterators have got, and ScalaBlitz machinery needs what Stealers have got, and it's pretty clunky to combine the two.I'll think about whether it's practical to construct a unified core that can then be decorated as either a Stealer or a Spliterator.Most of methods in Spliterator are there for a reason, though most of those reasons are performance oriented.
I believe that methods in Spliterator are a lot better thought-out, thanks to substantial experience that Alex has in this area,
just to illustrate: instead of having `tryAdvance` as spliterators, stealers have 3 methds `nextBatch`&`next`&`hasNext`. The advantage here is that due to
tryAdvance being effectively higher-order it is a lot harder to generate optimized code for it, as one would have a huge megamorphic dispatch inside.
This is an example that illustrates "But not all of Stealer methods can be efficiently implemented in terms of Spliterator" claim. And I believe that this is good argument to go with Stealers for scala, as they are better than Spliterator, and will also be enough for java interop.
If you are up to it, I'd propose to have a Skype meeting at some point, I believe we could have a good discussion here and settle on this minimal core.
--
While I would say that first one is stable, with 'optimize' there are several limitations that make it hard to proceed:
just to illustrate: instead of having `tryAdvance` as spliterators, stealers have 3 methds `nextBatch`&`next`&`hasNext`. The advantage here is that due to
tryAdvance being effectively higher-order it is a lot harder to generate optimized code for it, as one would have a huge megamorphic dispatch inside.
On Wed, Feb 25, 2015 at 3:21 PM, Dmitry Petrashko <darkd...@gmail.com> wrote:just to illustrate: instead of having `tryAdvance` as spliterators, stealers have 3 methds `nextBatch`&`next`&`hasNext`. The advantage here is that due to
tryAdvance being effectively higher-order it is a lot harder to generate optimized code for it, as one would have a huge megamorphic dispatch inside.I've just finished some exploratory performance testing of different iterator-like frameworks. They're all rather vulnerable to megamorphic dispatch problems. But it seems as though the hasNext/next form is the best overall, even if you can't really count on it. (Sometimes it's as good as a while loop, but it can eventually get megamorphized down to 20x worse performance.)Some implementations are a bit simpler if you go backwards: get the value first, then detect whether it was an actual value or if you finished instead. The performance is sometimes even better. But the difference is never so clear as to make it worth the different post-hoc way of doing things, and it only works if it is deployed optimistically (e.g. map won't check whether it should apply, it just does it, and then you have to check if the result is meaningful) which is difficult/surprising to reason about.
Most importantly, hasNext/next usually does as well at implementing a tryAdvance-like method as does direct creation of the method. So I'm tentatively on board with a "Stealer"-type splitter (i.e. hasNext/next not tryAdvance semantics).But all the concurrency logic still bothers me.Also, I don't see how it could possibly be more efficient to use Stealer.split than Spliterator.trySplit. Maybe a couple extra object creations are insignificant (probably so).
So I think there could be a common core, but I'm still doubtful that it's just stealing the Stealers from ScalaBlitz.--Rex
On Thursday, 26 February 2015 11:11:23 UTC+1, Rex Kerr wrote:On Wed, Feb 25, 2015 at 3:21 PM, Dmitry Petrashko <darkd...@gmail.com> wrote:just to illustrate: instead of having `tryAdvance` as spliterators, stealers have 3 methds `nextBatch`&`next`&`hasNext`. The advantage here is that due to
tryAdvance being effectively higher-order it is a lot harder to generate optimized code for it, as one would have a huge megamorphic dispatch inside.I've just finished some exploratory performance testing of different iterator-like frameworks. They're all rather vulnerable to megamorphic dispatch problems. But it seems as though the hasNext/next form is the best overall, even if you can't really count on it. (Sometimes it's as good as a while loop, but it can eventually get megamorphized down to 20x worse performance.)Some implementations are a bit simpler if you go backwards: get the value first, then detect whether it was an actual value or if you finished instead. The performance is sometimes even better. But the difference is never so clear as to make it worth the different post-hoc way of doing things, and it only works if it is deployed optimistically (e.g. map won't check whether it should apply, it just does it, and then you have to check if the result is meaningful) which is difficult/surprising to reason about.It's also a bit harder if primivites are considered, as they effectively ban all `sentinel` values. So this post-checking is in most cases either some computation(wasNext?) or a field that takes memory.
Most importantly, hasNext/next usually does as well at implementing a tryAdvance-like method as does direct creation of the method. So I'm tentatively on board with a "Stealer"-type splitter (i.e. hasNext/next not tryAdvance semantics).But all the concurrency logic still bothers me.Also, I don't see how it could possibly be more efficient to use Stealer.split than Spliterator.trySplit. Maybe a couple extra object creations are insignificant (probably so).Here the difference indeed lies in scheduling. Stealers form a tree, that is used to schedule computations. Note that in hardest examples of irragular workload we haven't see a tree with more that 170 nodes(including leaves).
Unlike this Spliterator.trySplit effectively maintains only leaves of this tree, which makes efficient workstealing and scheduling a lot harder as you miss inner `summary` nodes. This summarty nodes help to join computations for new threads and help to gather the result in the end of operation.
There's also other slight problem that I see with scheduling in `trySplit`s model. One would want to have non-blocking scheduling. Than question comes what happens if multiple threads want to `split` the node.
Thanks for the pointers to the papers, Aleks. It helps me understand the reasoning behind the data structure (though it seems not incredibly hard to provide hooks to build the same tree using a self-mutating trySplit as long as you don't have heavy contention for the nodes to split).I still am not entirely certain, though, whether we would be making more or less work for everyone involved to try to have a common basis for Stealers and Spliterators.
There is, of course, the problem of duplicating code (up to 2x) by having two different ways to concurrently visit all the elements of a collection.
But the requirements are otherwise rather different. trySplit, for instance, is _required_ to have the current Spliterator jump ahead (leaving the skipped part for the new Spliterator), while split is presumably required to _not_ alter the state of a Stealer (its purpose is to sit there, stolen).
hasNext works well on indexed collections where it's a simple check, but less well on e.g. HashTables where you have to write separate advancing and executing logic instead of just seeking and consuming if you find something. Stealers need CAS logic; Spliterators need flags.
So I still think they're similar but uncomfortable bedfellows, especially since I can't really recapitulate Java 8 stream performance if I'm obliged to go through a Stealer-type interface (in the sequential case). I get sequential penalties ranging from 25% on Iterator (singly wrapped in Spliterator or doubly wrapped in Stealer then Spliterator) to 80% on Array, at least when going through tryAdvance. (Haven't tried forEachRemaining path yet.)
I just read your complete proposal. Some comments:
(1)
We previously had a lot of pain with configuring parameters such as the parallelism level in Parallel Collections.
Mutating the tasksupport field is nothing short of ugly.
(3)
Be careful about the providing all the methods from TraversableOnce (or extending it). Some of those methods are naturally not parallelizable, and having them on an interface which is supposed to be parallel violates the principle of least surprise.
Confused users yelled at us more than once about this.
(5)
A single object allocation for the wrapper is not a high price to pay, compared to invoking a parallel operation.
In ScalaBlitz, I avoided value classes in some places precisely for this reason.
On 2/27/2015 1:49 AM, Rex Kerr wrote:
Thanks for the pointers to the papers, Aleks. It helps me understand the reasoning behind the data structure (though it seems not incredibly hard to provide hooks to build the same tree using a self-mutating trySplit as long as you don't have heavy contention for the nodes to split).I still am not entirely certain, though, whether we would be making more or less work for everyone involved to try to have a common basis for Stealers and Spliterators.
There is, of course, the problem of duplicating code (up to 2x) by having two different ways to concurrently visit all the elements of a collection.
Maybe there could be a common basis that does not cause a lot of work.
Please see my comment further below about this - duplication might not be necessary.
But the requirements are otherwise rather different. trySplit, for instance, is _required_ to have the current Spliterator jump ahead (leaving the skipped part for the new Spliterator), while split is presumably required to _not_ alter the state of a Stealer (its purpose is to sit there, stolen).
Note that the contract is: `split` leaves the Parallel Collection Splitter (or a ScalaBlitz Stealer) in an undefined state - it is not presumed to alter the state of the original, or leave it intact. The implementation is free to do whatever it wants.
To my understanding, this is a good thing:
- if you need to convert (wrap) a Spliterator to a Parallel Collections Splitter, a Splitter can return `(original splitter, new splitter)` from the perspective of Parallel Collections
class WrapperSplitter[T](s: Spliterator[T]) extends Splitter[T] {
def split: (Splitter[T], Splitter[T]) = (s, s.trySplit())
}
- if you need to convert (wrap) a Splitter to a Spliterator, the Spliterator can update its internal Splitter reference:
class WrapperSpliterator[T](var s: Splitter[T]) extends Spliterator[T] {
def trySplit(): Spliterator[T] = {
val (a, b) = s.split
s = a
b
}
}
It seems to me that the two should be compatible.
hasNext works well on indexed collections where it's a simple check, but less well on e.g. HashTables where you have to write separate advancing and executing logic instead of just seeking and consuming if you find something. Stealers need CAS logic; Spliterators need flags.
I think that a Stealer can be something like this (simplified):
trait Stealer[T] extends Splitter[T] {
def state: State
def trySteal(): Boolean
def tryComplete(): Boolean
def tryAdvance(): Boolean
}
A collection must return a Splitter.
If it happens to also return a Stealer, all the better.
E.g. an array could easily implement a stealer.
HashTable Stealers are not super-complicated, but they don't have to.
Tree Stealers should not be implemented.
A data-parallel scheduler expects to get at least a Splitter.
If a data-parallel scheduler gets a Stealer, all the better - it can schedule irregular workloads and nested parallelism more efficiently.
A default scheduler can just ignore the Stealer, and use it as if it were a Splitter.
A more complex, fancy work-stealing tree scheduler might do something fancier.
So I still think they're similar but uncomfortable bedfellows, especially since I can't really recapitulate Java 8 stream performance if I'm obliged to go through a Stealer-type interface (in the sequential case). I get sequential penalties ranging from 25% on Iterator (singly wrapped in Spliterator or doubly wrapped in Stealer then Spliterator) to 80% on Array, at least when going through tryAdvance. (Haven't tried forEachRemaining path yet.)
I would recommend using macros for inlining, to eliminate iterator penalties altogether. At least for arrays, or array buffers.