Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@adamgfraser
Copy link
Contributor

Resolves #3238. Resolves #3753.

Implements balanced concatenation for Chunk based on the algorithm used in Conc-Trees. This also addresses the stack overflow issue.

@adamgfraser adamgfraser requested review from iravid and jdegoes June 4, 2020 00:16
@ahoy-jon
Copy link
Contributor

ahoy-jon commented Jun 4, 2020

@adamgfraser Maybe I am completely wrong, but you may not need depth to do that, the size difference between left and right is enough to choose where to land the new piece in the tree.

For reference: https://github.com/zio/zio/pull/3358/files#diff-290e96e6c64027a32c36d751a35d65deR671-R735

@ahoy-jon
Copy link
Contributor

ahoy-jon commented Jun 4, 2020

I went back to the code I wrote at the time, with the perf test, this was doing a decent job and perfect balancing on the repeated concatenation.

final def concat[A](l: NonEmpty[A], r: NonEmpty[A]): NonEmptyChunk[A] =
    l match {
      case Concat(ll, lr) if ll.size >= lr.size + r.size => Concat(ll, concat(lr, r))
      case _                                             => Concat(l, r)
    }

This one is the same version without the recursion, and manage the case were the RHS is larger (code duplication).

def concat[A](l: NonEmpty[A], r: NonEmpty[A]): NonEmptyChunk[A] =
    if (l.length == r.length) Concat(l, r)
    else if (l.length > r.length) {

      val lefts: Array[NonEmptyChunk[A]] = Array.ofDim[NonEmptyChunk[A]](16)
      var n: Int                         = 0

      var point: NonEmptyChunk[A] = l

      var continue = true

      while (continue && n < 16) {
        point match {
          case _ if point.length <= r.length =>
            continue = false
          case Concat(ll, lr) if ll.length >= lr.length + r.length =>
            lefts(n) = ll
            n += 1
            point = lr
          case _ =>
            continue = false
        }
      }

      var res = Concat(point, r)
      var i   = n - 1

      while (i >= 0) {
        res = Chunk.Concat(lefts(i), res)
        i -= 1
      }

      res

    } else {
      val rights = Array.ofDim[NonEmptyChunk[A]](16)
      var n: Int = 0

      var point: NonEmptyChunk[A] = r

      var continue = true

      while (continue && n < 16) {
        point match {
          case _ if point.length <= l.length =>
            continue = false
          case Concat(rl, rr) if rr.length >= rl.length + l.length =>
            rights(n) = rr
            n += 1
            point = rl
          case _ =>
            continue = false
        }
      }

      var res = Concat(l, point)
      var i   = n - 1

      while (i >= 0) {
        res = Chunk.Concat(res, rights(i))
        i -= 1
      }

      res
    }

The issue was, due to the limited structure of the Chunk (with Concat having only 2 possibles children), the performance did not improve much (but both fixed the StackOverflow issues).

With your work on repeated appends and pretends (thanks 🙏 ), the performance differences may not be visible on regular use-cases.

@iravid
Copy link
Member

iravid commented Jun 4, 2020

Looks great @adamgfraser. Can you add a repeated concatenation benchmark to see the perf. hit?

@adamgfraser
Copy link
Contributor Author

Yes. It depends a lot on usage pattern. For balanced concatenation it doesn't make much of a difference. For degenerate cases where you repeatedly concatenate a single element it is quite a hit. Maybe when we concatenate we could check the length of the chunks and treat it as an append or prepend if one of them has a length of one to avoid the very worst case?

[info] Benchmark                             (size)   Mode  Cnt     Score    Error  Units
[info] ChunkConcatBenchmarks.balancedConcat   10000  thrpt   25  1576.387 ±  6.589  ops/s
[info] ChunkConcatBenchmarks.leftConcat       10000  thrpt   25  1199.527 ± 16.542  ops/s
[info] ChunkConcatBenchmarks.rightConcat      10000  thrpt   25  1210.338 ± 12.808  ops/s
[info] Benchmark                             (size)   Mode  Cnt     Score    Error  Units
[info] ChunkConcatBenchmarks.balancedConcat   10000  thrpt   25  1469.000 ± 16.430  ops/s
[info] ChunkConcatBenchmarks.leftConcat       10000  thrpt   25   431.142 ±  2.654  ops/s
[info] ChunkConcatBenchmarks.rightConcat      10000  thrpt   25   397.721 ±  3.231  ops/s

Copy link
Member

@jdegoes jdegoes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Copy link
Member

@iravid iravid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and we can do the appends/prepends optimization in a follow-up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chunk.toArray StackOverflow, ZIO-RC19 Optimize Chunk#++

4 participants