-
Notifications
You must be signed in to change notification settings - Fork 72
Optimize slice operation in ImmutableArray #354
Conversation
@@ -10,7 +10,7 @@ import scala.Predef.intWrapper | |||
|
|||
@BenchmarkMode(scala.Array(Mode.AverageTime)) | |||
@OutputTimeUnit(TimeUnit.NANOSECONDS) | |||
@Fork(1) | |||
@Fork(2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you changing this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ichoran I just though fork 2 jvm would make result more stable. (with less error). Reverted.
@@ -197,6 +199,10 @@ object ImmutableArray extends StrictOptimizedSeqFactory[ImmutableArray] { | |||
case that: ofByte => Arrays.equals(unsafeArray, that.unsafeArray) | |||
case _ => super.equals(that) | |||
} | |||
override def slice(from: Int, until: Int): ImmutableArray[Byte] = { | |||
val lo = scala.math.max(from, 0) | |||
ImmutableArray.unsafeWrapArray(Arrays.copyOfRange(unsafeArray, lo, until)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't correct. copyOfRange
pads with zeros rather than truncating when you run off the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ichoran fixed. Thank you.
// new ofUnit(util.Arrays.copyOfRange[Unit](array, from, until)) - Unit is special and doesnt compile | ||
// cant use util.Arrays.copyOfRange[Unit](repr, from, until) - Unit is special and doesnt compile | ||
val lo = scala.math.max(from, 0) | ||
val res = new Array[Unit](until-lo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also incorrect (it copies the copyOfRange
behavior instead of the normal slice
behavior). Note that I didn't mark all the places the copyOfRange
call needs to be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @Ichoran
I fixed this and all copyOfRange invocation. But for this ofUnit case, I am not sure whether we need have this case. Because Unit will be auto-boxed to BoxedUnit at runtime, and per my testing, the runtime implementation of slice is rely on definition of ofRef.
Do you know under which situation, the ofUnit representation will be generated and used?
Thanks for looking at this! The behavior needs to be tweaked to work the way |
c241908
to
29e0506
Compare
override def slice(from: Int, until: Int): ImmutableArray[T] = { | ||
val lo = scala.math.max(from, 0) | ||
val hi = scala.math.min(until, length) | ||
new ofRef(Arrays.copyOfRange[T](unsafeArray, lo, hi)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better! But the current behavior (in 2.12) is to give an empty array, not throw an exception, when the range is of negative size. So you either need to check lo < hi
or take from lo
to math.max(lo, hi)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, fixed.
// cant use util.Arrays.copyOfRange[Unit](repr, from, until) - Unit is special and doesnt compile | ||
val lo = scala.math.max(from, 0) | ||
val hi = scala.math.min(until, length) | ||
val slicedLenght = hi - lo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: spelling should be slicedLength
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good spot. Fixed
val lo = scala.math.max(from, 0) | ||
val hi = scala.math.min(until, length) | ||
val slicedLenght = hi - lo | ||
val res = new Array[Unit](slicedLenght) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to make sure the length is non-negative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, fixed
c2d6fd1
to
b994be2
Compare
@@ -186,6 +184,14 @@ object ImmutableArray extends StrictOptimizedSeqFactory[ImmutableArray] { | |||
case that: ofRef[_] => Arrays.equals(unsafeArray.asInstanceOf[Array[AnyRef]], that.unsafeArray.asInstanceOf[Array[AnyRef]]) | |||
case _ => super.equals(that) | |||
} | |||
override def slice(from: Int, until: Int): ImmutableArray[T] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a performance advantage in duplicating this functionality in all subclasses rather than calling new Array
with an available implicit ClassTag
and then using System.arraycopy
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should have thought about this for a few more seconds. There is a definite advantage but it's currently not realized. All these slice
methods should declare the correct subtype as the return type, for example in this case ofRef[T]
. This ensures that the slices can still be accessed without boxing.
010055f
to
f86e69d
Compare
rerun benchmark against 2.12.4 with following sbt setting:
This PR improvement
collection strawman baseline (0a00062)
scala 2.12.x official baseline (scala/scala@9fc1356)
|
My previous comment still stands: Since With the current non-specialized return types you could use a single implementation of |
This PR Latest benchmark (return specialized ImmutableArray to avoid auto-boxing), slightly improved:
|
Could you run a benchmark against the update in #492? Does this version here still offer an advantage? As far as I can tell the difference comes down to 1 vs 2 or 3 operations on polymorphic arrays and any remaining advantage would be small. |
1bf1090
to
72f16ac
Compare
Hi @szeiger You are right, the remaining advantage is small to be noticed:
Thank you for your help on reviewing this |
baseline performance (0a00062):
This PR improvement: