Using GroovyScriptEngine can result in performance which is magnitudes worse than loading scripts via GroovyClassLoader.
The reason is the extensive dependency checking that assures that the script will be recompiled should it or one of its dependencies change.
In the following examples/suggestions running a simple script via GroovyClassLoader (compiling it once) has a performance factor of 1. Every performance number greater than 1 shows a respective slower performance, e.g. performance factor 2 means the test's runtime is twice as high.
In a simple test, using GroovyScriptEngine resulted in a performance factor of 33, meaning GroovyScriptEngine performed 33 times worse than a test case using GroovyClassLoader!
Possible improvements with resulting performance factors:
(1) Use java.io.File instead of java.net.URL where possible
Tests have shown that determining a last modified timestamp for a java.io.File is faster than for a java.net.URL (via URLConnection). If the information that a resource is actually a File is determined and cached, performance can be increased.
(2) Reduce the number of dependencies that need to be checked
The dependencies map returned from parseClass includes a lot of dependencies that don't really exist (e.g. it.groovy in various packages and classes resulting from closures). Assuming that introducing such non-existing dependencies after the script has been parsed the first time is not useful, every dependency for which no resource exists can be removed from the map. In simple cases (the script doesn't use any other external classes), the dependency map will be empty.
(3) Don't check dependencies on every script run
Assuming that external scripts are not changed at a fast rate, introduce a time based threshold to check the dependencies only every x ms. The test was performed with a interval of 1000 ms.
Though this result is optimal, it is only relevant to certain scenarios. E.g. if the script would not have been run in a tight loop, but executed only every second, there would have been no improvement using this strategy.
Using Files (1) doesn't change that much and is probably not worth the hassle, especially if (2) and/or (3) are implemented.
Removing dependencies (2) should always be done. Please comment if you know of cases that the used algorithm (check existence of dependency and remove it in case of non-existence when creating the cache entry for the script) does break something.
Introducing a time interval for dependency checking (3) should be implemented with a setter to set the interval. With a default value of 0 the behaviour would be the same as it is now. It can then be fine tuned for the specific application to achieve optimal results.
Please comment on the above suggestions. Once it has been decided which strategy to use, I can provide both a patch to GroovyScriptEngine and a standalone class ("OptimizedGroovyScriptEngine" ?) for people who need to use that now.