Uploaded image for project: 'Calcite'
  1. Calcite
  2. CALCITE-604

Invoke metadata providers via a class generated at runtime, rather than reflection

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 1.7.0
    • Component/s: None
    • Labels:
      None

      Description

      Currently we dispatch to metadata providers using reflection. With this change, we generate a dispatcher class and compile using Janino.

      Currently calls to metadata providers are dispatched via reflection (Method.invoke, see ReflectiveRelMetadataProvider, ChainedRelMetadataProvider and CachingInvocationHandler), and I suspect that planning a complex query (especially with tracing enabled) makes millions of calls. Reflection has a performance problem; lots of boxing and unboxing and creating temporary arrays and bound objects.

      One option we considered to improve performance was moving to MethodHandle. (JDK 1.7 introduced http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandle.html, a more efficient way to invoke methods. Access control etc. is done when the handle is created, not each invocation.) However, MethodHandle.invoke can dispatch only within one provider.

      So, we should generate a dispatcher class, and compile and instantiate at run time using Janino.

      For each metadata interface we will need to add a handler interface that has the same methods but two extra arguments (a sub-class of RelNode, and a RelMetadataQuery). Previously these arguments were passed by calling "bind" on an UnboundMetadata.

      We can also make the dispatcher cache the results within a metadata call. This should yield a performance improvement when, say, a join requires many kinds of metadata (rowCount, averageRowSize, selectivity) and they all rely on the rowCount of the input, and that rowCount is expensive to compute.

        Issue Links

          Activity

          Hide
          julianhyde Julian Hyde added a comment -

          Resolved in release 1.7.0 (2016-03-22).

          Show
          julianhyde Julian Hyde added a comment - Resolved in release 1.7.0 (2016-03-22).
          Show
          julianhyde Julian Hyde added a comment - Fixed in http://git-wip-us.apache.org/repos/asf/calcite/commit/d14040c5 .
          Hide
          vladimirsitnikov Vladimir Sitnikov added a comment -

          I'll pick it up this Sunday.

          Show
          vladimirsitnikov Vladimir Sitnikov added a comment - I'll pick it up this Sunday.
          Hide
          jnadeau Jacques Nadeau added a comment -

          I just haven't been able to get to this, sorry. Don't wait for me.

          Show
          jnadeau Jacques Nadeau added a comment - I just haven't been able to get to this, sorry. Don't wait for me.
          Hide
          julianhyde Julian Hyde added a comment -

          Jacques Nadeau, Vladimir Sitnikov, Do you need more time for review? If I don't hear I will check in in a day or two.

          Show
          julianhyde Julian Hyde added a comment - Jacques Nadeau , Vladimir Sitnikov , Do you need more time for review? If I don't hear I will check in in a day or two.
          Hide
          julianhyde Julian Hyde added a comment -

          To each existing metadata class, you need to add a DEF constant and a handler interface. For example, Memory was

            public interface Memory extends Metadata {
              Double memory();
              Double cumulativeMemoryWithinPhase();
              Double cumulativeMemoryWithinPhaseSplit();
            }
          

          and is now

            public interface Memory extends Metadata {
              MetadataDef<Memory> DEF = MetadataDef.of(Memory.class,
                  Memory.Handler.class, BuiltInMethod.MEMORY.method,
                  BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE.method,
                  BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE_SPLIT.method);
          
              Double memory();
              Double cumulativeMemoryWithinPhase();
              Double cumulativeMemoryWithinPhaseSplit();
          
              interface Handler extends MetadataHandler<Memory> {
                Double memory(RelNode r, RelMetadataQuery mq);
                Double cumulativeMemoryWithinPhase(RelNode r, RelMetadataQuery mq);
                Double cumulativeMemoryWithinPhaseSplit(RelNode r, RelMetadataQuery mq);
              }
            }
          

          and

          public class RelMdMemory {
            ...
          

          is now

          public class RelMdMemory implements MetadataHandler<BuiltInMetadata.Memory> {
            public MetadataDef<BuiltInMetadata.Memory> getDef() {
              return BuiltInMetadata.Memory.DEF;
            }
            ...
          
          Show
          julianhyde Julian Hyde added a comment - To each existing metadata class, you need to add a DEF constant and a handler interface. For example, Memory was public interface Memory extends Metadata { Double memory(); Double cumulativeMemoryWithinPhase(); Double cumulativeMemoryWithinPhaseSplit(); } and is now public interface Memory extends Metadata { MetadataDef<Memory> DEF = MetadataDef.of(Memory.class, Memory.Handler.class, BuiltInMethod.MEMORY.method, BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE.method, BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE_SPLIT.method); Double memory(); Double cumulativeMemoryWithinPhase(); Double cumulativeMemoryWithinPhaseSplit(); interface Handler extends MetadataHandler<Memory> { Double memory(RelNode r, RelMetadataQuery mq); Double cumulativeMemoryWithinPhase(RelNode r, RelMetadataQuery mq); Double cumulativeMemoryWithinPhaseSplit(RelNode r, RelMetadataQuery mq); } } and public class RelMdMemory { ... is now public class RelMdMemory implements MetadataHandler<BuiltInMetadata.Memory> { public MetadataDef<BuiltInMetadata.Memory> getDef() { return BuiltInMetadata.Memory.DEF; } ...
          Hide
          julianhyde Julian Hyde added a comment - - edited

          Here is an example of generated code.

          public class GeneratedMetadataHandler_ColumnUniqueness {
            private final java.util.List relClasses;
            public final org.apache.calcite.rel.metadata.RelMdColumnUniqueness provider0;
            public GeneratedMetadataHandler_ColumnUniqueness(java.util.List relClasses,
                org.apache.calcite.rel.metadata.RelMdColumnUniqueness provider0) {
              this.relClasses = relClasses;
              this.provider0 = provider0;
            }
            public org.apache.calcite.rel.metadata.MetadataDef getDef() {
              return org.apache.calcite.rel.metadata.BuiltInMetadata$ColumnUniqueness.DEF;
            }
            public java.lang.Boolean areColumnsUnique(
                org.apache.calcite.rel.RelNode r,
                org.apache.calcite.rel.metadata.RelMetadataQuery mq,
                org.apache.calcite.util.ImmutableBitSet a0,
                boolean a1) {
              final java.util.List key = org.apache.calcite.runtime.FlatLists.of(org.apache.calcite.rel.metadata.BuiltInMetadata$ColumnUniqueness.DEF, r, org.apache.calcite.rel.metadata.NullSentinel.mask(a0), a1);
              if (!mq.set.add(key)) {
                throw org.apache.calcite.rel.metadata.CyclicMetadataException.INSTANCE;
              }
              try {
                switch (relClasses.indexOf(r.getClass())) {
                default:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.RelNode) r, mq, a0, a1);
                case 2:
                  return provider0.areColumnsUnique((org.apache.calcite.plan.volcano.RelSubset) r, mq, a0, a1);
                case 3:
                  return areColumnsUnique(((org.apache.calcite.plan.hep.HepRelVertex) r).getCurrentRel(), mq, a0, a1);
                case 4:
                case 5:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.convert.Converter) r, mq, a0, a1);
                case 6:
                case 24:
                case 30:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Aggregate) r, mq, a0, a1);
                case 8:
                case 32:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Correlate) r, mq, a0, a1);
                case 9:
                case 33:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Exchange) r, mq, a0, a1);
                case 10:
                case 25:
                case 34:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Filter) r, mq, a0, a1);
                case 11:
                case 35:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Intersect) r, mq, a0, a1);
                case 12:
                case 27:
                case 36:
                case 48:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Join) r, mq, a0, a1);
                case 13:
                case 37:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Minus) r, mq, a0, a1);
                case 14:
                case 26:
                case 38:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Project) r, mq, a0, a1);
                case 15:
                case 39:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Sort) r, mq, a0, a1);
                case 18:
                case 28:
                case 42:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.TableScan) r, mq, a0, a1);
                case 19:
                case 43:
                case 50:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.SetOp) r, mq, a0, a1);
                case 20:
                case 44:
                  return provider0.areColumnsUnique((org.apache.calcite.rel.core.Values) r, mq, a0, a1);
                case -1:
                  throw new org.apache.calcite.rel.metadata.JaninoRelMetadataProvider$NoHandler(r.getClass());
                }
              } finally {
                mq.set.remove(key);
              }
            }
          }
          

          Note how 3 sub-classes of Aggregate all end up in the same handler method. The "default" label handles RelNode and an unspecified other sub-classes. The -1 label deals with RelNode sub-classes that were previously unknown, and will therefore require a re-generation.

          In this case there is only one provider, stored in the field provider0, but if Calcite was used in Drill or Hive there could be more than one.

          Show
          julianhyde Julian Hyde added a comment - - edited Here is an example of generated code. public class GeneratedMetadataHandler_ColumnUniqueness { private final java.util.List relClasses; public final org.apache.calcite.rel.metadata.RelMdColumnUniqueness provider0; public GeneratedMetadataHandler_ColumnUniqueness(java.util.List relClasses, org.apache.calcite.rel.metadata.RelMdColumnUniqueness provider0) { this .relClasses = relClasses; this .provider0 = provider0; } public org.apache.calcite.rel.metadata.MetadataDef getDef() { return org.apache.calcite.rel.metadata.BuiltInMetadata$ColumnUniqueness.DEF; } public java.lang. Boolean areColumnsUnique( org.apache.calcite.rel.RelNode r, org.apache.calcite.rel.metadata.RelMetadataQuery mq, org.apache.calcite.util.ImmutableBitSet a0, boolean a1) { final java.util.List key = org.apache.calcite.runtime.FlatLists.of(org.apache.calcite.rel.metadata.BuiltInMetadata$ColumnUniqueness.DEF, r, org.apache.calcite.rel.metadata.NullSentinel.mask(a0), a1); if (!mq.set.add(key)) { throw org.apache.calcite.rel.metadata.CyclicMetadataException.INSTANCE; } try { switch (relClasses.indexOf(r.getClass())) { default : return provider0.areColumnsUnique((org.apache.calcite.rel.RelNode) r, mq, a0, a1); case 2: return provider0.areColumnsUnique((org.apache.calcite.plan.volcano.RelSubset) r, mq, a0, a1); case 3: return areColumnsUnique(((org.apache.calcite.plan.hep.HepRelVertex) r).getCurrentRel(), mq, a0, a1); case 4: case 5: return provider0.areColumnsUnique((org.apache.calcite.rel.convert.Converter) r, mq, a0, a1); case 6: case 24: case 30: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Aggregate) r, mq, a0, a1); case 8: case 32: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Correlate) r, mq, a0, a1); case 9: case 33: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Exchange) r, mq, a0, a1); case 10: case 25: case 34: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Filter) r, mq, a0, a1); case 11: case 35: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Intersect) r, mq, a0, a1); case 12: case 27: case 36: case 48: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Join) r, mq, a0, a1); case 13: case 37: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Minus) r, mq, a0, a1); case 14: case 26: case 38: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Project) r, mq, a0, a1); case 15: case 39: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Sort) r, mq, a0, a1); case 18: case 28: case 42: return provider0.areColumnsUnique((org.apache.calcite.rel.core.TableScan) r, mq, a0, a1); case 19: case 43: case 50: return provider0.areColumnsUnique((org.apache.calcite.rel.core.SetOp) r, mq, a0, a1); case 20: case 44: return provider0.areColumnsUnique((org.apache.calcite.rel.core.Values) r, mq, a0, a1); case -1: throw new org.apache.calcite.rel.metadata.JaninoRelMetadataProvider$NoHandler(r.getClass()); } } finally { mq.set.remove(key); } } } Note how 3 sub-classes of Aggregate all end up in the same handler method. The "default" label handles RelNode and an unspecified other sub-classes. The -1 label deals with RelNode sub-classes that were previously unknown, and will therefore require a re-generation. In this case there is only one provider, stored in the field provider0 , but if Calcite was used in Drill or Hive there could be more than one.
          Hide
          julianhyde Julian Hyde added a comment -

          While upgrading, if you get a stack like this:

          java.lang.ExceptionInInitializerError
          	at org.apache.calcite.rel.metadata.DefaultRelMetadataProvider.<init>(DefaultRelMetadataProvider.java:36)
          	at ... 
          Caused by: java.lang.ClassCastException: org.apache.calcite.rel.metadata.RelMdSomething cannot be cast to org.apache.calcite.rel.metadata.MetadataHandler
          	at org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider.reflectiveSource(ReflectiveRelMetadataProvider.java:119)
          	at org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider.reflectiveSource(ReflectiveRelMetadataProvider.java:107)
          	at org.apache.calcite.rel.metadata.RelMdPopulationSize.<clinit>(RelMdPopulationSize.java:41)
          	... 33 more
          

          you need to add implements MetadataHandler<BuiltinMetadata.Something> to your provider (say RelMdPopulationSize needs to implement MetadataHandler<BuiltinMetadata.PopulationSize>.

          Show
          julianhyde Julian Hyde added a comment - While upgrading, if you get a stack like this: java.lang.ExceptionInInitializerError at org.apache.calcite.rel.metadata.DefaultRelMetadataProvider.<init>(DefaultRelMetadataProvider.java:36) at ... Caused by: java.lang.ClassCastException: org.apache.calcite.rel.metadata.RelMdSomething cannot be cast to org.apache.calcite.rel.metadata.MetadataHandler at org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider.reflectiveSource(ReflectiveRelMetadataProvider.java:119) at org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider.reflectiveSource(ReflectiveRelMetadataProvider.java:107) at org.apache.calcite.rel.metadata.RelMdPopulationSize.<clinit>(RelMdPopulationSize.java:41) ... 33 more you need to add implements MetadataHandler<BuiltinMetadata.Something> to your provider (say RelMdPopulationSize needs to implement MetadataHandler<BuiltinMetadata.PopulationSize> .
          Hide
          julianhyde Julian Hyde added a comment -
          Show
          julianhyde Julian Hyde added a comment - Work in progress at https://github.com/julianhyde/calcite/tree/604-tune-metadata .

            People

            • Assignee:
              julianhyde Julian Hyde
              Reporter:
              julianhyde Julian Hyde
            • Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development