8000 Array slice optimisations by mkeskells · Pull Request #5652 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

Array slice optimisations #5652

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

Closed
wants to merge 2 commits into from
Closed

Conversation

mkeskells
Copy link
Contributor

No description provided.

@scala-jenkins scala-jenkins added this to the 2.11.9 milestone Jan 18, 2017
@Ichoran
Copy link
Contributor
Ichoran commented Jan 18, 2017

This looks like a good approach in principle but there seem to be issues with return types and presence or absence of a ClassTag that makes me think it could not work as written. Have you compiled this locally?

@mkeskells
Copy link
Contributor Author
mkeskells commented Jan 19, 2017 via email

protected override def sliceImpl(from: Int, until: Int) = {
// cant use util.Arrays.copyOfRange[Unit](repr, from, until) - Unit is special and doesnt compile
// but Unit and null are analogous so values are not important to copy across
Array[Unit](until-from)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you missed the new. As written, this discards the value until-from to produce a single unit, then creates an array of length one. There should be tests of all the slices for all the different implementations to catch stuff like this!

Copy link
Contributor

Choose a reason for hiding this comment

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

If you wanted to actually copy the array (to preserve whether the contents are null or instances of scala.runtime.BoxedUnit--perversely, either one is okay--you could

java.util.Arrays.copyOfRange(
  repr.asInstanceOf[Array[scala.runtime.BoxedUnit]],
  from,
  until
).asInstanceOf[Array[Unit]]

(maybe on a single line--I'm just spreading it out here to make it easier to see).

// cant use
// new ofUnit(util.Arrays.copyOfRange[Unit](array, from, until)) - Unit is special and doesnt compile
// but Unit and null are analogous so values are not important to copy across
new ofUnit(Array[Unit](until-from))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same mistake here as with the ArrayOps version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

doh

@Ichoran
Copy link
Contributor
Ichoran commented Jan 19, 2017

This looks pretty good! Benchmarks for this one are less important, as it is super-obvious that this will be a big win in many cases. However, unit tests are very important to make sure that now, and in the future, no indices have been messed up.

@mkeskells
Copy link
Contributor Author
mkeskells commented Jan 19, 2017 via email

@Ichoran
Copy link
Contributor
Ichoran commented Jan 19, 2017

You should verify that a proper unit test exists, or write a new one. You can't automatically assume that when you split one implementation into ten that the unit tests will properly cover all ten cases. When everything shared an implementation, a single unit test with a single data type would have been enough.

I'm not sure understand what you were asking about "expectation for exception behavior". slice is generally considered a "safe" operation; it won't throw an exception but rather will clip things to sensible ranges. So for instance a.slice(-8, -3) will return an empty array rather than throwing an exception. Is this what you meant?

@mkeskells
Copy link
Contributor Author
mkeskells commented Jan 20, 2017 via email

@mkeskells
Copy link
Contributor Author
mkeskells commented Jan 20, 2017 via email

@Ichoran
Copy link
Contributor
Ichoran commented Jan 20, 2017

Looks very extensive! That's good. I can't eyeball whether you've successfully gotten around generic code making everything look like Object, but it looks like you probably have. (It would be more idiomatic to use ClassTag, but I think it's okay anyway.)

I don't have time right now to think about binary compatibility (which is why the test failed). That could be a problem. Maybe someone else can pick this up? @szeiger ?

@szeiger
Copy link
Contributor
szeiger commented Jan 20, 2017
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofChar is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofShort is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofUnit is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofInt is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofBoolean is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofDouble is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofRef is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofByte is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofLong is declared final in new version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.ArrayOps#ofFloat is declared final in new version

These look harmless and could simply be whitelisted. They are members of final classes, so the methods are effectively final anyway. MiMa shouldn't report them in the first place.

 * method sliceImpl(Int,Int)java.lang.Object in trait
   scala.collection.mutable.ArrayOps does not have a correspondent in old
   version
 * method emptyImpl()java.lang.Object in trait
   scala.collection.mutable.ArrayOps does not have a correspondent in old
   version
 * method slice(Int,Int)java.lang.Object in class
   scala.collection.mutable.WrappedArray is declared final in new version
 * abstract method sliceImpl(Int,Int)scala.collection.mutable.WrappedArray
   in class scala.collection.mutable.WrappedArray does not have a correspondent
   in old version
 * abstract method emptyImpl()scala.collection.mutable.WrappedArray in class
   scala.collection.mutable.WrappedArray does not have a correspondent in old
   version

These changes break binary compatibility in a fundamental way. You can never add a new abstract method or make a non-final method final. For 2.11 & 2.12 you could do these changes in a new private implementation class inside the ArrayOps and WrappedArrray companion objects (where the concrete subclasses that use the new implementation are also defined). It can be refactored back to the current version in 2.13.

@mkeskells
Copy link
Contributor Author

I have updated the patch to, hopefully, comply with binary compatibility needs

I have taken the liberty of marking WrappedArray as @deprecatedInheritance like ArrayOps. Happy to remove this if they should not be in step with ArrayOps, but it seemed sane to me

What is the appropriate way to provide a markup for the changes at 2.13 as proposed above? For the moment I have just added comments //at 2.13 ....

I have also moved the regression test to scala.collections, and added some support ofor other classes that follow a similar pattern
It seems sensible to be that this regression test should be the subject of a separate PR, otherwise it proves nothing, just that the code with my chnages matches some new test
If this is a separate PR then it shows that this is a valid representation of the current state and acts as a regression test to my changes. Would approciate your thoughts of if this is needed of it is easier for you to validate the current functionallity in some other way

@retronym
Copy link
Member
retronym commented Jan 24, 2017

I have taken the liberty of marking WrappedArray as @deprecatedInheritance like ArrayOps. Happy to remove this if they should not be in step with ArrayOps, but it seemed sane to me

Typically we don't introduce deprrcations in minor releases, so I'd leave this until 2.13.x.

What is the appropriate way to provide a markup for the changes at 2.13 as proposed above? For the moment I have just added comments //at 2.13 ....

You can just keep the extra commit that will eventually target 2.13.x on a separate branch in your repository and link to it from here.

It seems sensible to be that this regression test should be the subject of a separate PR, otherwise it proves nothing, just that the code with my chnages matches some new test

You can add the test for existing functionality in the first commit of this PR, then change the implementation in the second. Our pull request validation checks that each commit passes the test suite.

@mkeskells
Copy link
Contributor Author
mkeskells commented Jan 25, 2017 via email

@szeiger
Copy link
Contributor
szeiger commented Jan 25, 2017

The changes for binary compatibility are not enough. Adding a protected concrete trait method is not even backward compatible in 2.11 (but it is in 2.12). It is not forward compatible in either version. You really need to move all of that stuff down into a private implementation class and keep the trait as it is.

@mkeskells
Copy link
Contributor Author

restructured the commits as first one with the test code and second with the changes as @retronym suggested
updated the trait based chnages to includes a seperate trait for the extension as @szeiger suggested. Cant use a class as I see it because of the issues of extending from AnyVal

@mkeskells
Copy link
Contributor Author

/rebuild

@adriaanm
Copy link
Contributor
adriaanm commented Jan 25, 2017

The test failure is likely due to your branch being based on an old version of 2.11.x. To get up to date, could you do a git pull --rebase https://github.com/scala/scala.git 2.11.x in the arraySlice branch and git push -f https://github.com/rorygraves/scala.git arraySlice? Thanks!

@mkeskells
Copy link
Contributor Author

applied the git foo suggested by @adriaanm

@szeiger
Copy link
Contributor
szeiger commented Jan 27, 2017

Looks good. Please add the remaining problem filters from the log to bincompat-backward.whitelist.conf to make CI happy.

@mkeskells
Copy link
Contributor Author

Is there anything that I need to do here?
@szeiger I presume that the filters as part of the build system, not part of this commit

@adriaanm
Copy link
Contributor
adriaanm commented Jan 28, 2017

The filters are part of the code tree: https://github.com/scala/scala/blob/2.11.x/bincompat-backward.whitelist.conf

the items to add to problems are:

            {
                matchName="scala.collection.mutable.ArrayOps#ofChar.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofShort.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofUnit.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofInt.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofBoolean.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofDouble.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofRef.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofByte.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofLong.slice"
                problemName=FinalMethodProblem
            },
            {
                matchName="scala.collection.mutable.ArrayOps#ofFloat.slice"
                problemName=FinalMethodProblem
            }

@adriaanm
Copy link
Contributor

See #5664 for a consolidated version with test & mima fixes

@adriaanm
Copy link
Contributor
adriaanm commented Feb 2, 2017

Commits moved to #5664. Thanks!

@adriaanm adriaanm closed this Feb 2, 2017
@SethTisue SethTisue removed this from the 2.11.9 milestone Feb 17, 2017
@rorygraves rorygraves deleted the arraySlice branch March 5, 2017 18:24
@ackratos ackratos restored the arraySlice branch December 10, 2017 10:28
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.

7 participants
0