Uploaded image for project: 'JBRULES'
  1. JBRULES
  2. JBRULES-2067

Compilation performance for rules degrades as the classpath increases

This issue belongs to an archived project. You can view it, but you can't modify it. Learn more

    XMLWordPrintable

Details

    • Bug
    • Resolution: Done
    • Major
    • 5.1.0.M2
    • 5.0.0.CR1
    • drools-compiler
    • None

    Description

      REPORTED BY: Christian NedregÄrd

      =============================
      This is now solved.

      I noticed that the problem became worse the bigger the classpath was, even if the extra jars contained totally irrellevant classes.

      Profiling revealed that the rule compilation generated an enomous amount of IO traffic from ClassLoader.loadClass originating from
      org.mvel.ParserContext.checkForDynamicImport and org.drools.rule.PackageCompilationData$PackageClassLoader.

      In both these places Drools generates a lot of loadClass calls for classes that does not exist.
      It seems like the Sun classloaders does not cache the result of failed class lookups, but scans the whole classpath again and again looking for these non-existing classes.
      That is why the problem gets bigger the larger your classpath is. Our classpath contains 100+ jars, so we got hit pretty bad.

      I solved the problem by creating a caching classloader that also caches the result of failed lookups. When I added this as the context classloader scoped around all my compilation I saw a performance increase of a factor of 10 - 20 in our various environments.

      Here are the stats from the caching classloader after compiling 850 rules:

      Caching loader called 200608 times
      119 actual successful loads
      932 actual failed loads
      73738 cache hits for successfull loads
      125819 cache hits for failed loads

      In other words: Drools generated 126751 loads for 932 non-existing classes. We saved 125819 full classapth scans by caching the failed lookups.

      As mentioned above the caching classloader can be hooked into the system by setting it at the context classloader.
      org.mvel.ParserContext.checkForDynamicImport uses the context classloader directly.
      org.drools.rule.PackageCompilationData$PackageClassLoader uses the classloader configured in PackageBuilderConfiguration which is the context classloader by default.
      This means you get the full effect only when you scope the caching class loader around all your PackageBuilderConfiguration and PackageBuilder usage.

      Below is the code for our caching classloader for the reference of anybody else who might encounter this problem.

      Regards
      Chr

      PS Edson:
      This problem is related to compilation. The RETE building is usually very fast.

      ----------------------------------------------
      package domain.rules;

      import java.util.HashMap;
      import java.util.Map;

      /**

      • Classloader that intercepts calls to the current context class loader and caches the result of the calls to {@link #loadClass}
      • on it.
      • <p/>
      • The interception will start automagically when this class is instantiated. Make sure that you call {@link #stop()}

        from a finally

      • block when you want the loader interception to stop, to avoid unnecessary caching.
        *
      • @author Christian NedregĂ„rd
      • @see Thread#getContextClassLoader()
        */
        public class CachingContextClassLoader extends ClassLoader {
        private Map classLoaderResultMap = new HashMap();
        private ClassLoader originalClassLoader;
        private int successfulCalls = 0;
        private int failedCalls = 0;
        private int cacheHits = 0;
        private int successfulLoads = 0;
        private int failedLoads = 0;

      /**

      • Create an instance. The instance will automagically start intercepting {@link #loadClass(String)}

        calls to the current

      • context class loader.
        *
      • @see Thread#getContextClassLoader()
        */
        CachingContextClassLoader() { super(Thread.currentThread().getContextClassLoader()); this.originalClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(this); }

      /**

      • Try to load the class from the original context class loader and chache the result (also <code>ClassNotFoundException</code>s).
      • Subsequent calls for the same class name will return the cached version, or throw a cached ClassNotFoundException if it was
      • not found initially.
        *
      • @param name the name of the class to find.
      • @return the cached result of a call to <code>loadClass</code> on the original context class loader.
      • @throws ClassNotFoundException when the class of the given name could not be found.
        */
        public Class loadClass(String name) throws ClassNotFoundException {
        Object result;
        if (classLoaderResultMap.containsKey(name)) { ++cacheHits; result = classLoaderResultMap.get(name); }

        else

        Unknown macro: { try { result = super.loadClass(name); ++successfulLoads; } catch (ClassNotFoundException e) { ++failedLoads; result = e; } classLoaderResultMap.put(name, result); }

        if (result instanceof ClassNotFoundException)

        { ++failedCalls; throw (ClassNotFoundException) result; }

        else

        { ++successfulCalls; return (Class) result; }

        }

      /**

      • Stop intercepting by resetting the original context classloader using {@link Thread#setContextClassLoader(ClassLoader)}

        .
        */
        public void stop()

        { Thread.currentThread().setContextClassLoader(originalClassLoader); }

      /**

      • Get a textual report of the caching that was performed.
        *
      • @return a textual report of the caching that was performed.
        */
        public String getReport() { return new StringBuffer("Loader called ").append(successfulCalls + failedCalls).append(" times, ").append(successfulCalls) .append(" successfull, ").append(failedCalls).append(" failed. Cache hits: ").append(cacheHits) .append(", successful loads: ").append(successfulLoads).append(", Failed loads: ").append(failedLoads) .append('.').toString(); }

        }

      Attachments

        Issue Links

          Activity

            People

              etirelli@redhat.com Edson Tirelli
              etirelli@redhat.com Edson Tirelli
              Archiver:
              rhn-support-ceverson Clark Everson

              Dates

                Created:
                Updated:
                Resolved:
                Archived:

                PagerDuty