Pack

Pack is an immutable collection of values designed to replace scala.List and scala.Vector. Pack is mostly a wrapper around an Array, which is private and cannot be modified. Scala3 does introduce an IArray type, which also cannot be modified, but it can easily be cast into regular array and therefor is not sufficiently tamper proof.

Creation

The only drawback of Pack is the "element append" operation. The entire internal array has to be copied each time. On smaller element count Pack is still competitive:

    val array  : Array[String] = (0 <>> 12).stream.map(_.toString).toArray

    J.Benchmark(
      ("List",   () => {var l: List[String]   = Nil;          for(i <- 0 <>> 12) l = array(i) :: l; l.size}),
      ("Vector", () => {var v: Vector[String] = Vector.empty; for(i <- 0 <>> 12) v = v :+ array(i); v.size}),
      ("Pack",   () => {var p: Pack[String]   = VOID;         for(i <- 0 <>> 12) p = p + array(i);  p.size}),
    )

    // Output
    Final Result. Total length is about 12 secs
    --- ------ ------- --- ------ --- ---------
    Num Name   Ops/Sec %   Memory %   Avg Value
    --- ------ ------- --- ------ --- ---------
    1   List   13.1m   100 228B   24  12.0
    2   Vector 3.1m    23  930B   100 12.0
    3   Pack   4.8m    37  712B   76  12.0
    --- ------ ------- --- ------ --- ---------

However, it is rare in Scalqa, to add elements one by one, everything is done with streams. When adding multiple elements Pack provides Buffer like performance.

    val many: Seq[String] = (0 <>> 12).stream.map(_.toString).toSeq

    J.Benchmark(
        ("List",   () => {var l: List[String]   = Nil;          l = l :++ many; l.size}),
        ("Vector", () => {var v: Vector[String] = Vector.empty; v = v :++ many; v.size}),
        ("Pack",   () => {var p: Pack[String]   = VOID;         p = p ++ many;  p.size}),
    )


    // Output
    Final Result. Total length is about 12 secs
    --- ------ ------- --- ------ --- ---------
    Num Name   Ops/Sec %   Memory %   Avg Value
    --- ------ ------- --- ------ --- ---------
    1   List   5.0m    21  383B   100 12.0
    2   Vector 14.2m   59  124B   32  12.0
    3   Pack   23.8m   100 59B    15  12.0
    --- ------ ------- --- ------ --- ---------

Storage

Due to the fact that, from JVM prospective, Array is a perfect storage, Pack beats both: scala.List and scala.Vector on compactness. Note memory utilization to store 1 million strings:

    val array: Array[String] = (0 <>> 1000000).stream.map(_.toString).toArray

    J.Benchmark(
      ("List",   () => array.toList.size),
      ("Vector", () => array.toVector.size),
      ("Pack",   () => array.pack.size),
    )

    // Output
    Final Result. Total length is about 12 secs
    --- ------ ------- --- ------ --- ---------
    Num Name   Ops/Sec %   Memory %   Avg Value
    --- ------ ------- --- ------ --- ---------
    1   List   154     14  24.0mB 100 1000000.0
    2   Vector 120     11  4.5mB  18  1000000.0
    3   Pack   1.0k    100 3.7mB  15  1000000.0
    --- ------ ------- --- ------ --- ---------

Specialized

Pack really shines when storing unboxed primitives:

    val array : Array[Time.Length] = (0 <>> 1000000).stream.map(_.Seconds).toArray

    J.Benchmark(
      ("List",   () => array.toList.size),
      ("Vector", () => array.toVector.size),
      ("Pack",   () => array.pack.size),
    ) 

   // Output
   Final Result. Total length is about 12 secs
   --- ------ ------- --- ------ --- ---------
   Num Name   Ops/Sec %   Memory %   Avg Value
   --- ------ ------- --- ------ --- ---------
   1   List   81      12  47.7mB 100 1000000.0
   2   Vector 101     16  27.5mB 57  1000000.0
   3   Pack   631     100 8.2mB  17  1000000.0
   --- ------ ------- --- ------ --- ---------
   

It takes slightly over 8mB to store 1 million Long values.