[rules-users] Performance of compiler

Edson Tirelli tirelli at post.com
Mon Apr 20 08:30:21 EDT 2009


   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 at 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 at 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 at 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 at 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 at lists.jboss.org
>>>> https://lists.jboss.org/mailman/listinfo/rules-users
>>>>
>>>>
>>>
>>> _______________________________________________
>>> rules-users mailing list
>>> rules-users at 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 at lists.jboss.org
>> https://lists.jboss.org/mailman/listinfo/rules-users
>>
>>
>
> _______________________________________________
> rules-users mailing list
> rules-users at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/rules-users/attachments/20090420/c0e9a694/attachment.html 


More information about the rules-users mailing list