Skip to content

Commit

Permalink
Align StackBool internals with Long size to (zio#860)
Browse files Browse the repository at this point in the history
* Add generators to tests

* Fix push/pop methods

* Fix getOrElse method to work with multiple entries

Co-Authored-By: Regis Kuckaertz <regis.kuckaertz@guardian.co.uk>
  • Loading branch information
vilu and Regis Kuckaertz committed May 25, 2019
1 parent 71a4c49 commit b5a411f
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 62 deletions.
32 changes: 17 additions & 15 deletions core/shared/src/main/scala/scalaz/zio/internal/StackBool.scala
Expand Up @@ -18,7 +18,7 @@ package scalaz.zio.internal

/**
* A very fast, hand-optimized stack designed just for booleans.
* In the common case (size < 256), achieves zero allocations.
* In the common case (size < 64), achieves zero allocations.
*/
private[zio] final class StackBool private () {
import StackBool.Entry
Expand All @@ -27,30 +27,32 @@ private[zio] final class StackBool private () {
private[this] var _size = 0L

final def getOrElse(index: Int, b: Boolean): Boolean = {
var j = index.toLong
val i0 = _size & 63L
val i = ((64L - i0) + index) & 63L
var ie = ((64L - i0) + index) >> 6
var cur = head
while (j >= 256L && (cur.next ne null)) {
j -= 256L

while (ie > 0) {
ie -= 1
cur = cur.next
}
assert(j < 256L && j >= 0)
assert(i < 64L && i >= 0)
if (cur eq null) b
else {
val mask = 1L << j

val mask = 1L << (63L - i)
(cur.bits & mask) != 0L
}
}

final def size = _size
final def size: Long = _size

final def push(flag: Boolean): Unit = {
val index = _size & 0XFFL
val index = _size & 0X3FL

if (flag) head.bits = head.bits | (1L << index)
else head.bits = head.bits & (~(1L << index))

if (index == 255L) head = new Entry(head)
if (index == 63L) head = new Entry(head)

_size += 1L
}
Expand All @@ -59,9 +61,9 @@ private[zio] final class StackBool private () {
if (_size == 0L) b
else {
_size -= 1L
val index = _size & 0XFFL
val index = _size & 0X3FL

if (index == 0L && head.next != null) head = head.next
if (index == 63L && head.next != null) head = head.next

((1L << index) & head.bits) != 0L
}
Expand All @@ -70,17 +72,17 @@ private[zio] final class StackBool private () {
if (_size == 0L) b
else {
val size = _size - 1L
val index = size & 0XFFL
val index = size & 0X3FL
val entry =
if (index == 0L && head.next != null) head.next else head
if (index == 63L && head.next != null) head.next else head

((1L << index) & entry.bits) != 0L
}

final def popDrop[A](a: A): A = { popOrElse(false); a }

final def toList: List[Boolean] =
(0 until _size.toInt).map(getOrElse(_, false)).toList.reverse
(0 until _size.toInt).map(getOrElse(_, false)).toList

final override def toString: String =
"StackBool(" + toList.mkString(", ") + ")"
Expand Down
107 changes: 60 additions & 47 deletions core/shared/src/test/scala/scalaz/zio/internal/StackBoolSpec.scala
@@ -1,8 +1,9 @@
package scalaz.zio.internal

import org.specs2.Specification
import org.scalacheck.{ Arbitrary, Gen }
import org.specs2.{ ScalaCheck, Specification }

class StackBoolSpec extends Specification {
class StackBoolSpec extends Specification with ScalaCheck {
def is =
"StackBoolSpec".title ^ s2"""
Size tracking $e0
Expand All @@ -13,23 +14,31 @@ class StackBoolSpec extends Specification {
Peek/pop identity $e5
"""

def e0 = {
val list = List.fill(100)(true)

StackBool(list: _*).size must_== list.length
}

def e1 = {
val list = (1 to 200).map(_ % 2 == 0).toList

StackBool(list: _*).toList must_=== list
}

def e2 = {
val list = (1 to 400).map(_ % 2 == 0).toList

StackBool(list: _*).toList must_=== list
}
implicit val booleanGen: Gen[Boolean] = Arbitrary.arbitrary[Boolean]

def e0 =
prop { list: List[Boolean] =>
StackBool(list: _*).size must_== list.length
}.setGen(for {
size <- Gen.choose(0, 100)
g <- Gen.listOfN(size, booleanGen)
} yield g)

def e1 =
prop { list: List[Boolean] =>
StackBool(list: _*).toList must_=== list
}.setGen(for {
size <- Gen.choose(0, 32)
g <- Gen.listOfN(size, booleanGen)
} yield g)

def e2 =
prop { list: List[Boolean] =>
StackBool(list: _*).toList must_=== list
}.setGen(for {
size <- Gen.choose(0, 400)
g <- Gen.listOfN(size, booleanGen)
} yield g)

def e3 = {
val stack = StackBool()
Expand All @@ -47,32 +56,36 @@ class StackBoolSpec extends Specification {
(v3 must_=== true)
}

def e4 = {
val stack = StackBool()

val list = (1 to 400).map(_ % 2 == 0).toList

list.foreach(stack.push(_))

list.reverse.foldLeft(true must_=== true) {
case (result, flag) =>
result and (stack.popOrElse(!flag) must_=== flag)
}
}

def e5 = {
val stack = StackBool()

val list = (1 to 400).map(_ % 2 == 0).toList

list.foreach(stack.push(_))

list.reverse.foldLeft(true must_=== true) {
case (result, flag) =>
val peeked = stack.peekOrElse(!flag)
val popped = stack.popOrElse(!flag)

result and (peeked must_=== popped)
}
}
def e4 =
prop { list: List[Boolean] =>
val stack = StackBool()

list.foreach(stack.push(_))

list.reverse.foldLeft(true must_=== true) {
case (result, flag) =>
result and (stack.popOrElse(!flag) must_=== flag)
}
}.setGen(for {
size <- Gen.choose(100, 400)
g <- Gen.listOfN(size, booleanGen)
} yield g)

def e5 =
prop { list: List[Boolean] =>
val stack = StackBool()

list.foreach(stack.push(_))

list.reverse.foldLeft(true must_=== true) {
case (result, flag) =>
val peeked = stack.peekOrElse(!flag)
val popped = stack.popOrElse(!flag)

result and (peeked must_=== popped)
}
}.setGen(for {
size <- Gen.choose(100, 400)
g <- Gen.listOfN(size, booleanGen)
} yield g)
}

0 comments on commit b5a411f

Please sign in to comment.