Scala Programming Language
  1. Scala Programming Language
  2. SI-7186

Consider introducing special cases for TypeRefs of small arity

    Details

    • Type: Improvement Improvement
    • Status: Open
    • Priority: Minor Minor
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: Scala 2.11.1-RC1
    • Component/s: Type Checker
    • Labels:
      None

      Description

      Jason proposed we would look into special casing TypeRefs that have small arity (probably 0, 1 and 2) in order to reduce memory consumption coming from references to :: objects.

        Activity

        Hide
        Jason Zaugg added a comment -

        The distribution of created TypeRefs when compiling scala/collection/**

        
        

        ================================================================================
        TypeRef args distribution
        ================================================================================
        0 1895430 47.26%
        1 867559 21.63%
        2 887096 22.12%
        3 357780 8.92%
        4 1287 0.03%
        5 1156 0.03%
        6 69 0.00%
        7 8 0.00%
        8 8 0.00%
        9 10 0.00%

        I've prototyped a solution that avoids the :: for one-arg type refs. The tradeoff is slower pattern matching on TypeRefs, which now must go through an unapply method. I couldn't measure any different in compiler speed.

        I also noticed that some TypeRef subclasses had a needless outer pointer, and have eliminated that by unnesting them out of object TypeRef. For fun, I added infrastructure to the Instrumented tests to show the JVM object size.

        https://github.com/retronym/scala/compare/ticket/7186

        I'll extract the changes to slim down TypeRef to another branch and submit a PR, as they are a clear micro-improvement. But I'll leave the first change on the branch; @Grzegorz, perhaps you can suggest a scientific way to benchmark the memory impact? I was using YourKit manually, but I didn't have good control over when I took the memory snapshot.

        Show
        Jason Zaugg added a comment - The distribution of created TypeRefs when compiling scala/collection/** ================================================================================ TypeRef args distribution ================================================================================ 0 1895430 47.26% 1 867559 21.63% 2 887096 22.12% 3 357780 8.92% 4 1287 0.03% 5 1156 0.03% 6 69 0.00% 7 8 0.00% 8 8 0.00% 9 10 0.00% I've prototyped a solution that avoids the :: for one-arg type refs. The tradeoff is slower pattern matching on TypeRefs, which now must go through an unapply method. I couldn't measure any different in compiler speed. I also noticed that some TypeRef subclasses had a needless outer pointer, and have eliminated that by unnesting them out of object TypeRef. For fun, I added infrastructure to the Instrumented tests to show the JVM object size. https://github.com/retronym/scala/compare/ticket/7186 I'll extract the changes to slim down TypeRef to another branch and submit a PR, as they are a clear micro-improvement. But I'll leave the first change on the branch; @Grzegorz, perhaps you can suggest a scientific way to benchmark the memory impact? I was using YourKit manually, but I didn't have good control over when I took the memory snapshot.
        Hide
        Miguel Garcia added a comment -

        It's a bit off topic, but what's the reason that "redundant outer pointers" aren't removed by, say, the optimizer? Does "separate compilation" stand in the way?

        At least for "late closure classes", the new optimizer does a thorough job of squashing unneeded outer pointers,
        https://github.com/magarciaEPFL/scala/commit/a4bed205aeb1eb968702294703d5730c9f849f5c

        I thought that Constructors would detect (most) cases of redundant outer pointers, isn't that enough?

        Show
        Miguel Garcia added a comment - It's a bit off topic, but what's the reason that "redundant outer pointers" aren't removed by, say, the optimizer? Does "separate compilation" stand in the way? At least for "late closure classes", the new optimizer does a thorough job of squashing unneeded outer pointers, https://github.com/magarciaEPFL/scala/commit/a4bed205aeb1eb968702294703d5730c9f849f5c I thought that Constructors would detect (most) cases of redundant outer pointers, isn't that enough?
        Hide
        Jason Zaugg added a comment -

        That's a good question. I'm not really sure what is triggering the creation and retention of that outer pointer.

        e.g. what is different about Types/TypeRef to this example?

        scala> trait T { val t = "t"; class C { def tt = t }; object O { def foo = new C {} } }
        defined trait T
        
        scala> new T{}.O.foo.getClass.getDeclaredFields
        res2: Array[java.lang.reflect.Field] = Array()
        

        Needs some investigation.

        Show
        Jason Zaugg added a comment - That's a good question. I'm not really sure what is triggering the creation and retention of that outer pointer. e.g. what is different about Types/TypeRef to this example? scala> trait T { val t = "t"; class C { def tt = t }; object O { def foo = new C {} } } defined trait T scala> new T{}.O.foo.getClass.getDeclaredFields res2: Array[java.lang.reflect.Field] = Array() Needs some investigation.
        Hide
        Miguel Garcia added a comment -

        I'll take a look what can be done (if all else fails, via the new optimizer).

        Show
        Miguel Garcia added a comment - I'll take a look what can be done (if all else fails, via the new optimizer).
        Hide
        Jason Zaugg added a comment -

        I've added a recent optimization of Paul's to the branch.

        https://github.com/scala/scala/commit/79b18ccf9

        That takes TypeRefs from 54 bytes to 40 bytes, by moving usually-null fields to global maps. Back-of-the-envelope-wise, it should save about 2-3% of our retained memory.

        Show
        Jason Zaugg added a comment - I've added a recent optimization of Paul's to the branch. https://github.com/scala/scala/commit/79b18ccf9 That takes TypeRefs from 54 bytes to 40 bytes, by moving usually-null fields to global maps. Back-of-the-envelope-wise, it should save about 2-3% of our retained memory.
        Hide
        Jason Zaugg added a comment -

        The non-controversial change to unnest anonymous TypeRef subclasses from object TypeRef has been merged https://github.com/scala/scala/pull/2325.

        But it would still be great to pin down the real raison d'être for that extra outer field.

        Show
        Jason Zaugg added a comment - The non-controversial change to unnest anonymous TypeRef subclasses from object TypeRef has been merged https://github.com/scala/scala/pull/2325 . But it would still be great to pin down the real raison d'être for that extra outer field.
        Hide
        Adriaan Moors added a comment -

        Unassigning as milestone deadline was reached.

        Show
        Adriaan Moors added a comment - Unassigning as milestone deadline was reached.
        Hide
        Adriaan Moors added a comment -

        Since 2.11.0-RC1 is one week away, pushing all non-blockers without PR to 2.11.1-RC1. Please undo the change if I missed work in progress.

        Show
        Adriaan Moors added a comment - Since 2.11.0-RC1 is one week away, pushing all non-blockers without PR to 2.11.1-RC1. Please undo the change if I missed work in progress.

          People

          • Assignee:
            Jason Zaugg
            Reporter:
            Grzegorz Kossakowski
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:

              Development