Skip to content

Commit d7789d1

Browse files
committed
Experiment with specialized lists
The idea is to have a compiler-specific list data type that optimizes for small lists. Where the overhead of normal lists is one Cons node per element, the overhead of `Lst` is 0 for lists of length <= 1, and 1 array node for longer lists. Furthermore, we'll try to inline heavily, which is easier for specialized data structures. The main downsides are inefficient `tail` and `::`, in particular for longer lists. Compiler code will need to be adapted to avoid these where it is performance critical.
1 parent 97df959 commit d7789d1

File tree

2 files changed

+440
-0
lines changed

2 files changed

+440
-0
lines changed

tests/run/lst/Lst.scala

Lines changed: 397 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
package dotty.tools.dotc.util
2+
3+
import collection.mutable.ListBuffer
4+
import reflect.ClassTag
5+
6+
// List Lst
7+
// 0 0 0
8+
// 1 1 0
9+
// 2 2 1
10+
// 3 3 1
11+
// 4 4 1
12+
class Lst[+T <: AnyRef](val elems: AnyRef) extends AnyVal {
13+
import Lst._
14+
15+
// should be called only if `elems` is an array
16+
private[this] def elemTag: ClassTag[T] = ClassTag[T](elems.getClass.getComponentType)
17+
18+
19+
def length: Int = elems match {
20+
case null => 0
21+
case elems: Array[T] @unchecked => elems.length
22+
case elem: T @ unchecked => 1
23+
}
24+
25+
def isEmpty = elems == null
26+
def nonEmpty = elems != null
27+
28+
inline def foreach(inline op: T => Unit): Unit = {
29+
def sharedOp(x: T) = op(x)
30+
elems match {
31+
case null =>
32+
case elems: Array[T] @unchecked =>
33+
var i = 0
34+
while (i < elems.length) { sharedOp(elems(i)); i += 1 }
35+
case elem: T @ unchecked => sharedOp(elem)
36+
}
37+
}
38+
39+
/** Like `foreach`, but completely inlines `op`, atthe price of generating the code twice.
40+
* Should be used only of `op` is small
41+
*/
42+
inline def foreachInlined(inline op: T => Unit): Unit = elems match {
43+
case null =>
44+
case elems: Array[T] @unchecked =>
45+
var i = 0
46+
while (i < elems.length) { op(elems(i)); i += 1 }
47+
case elem: T @ unchecked => op(elem)
48+
}
49+
50+
def iterator(): Iterator[T] = elems match {
51+
case null => Iterator.empty
52+
case elems: Array[T] @unchecked => elems.iterator
53+
case elem: T @unchecked => Iterator.single(elem)
54+
}
55+
56+
def copyToArray[U >: T <: AnyRef](target: Array[U], from: Int) = elems match {
57+
case null =>
58+
case elems: Array[T] @unchecked => System.arraycopy(elems, 0, target, from, elems.length)
59+
case elem: T @ unchecked => target(from) = elem
60+
}
61+
62+
def map[U <: AnyRef : ClassTag](f: T => U): Lst[U] = elems match {
63+
case null => Empty
64+
case elems: Array[T] @unchecked =>
65+
val newElems = newArray[U](elems.length)
66+
var i = 0
67+
while (i < elems.length) { newElems(i) = f(elems(i)); i += 1 }
68+
new Lst(newElems)
69+
case elem: T @ unchecked => new Lst(f(elem))
70+
}
71+
72+
def mapconserve[U <: AnyRef : ClassTag](f: T => U): Lst[U] = elems match {
73+
case null => Empty
74+
case elems: Array[T] @unchecked =>
75+
var newElems: Array[U] = null
76+
var i = 0
77+
while (i < elems.length) {
78+
val x = elems(i)
79+
val y = f(x)
80+
if (newElems != null) newElems(i) = y
81+
else if (x `ne` y) {
82+
newElems = newArray[U](elems.length)
83+
System.arraycopy(elems, 0, newElems, 0, i)
84+
newElems(i) = y
85+
}
86+
i += 1
87+
}
88+
new Lst(newElems)
89+
case elem: T @ unchecked => new Lst(f(elem))
90+
}
91+
92+
def flatMap[U <: AnyRef : ClassTag](f: T => Lst[U]): Lst[U] = elems match {
93+
case null => Empty
94+
case elems: Array[T] @unchecked =>
95+
val newElemss: Array[AnyRef] = newArray[AnyRef](elems.length)
96+
var i = 0
97+
var len = 0
98+
while (i < elems.length) {
99+
val ys = f(elems(i))
100+
len += ys.length
101+
newElemss(i) = ys.elems
102+
i += 1
103+
}
104+
if (len == 0) Empty
105+
else if (len == 1) {
106+
i = 0
107+
while (newElemss(i) == null) i += 1
108+
new Lst(newElemss(i))
109+
}
110+
else {
111+
val newElems = newArray[U](len)
112+
i = 0
113+
var j = 0
114+
while (i < newElemss.length) {
115+
val ys = new Lst(newElemss(i))
116+
ys.copyToArray(newElems, j)
117+
j += ys.length
118+
i += 1
119+
}
120+
new Lst(newElems)
121+
}
122+
case elem: T @ unchecked => new Lst(f(elem).elems)
123+
}
124+
125+
def filter(p: T => Boolean): Lst[T] = elems match {
126+
case null => Empty
127+
case elems: Array[T] @unchecked =>
128+
val scratch = newArray[T](elems.length)(elemTag)
129+
var i = 0
130+
var len = 0
131+
while (i < elems.length) {
132+
val x = elems(i)
133+
if (p(x)) { scratch(len) = x; len += 1 }
134+
i += 1
135+
}
136+
if (len == elems.length) this
137+
else fromArray(scratch, 0, len)(elemTag)
138+
case elem: T @unchecked =>
139+
if (p(elem)) this else Empty
140+
}
141+
def filterNot(p: T => Boolean): Lst[T] = filter(!p(_))
142+
143+
def exists(p: T => Boolean): Boolean = elems match {
144+
case null => false
145+
case elems: Array[T] @unchecked =>
146+
var i = 0
147+
while (i < elems.length && p(elems(i))) i += 1
148+
i < elems.length
149+
case elem: T @unchecked =>
150+
p(elem)
151+
}
152+
153+
def contains[U >: T](x: U): Boolean = elems match {
154+
case null => false
155+
case elems: Array[T] @unchecked =>
156+
var i = 0
157+
while (i < elems.length && elems(i) != x) i += 1
158+
i < elems.length
159+
case elem: T @unchecked =>
160+
elem == x
161+
}
162+
163+
def forall(p: T => Boolean): Boolean = elems match {
164+
case null => true
165+
case elems: Array[T] @unchecked =>
166+
var i = 0
167+
while (i < elems.length && p(elems(i))) i += 1
168+
i == elems.length
169+
case elem: T @unchecked =>
170+
p(elem)
171+
}
172+
173+
def /: [U](z: U)(op: (U, T) => U) = elems match {
174+
case null => z
175+
case elems: Array[T] @unchecked =>
176+
var i = 0
177+
var acc = z
178+
while (i < elems.length) { acc = op(acc, elems(i)); i += 1 }
179+
acc
180+
case elem: T @unchecked =>
181+
op(z, elem)
182+
}
183+
184+
def reverse: Lst[T] = elems match {
185+
case elems: Array[T] @unchecked =>
186+
val newElems = newArray[T](elems.length)(elemTag)
187+
var i = 0
188+
while (i < elems.length) {
189+
newElems(elems.length - i) = elems(i)
190+
i += 1
191+
}
192+
new Lst(newElems)
193+
case _ => this
194+
}
195+
196+
def apply(n: Int) = elems match {
197+
case null => throw new IndexOutOfBoundsException(n.toString)
198+
case elems: Array[T] @unchecked => elems(n)
199+
case elem: T @unchecked =>
200+
if (n == 0) elem else throw new IndexOutOfBoundsException(n.toString)
201+
}
202+
203+
def head = apply(0)
204+
def last = apply(length - 1)
205+
206+
def headOr[U >: T](alt: => U): U = if (length == 0) alt else head
207+
208+
def drop(n: Int): Lst[T] =
209+
if (n <= 0) this
210+
else elems match {
211+
case null => this
212+
case elems: Array[T] @unchecked => fromArray(elems, n, elems.length)(elemTag)
213+
case elem: T @ unchecked => Empty
214+
}
215+
216+
def tail = drop(1)
217+
218+
def take(n: Int): Lst[T] =
219+
if (n <= 0) Empty
220+
else elems match {
221+
case null => this
222+
case elems: Array[T] @unchecked => fromArray(elems, 0, n min elems.length)(elemTag)
223+
case elem: T @ unchecked => this
224+
}
225+
226+
def ++ [U >: T <: AnyRef : ClassTag](that: Lst[U]): Lst[U] =
227+
if (elems == null) that
228+
else if (that.elems == null) this
229+
else {
230+
val len1 = this.length
231+
val len2 = that.length
232+
val newElems = newArray[U](len1 + len2)
233+
this.copyToArray(newElems, 0)
234+
that.copyToArray(newElems, len1)
235+
new Lst(newElems)
236+
}
237+
238+
def zipWith[U <: AnyRef, V <: AnyRef : ClassTag](that: Lst[U])(op: (T, U) => V): Lst[V] =
239+
this.elems match {
240+
case null => Empty
241+
case elems1: Array[T] @unchecked =>
242+
that.elems match {
243+
case null => Empty
244+
case elems2: Array[U] @unchecked =>
245+
val len = elems1.length min elems2.length
246+
if (len == 0) Empty
247+
else if (len == 1) new Lst(op(elems1(0), elems2(0)))
248+
else {
249+
var newElems: Array[V] = null
250+
var i = 0
251+
while (i < len) {
252+
val x = elems1(i)
253+
val y = op(x, elems2(i))
254+
if (newElems != null) newElems(i) = y
255+
else if (x `ne` y) {
256+
newElems = newArray[V](len)
257+
System.arraycopy(elems, 0, newElems, 0, i)
258+
newElems(i) = y
259+
}
260+
i += 1
261+
}
262+
new Lst(newElems)
263+
}
264+
case elem2: U @unchecked =>
265+
new Lst(op(elems1(0), elem2))
266+
}
267+
case elem1: T @unchecked =>
268+
that.elems match {
269+
case null => Empty
270+
case elems2: Array[U] @unchecked => new Lst(op(elem1, elems2(0)))
271+
case elem2: U @unchecked => new Lst(op(elem1, elem2))
272+
}
273+
}
274+
275+
def zip[U <: AnyRef](that: Lst[U]): Lst[(T, U)] = zipWith(that)((_, _))
276+
277+
def zipWithIndex: Lst[(T, Int)] = elems match {
278+
case null => Empty
279+
case elems: Array[T] @unchecked =>
280+
val newElems = newArray[(T, Int)](elems.length)
281+
var i = 0
282+
while (i < elems.length) { newElems(i) = (elems(i), i); i += 1 }
283+
new Lst(newElems)
284+
case elem: T @unchecked =>
285+
new Lst((elem, 0))
286+
}
287+
288+
def corresponds[U <: AnyRef](that: Lst[U])(p: (T, U) => Boolean): Boolean =
289+
(this.elems `eq` that.elems) || {
290+
this.elems match {
291+
case null => that.elems == null
292+
case elems1: Array[T] @unchecked =>
293+
that.elems match {
294+
case null => false
295+
case elems2: Array[U] @unchecked =>
296+
val len = elems1.length
297+
len == elems2.length && {
298+
var i = 0
299+
while (i < len && p(elems1(i), elems2(i))) i += 1
300+
i == len
301+
}
302+
}
303+
}
304+
}
305+
306+
def eqElements[U <: AnyRef](that: Lst[U]): Boolean = corresponds(that)(eqFn)
307+
def equalElements[U <: AnyRef](that: Lst[U]): Boolean = corresponds(that)(equalsFn)
308+
}
309+
310+
object Lst {
311+
def newArray[T <: AnyRef](len: Int)(implicit tag: ClassTag[T]): Array[T] =
312+
java.lang.reflect.Array.newInstance(tag.runtimeClass, len).asInstanceOf[Array[T]]
313+
314+
val Empty = new Lst(null)
315+
316+
def apply[T <: AnyRef](): Lst[T] = Empty
317+
318+
def apply[T <: AnyRef](x0: T): Lst[T] = new Lst(x0)
319+
320+
def apply[T <: AnyRef : ClassTag](x0: T, x1: T): Lst[T] = {
321+
val elems = newArray[T](2)
322+
elems(0) = x0
323+
elems(1) = x1
324+
new Lst(elems)
325+
}
326+
327+
def apply[T <: AnyRef : ClassTag](x0: T, x1: T, x2: T): Lst[T] = {
328+
val elems = newArray[T](3)
329+
elems(0) = x0
330+
elems(1) = x1
331+
elems(2) = x2
332+
new Lst(elems)
333+
}
334+
335+
def apply[T <: AnyRef : ClassTag](x0: T, x1: T, x2: T, x3: T): Lst[T] = {
336+
val elems = newArray[T](4)
337+
elems(0) = x0
338+
elems(1) = x1
339+
elems(2) = x2
340+
elems(3) = x3
341+
new Lst(elems)
342+
}
343+
344+
def apply[T <: AnyRef : ClassTag](x0: T, x1: T, x2: T, x3: T, x4: T, xs: T*): Lst[T] = {
345+
val elems = newArray[T](5 + xs.length)
346+
elems(0) = x0
347+
elems(1) = x1
348+
elems(2) = x2
349+
elems(3) = x3
350+
elems(4) = x4
351+
xs.copyToArray(elems, 5)
352+
new Lst(elems)
353+
}
354+
355+
val eqFn = (x: AnyRef, y: AnyRef) => x `eq` y
356+
val equalsFn = (x: AnyRef, y: AnyRef) => x `equals` y
357+
358+
class Buffer[T <: AnyRef : ClassTag] {
359+
var len = 0
360+
private var elem: T = _
361+
private var elems: Array[T] = _
362+
363+
def += (x: T) = {
364+
if (len == 0) elem = x
365+
else {
366+
if (len == 1) {
367+
elems = newArray[T](16)
368+
elems(0) = elem
369+
}
370+
else if (len == elems.length) {
371+
val newElems = newArray[T](elems.length * 2)
372+
System.arraycopy(elems, 0, newElems, 0, elems.length)
373+
elems = newElems
374+
}
375+
elems(len) = x
376+
}
377+
len += 1
378+
}
379+
380+
def toLst: Lst[T] =
381+
if (len == 0) Empty
382+
else if (len == 1) new Lst(elem)
383+
else fromArray(elems, 0, len)
384+
}
385+
386+
def fromArray[T <: AnyRef : ClassTag](elems: Array[T], start: Int, end: Int) = {
387+
val len = end - start
388+
if (len <= 0) Empty
389+
else if (len == 1) new Lst(elems(start))
390+
else if (start == 0 && end == elems.length) new Lst(elems)
391+
else {
392+
val newElems = newArray[T](len)
393+
System.arraycopy(elems, start, newElems, 0, len)
394+
new Lst(newElems)
395+
}
396+
}
397+
}

0 commit comments

Comments
 (0)