Skip to content

Commit c6dedf9

Browse files
committed
Merge branch 'dec10-checkin'
2 parents 6bbc8d2 + 23687eb commit c6dedf9

File tree

3 files changed

+139
-13
lines changed

3 files changed

+139
-13
lines changed

src/library/scala/collection/immutable/Range.scala

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,6 @@ extends collection.AbstractSeq[Int]
7171

7272
def isInclusive = false
7373

74-
@inline final override def foreach[@specialized(Unit) U](f: Int => U) {
75-
if (length > 0) {
76-
val last = this.last
77-
var i = start
78-
while (i != last) {
79-
f(i)
80-
i += step
81-
}
82-
f(i)
83-
}
84-
}
85-
8674
override def length: Int = numRangeElements
8775
override lazy val last: Int =
8876
if (length == 0) Nil.last
@@ -95,6 +83,83 @@ extends collection.AbstractSeq[Int]
9583
if (idx < 0 || idx >= length) throw new IndexOutOfBoundsException(idx.toString)
9684
locationAfterN(idx)
9785
}
86+
87+
/** @note Making foreach run as fast as a while loop is a challenge.
88+
* The key elements which I can observe making a difference are:
89+
*
90+
* - the inner loop should be as small as possible
91+
* - the inner loop should be monomorphic
92+
* - the inner loop should perform no boxing and no avoidable tests
93+
*
94+
* This is achieved by:
95+
*
96+
* - keeping initialization logic out of the inner loop
97+
* - dispatching to custom variations based on initial conditions
98+
* - tricking the compiler into always calling Function1#apply$mcVI$sp
99+
*
100+
* The last one is important and less than obvious. Even when foreach
101+
* was specialized on Unit, only Int => Unit arguments benefited from it.
102+
* Other function types would be accepted, but in the absence of full
103+
* specialization the integer argument was boxed on every call. For example:
104+
*
105+
class A {
106+
final def f(x: Int): Int = x + 1
107+
// Calls Range.foreach, which calls Function1.apply
108+
def g1 = 1 until 100 foreach { x => f(x) }
109+
// Calls Range.foreach$mVc$sp, which calls Function1.apply$mcVI$sp
110+
def g2 = 1 until 100 foreach { x => f(x) ; () }
111+
}
112+
*
113+
* However! Since the result of the closure is always discarded, we
114+
* simply cast it to Int => Unit, thereby executing the fast version.
115+
* The seemingly looming ClassCastException can never arrive.
116+
*/
117+
@inline final override def foreach[U](f: Int => U) {
118+
if (step < 0) {
119+
if (isInclusive) foreachDownIn(f.asInstanceOf[Int => Unit])
120+
else foreachDownEx(f.asInstanceOf[Int => Unit])
121+
}
122+
else {
123+
if (isInclusive) foreachUpIn(f.asInstanceOf[Int => Unit])
124+
else foreachUpEx(f.asInstanceOf[Int => Unit])
125+
}
126+
}
127+
128+
/** !!! These methods must be public or they will not be inlined.
129+
* But they are certainly not intended to be part of the API.
130+
* This collision between inlining requirements and access semantics
131+
* is highly unfortunate and must be resolved.
132+
*
133+
* Proposed band-aid: an @internal annotation.
134+
*/
135+
@inline final def foreachDownIn(f: Int => Unit) {
136+
var i = start
137+
while (i >= end) {
138+
f(i)
139+
i += step
140+
}
141+
}
142+
@inline final def foreachUpIn(f: Int => Unit) {
143+
var i = start
144+
while (i <= end) {
145+
f(i)
146+
i += step
147+
}
148+
}
149+
@inline final def foreachDownEx(f: Int => Unit) {
150+
var i = start
151+
while (i > end) {
152+
f(i)
153+
i += step
154+
}
155+
}
156+
@inline final def foreachUpEx(f: Int => Unit) {
157+
var i = start
158+
while (i < end) {
159+
f(i)
160+
i += step
161+
}
162+
}
98163

99164
/** Creates a new range containing the first `n` elements of this range.
100165
*

src/library/scala/collection/parallel/immutable/ParRange.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ self =>
8888
/* accessors */
8989

9090
override def foreach[U](f: Int => U): Unit = {
91-
rangeleft.foreach(f)
91+
rangeleft.foreach(f.asInstanceOf[Int => Unit])
9292
ind = len
9393
}
9494

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package scala.collection.immutable
2+
package benchmarks
3+
4+
object RangeTest {
5+
// not inlined any more, needs investigation
6+
//
7+
// class XXS {
8+
// private val array = Array.range(0, 100)
9+
// def tst = { var sum = 0; for (i <- 0 until array.length) sum += array(i); sum }
10+
// }
11+
12+
var x: Int = 0
13+
14+
def foreachSum(max: Int): Int = {
15+
var sum = 0
16+
1 to max foreach (sum += _)
17+
sum
18+
}
19+
def whileSum(max: Int) = {
20+
var sum = 0
21+
var num = 1
22+
while (num <= max) {
23+
sum += num
24+
num += 1
25+
}
26+
sum
27+
}
28+
29+
def show(max: Int, foreachNanos: Long, whileNanos: Long) {
30+
val winner = if (foreachNanos < whileNanos) "foreachSum" else "whileSum"
31+
val ratio = if (foreachNanos < whileNanos) foreachNanos.toDouble / whileNanos else whileNanos.toDouble / foreachNanos
32+
println("1 to %d:, %12s wins, %.3f: foreach %.3f while %.3f".format(
33+
max, winner, ratio,
34+
foreachNanos.toDouble / 1000000L,
35+
whileNanos.toDouble / 1000000L)
36+
)
37+
}
38+
39+
def run(max: Int) = {
40+
val foreachFirst = util.Random.nextBoolean
41+
val t1 = System.nanoTime
42+
x = if (foreachFirst) foreachSum(max) else whileSum(max)
43+
val t2 = System.nanoTime
44+
x = if (foreachFirst) whileSum(max) else foreachSum(max)
45+
val t3 = System.nanoTime
46+
47+
val foreachNanos = if (foreachFirst) t2 - t1 else t3 - t2
48+
val whileNanos = if (foreachFirst) t3 - t2 else t2 - t1
49+
show(max, foreachNanos, whileNanos)
50+
}
51+
52+
def main(args: Array[String]): Unit = {
53+
var max = if (args.isEmpty) 100 else args(0).toInt
54+
while (max > 0) {
55+
run(max)
56+
run(max)
57+
run(max)
58+
max += (max / 7)
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)