Here is my benchmark code:
def bm(duration: Long)(f: => Unit)={
val end = System.currentTimeMillis + duration
var count = 0
while(System.currentTimeMillis < end) { f; count += 1 }
count
}
val array = new scala.util.Random().alphanumeric.take(1000).toArray
(1 to 20).map { _ => bm(1000) { array.slice(100,200) } }.sum / 20
Running this several times, I consistently get numbers in the ballpark of about 1.5 million slices per second. Between 1.4 and 1.6.
Now, I do this:
implicit class FastSlicing(val a: Array[Char]) extends AnyVal {
def fastSlice(from: Int, until: Int) = Arrays.copyOfRange(a, from, until)
}
(1 to 20).map { _ => bm(1000) { array.fastSlice(100,200) } }.sum / 20
And the result I get is between 16 and 18 million of slices per second. This is more than 10 times faster.
Now, I know all the usual reasoning about the trade-offs that scala makes to provide functional idioms and type safety sometimes at the cost of performance ...
But in this case, I think they all fail to answer a simple question: why is ArrayOps.slice
not implemented this way??? I realize, there would be multiple identical implementations needed, because of the way java deals with primitive arrays, but that's at most a minor annoyance, not really a deal-breaker kind of problem to justify a 10x performance hit.
The .slice
is only one example, most of other array ops seem to suffer from the same problem too. Why does it have to be this way?
Update now, here is something that I find even more shocking:
val seq = new scala.util.Random().alphanumeric.take(1000).toIndexedSeq
(1 to 20).map { _ => bm(1000) { seq.slice(100,200) } }.sum / 20
This does about 5-6 million slices per second for me. But this:
import scala.collections.JavaConversions._
(1 to 20).map { _ => bm(1000) { seq.subList(100,200) } }.sum / 20
does between 12 and 15 million! Granted, this is not order of magnitude difference, like in the arrays case, but (1) there is no special handling of primitives involved here, so this would be completely trivial to just implement using java standard tooling, and (2) the collection is immutable ... how hard can it be to return a reference to a range of indices???
slice() can be more than 2x slower than just manually copying elements from one array to another.
Overview. The ArraySlice type makes it fast and efficient for you to perform operations on sections of a larger array.
Array Slicing in Python With two parameters Again, specifying any two parameters among the start, stop and end, you can perform array slicing in Python by considering default value for the third parameter.
Array.prototype.slice() The slice() method returns a shallow copy of a portion of an array into a new array object selected from start to end ( end not included) where start and end represent the index of items in that array. The original array will not be modified.
It has been fixed in scala 2.12.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With