[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