Christian,

   This is an excellent feedback. We will certainly look into improving this!

   Thanks a lot,
      Edson

2009/4/20 Christian Nedregård <christian_nedregaard@email.com>
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 {
            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();
    }
}

----------------------------------------------


2009/4/17 Edson Tirelli <tirelli@post.com>


   Christian,

   Can you break down this performance into 2 steps?

1. COMPILE: this is the step where textual information is parsed and compiled into binary data. This is done by the PackageBuilder.addPackageFromDrl() method.

2. RETE BUILDING: this is the step where the binary data is assembled into the rete network. This is done by RuleBase.addPackage() method.

    These two processes are mostly independent and it would be good to know what is the relative time spent in each.

    After that, we will need to get a few rule samples from you with a test case so that we can investigate.

     Thanks,
        Edson

2009/4/17 Christian Nedregård <christian_nedregaard@email.com>

Thank you for the quick reply and the tip Ingomar.

Since our rules are template based I was able to merge them into 30 drls (850 before). This gave a 30% perfomance improvement, but we are still not able to compile more than 2 rules per second. I also tried to execute in with jdk 1.6.0_03 instead of 1.4.2_11. This only gave a small performance increase.

We are also seing the same slow performance in our production environment wich is JDK 1.4.2_11 on Solaris, so this is not a OS spesific problem.

I addition I tried to run the compilation in a dedicated process with 1G available memory with no performance increase, so it is not related to memory restrictions either.

From our experimentation I see that the compile time, unsurpisingly, is directly connectet to the complexity of the rule. If i strip away he LHS and RHS of the rules and only keep the imports and global definitions I am able to get a compilation throughput at 24 rules per second.

Are your rules much simpler than ours? Can you think of any other reason why you are seeing a much better compiler throughput?

Regards
Chr



On Fri, Apr 17, 2009 at 10:34 AM, Ingomar Otter <iotter@mac.com> wrote:
Christian,
with our app, we see a compile performance which is at least an order of magnitude higher.
I would assume that in your case the extreme fine granularity of your DRLs may be slowing things down.
Have you tried to consolidate these in bigger but fewer DRls?

If you need that granualarity, you still have the option of joining these together before running it with the help of perl and the likes.

--I
_______________________________________________
rules-users mailing list
rules-users@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/rules-users



_______________________________________________
rules-users mailing list
rules-users@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/rules-users




--
 Edson Tirelli
 JBoss Drools Core Development
 JBoss, a division of Red Hat @ www.jboss.com

_______________________________________________
rules-users mailing list
rules-users@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/rules-users



_______________________________________________
rules-users mailing list
rules-users@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/rules-users




--
 Edson Tirelli
 JBoss Drools Core Development
 JBoss, a division of Red Hat @ www.jboss.com