[forge-dev] Forge performance considerations / Bootstrap / modularity
Thomas Frühbeck
fruehbeck at aon.at
Sat Nov 3 13:40:39 EDT 2012
Hi,
following the discussions about boot-up performance of Forge I made some
investigations using a profiler.
After testing a lot of different scenarios I would like to share my
results and ask for your opinion on it.
*************************************************************************************
Issue 1.: hot spot inv.
org.jboss.forge.bus.cdi.ObserverCaptureExtension.scan
profiling shows that 37% CPU time are used in firing
ProcessAnnotatedType events, which are AFAICS only used to filter old
event types
"37,2% - 13.442 ms - 1.866 inv.
org.jboss.weld.bootstrap.events.ProcessAnnotatedTypeImpl.fire"
By introducing a preparatory check in ObserverCaptureExtension like
> if (isMethodsAnnotated(originalType, Observes.class)) {
> ....
> }
and something like:
> private boolean isMethodsAnnotated(AnnotatedType<?> type, Class<?
extends Annotation> annotation) {
> if (type.getClass().isAnnotationPresent(annotation))
> return true;
> for (Method m : type.getClass().getMethods()) {
> if (m.isAnnotationPresent(annotation))
> return true;
> for (Annotation[] arr : m.getParameterAnnotations()) {
> for (Annotation a : arr) {
> if (a.equals(annotation))
> return true;
> }
> }
> }
> return false;
> }
we could reduce startup by 20%
"13,8% - 2.438 ms - 1.798 inv.
org.jboss.weld.bootstrap.events.ProcessAnnotatedTypeImpl.fire
Test: call Forge with commands "list-plugins" and "exit", like ~> time
((echo forge list-plugins; echo exit;)| bin/forge)
Before optimization:
~/forge> time ((echo forge list-plugins && echo exit)| bin/forge )
_____
| ___|__ _ __ __ _ ___
| |_ / _ \| `__/ _` |/ _ \ \\
| _| (_) | | | (_| | __/ //
|_| \___/|_| \__, |\___|
|___/
[no project] forge-distribution-1.1.1-SNAPSHOT $ forge list-plugins
org.richfaces.forge.richfaces-forge-plugin:1.0.5.Final:1.0.0-SNAPSHOT-b1a6ebed-462f-40e1-ac08-82f927489301
com.ocpsoft.forge.prettyfaces-plugin:1.0.2.Final:1.0.0-SNAPSHOT-c6b071ad-a6df-4112-8fff-82ecccd003fb
org.jboss.hibernate.forge.hibernate-tools-plugin:1.0.5.Final:1.0.0-SNAPSHOT-afb1da29-a99f-4dd7-b020-5f3ba6073cde
org.arquillian.forge.arquillian-plugin:1.0.3-SNAPSHOT:1.0.0-SNAPSHOT-42907982-d02e-4a1d-8ea5-8e66c8013fde
[no project] forge-distribution-1.1.1-SNAPSHOT $ exit
real 0m9.721s
user 0m26.533s
sys 0m0.433s
After optimization:
~forge1.1.1-weld1.2> time ((echo forge list-plugins; echo exit;)| bin/forge)
Using Forge at /raid/home/thomas/java/forge1.1.1-weld1.2
_____
| ___|__ _ __ __ _ ___
| |_ / _ \| `__/ _` |/ _ \ \\
| _| (_) | | | (_| | __/ //
|_| \___/|_| \__, |\___|
|___/
[no project] forge1.1.1-weld1.2 $ forge list-plugins
org.richfaces.forge.richfaces-forge-plugin:1.0.5.Final:1.0.0-SNAPSHOT-b1a6ebed-462f-40e1-ac08-82f927489301
com.ocpsoft.forge.prettyfaces-plugin:1.0.2.Final:1.0.0-SNAPSHOT-c6b071ad-a6df-4112-8fff-82ecccd003fb
org.jboss.hibernate.forge.hibernate-tools-plugin:1.0.5.Final:1.0.0-SNAPSHOT-afb1da29-a99f-4dd7-b020-5f3ba6073cde
org.arquillian.forge.arquillian-plugin:1.0.3-SNAPSHOT:1.0.0-SNAPSHOT-42907982-d02e-4a1d-8ea5-8e66c8013fde
[no project] forge1.1.1-weld1.2 $ exit
real 0m6.016s
user 0m14.482s
sys 0m0.449s
*************************************************************************************
Issue 2.: double starting of Weld container
I admit that I do not fully understand, what I did and if this really
provides the full solution, but it worked and produced a fully
functional Forge instance.
I tried to restructure the Bootstrap logic a bit:
initializeClassloader();
WeldContainer container = weld.initialize();
manager = container.getBeanManager();
try
{
loadPlugins();
weld.reInitialize();
where initializeClassloader already uses the CompositeClassloader:
private static void initializeClassloader() {
ModuleLoader moduleLoader = Module.getBootModuleLoader();
CompositeClassLoader composite = new CompositeClassLoader();
composite.add(Module.forClassLoader(Bootstrap.class.getClassLoader(),
true).getClassLoader());
Thread.currentThread().setContextClassLoader(composite);
}
and loadPlugins simply adds more classloaders:
CompositeClassLoader composite =
(CompositeClassLoader)Thread.currentThread().getContextClassLoader();
for (PluginEntry plugin : toLoad)
{
Module module =
moduleLoader.loadModule(ModuleIdentifier.fromString(plugin.toModuleId()));
composite.add(module.getClassLoader());
This way the classes are already available in the correct classloader
for the later ModularWeld.reInitialize():
public void reInitialize() {
bootstrap.revisit();
bootstrap.validateBeans();
bootstrap.endInitialization();
}
Where Bootstrap.revisit simply revisits the BeanDeployments which have
been newly found:
private static class DeploymentVisitor {
.....
public Map<BeanDeploymentArchive, BeanDeployment> revisit() {
for (BeanDeploymentArchive archvive :
deployment.getBeanDeploymentArchives()) {
if
(!managerAwareBeanDeploymentArchives.containsKey(archvive)) {
visit(archvive, managerAwareBeanDeploymentArchives,
new HashSet<BeanDeploymentArchive>(), true);
BeanDeployment bd =
managerAwareBeanDeploymentArchives.get(archvive);
bd.createBeans(environment);
bd.deployBeans(environment);
}
}
return managerAwareBeanDeploymentArchives;
}
*************************************************************************************
Issue 3.: invocation of potentially expensive operations on
non-annotated or annotation-relevant classes
The current BeanDeployer simply invokes ProcessAnnotatedType on any
class, w/o checking, if this class is used by any annotation anywhere.
By jandexing all jars and assembling the dispersed Jandex indices during
URLScanning we could reduce the expensive WeldClass conversion.
Original:
11.108 ms - 2.024 inv. org.jboss.weld.bootstrap.BeanDeployer.addClass
Jandex-aware:
4.721 ms - 990 inv. org.jboss.weld.bootstrap.BeanDeployer.addClass
Possible solution:
- 1. URLHandler scans Jandex file locations
protected void addToDiscovered(String name, URL url) {
...
} else if (name.endsWith(JANDEX_IDX)) {
discoveredJandexIndexUrls.add(url);
}
}
- 2. URLScanner assembles all found Jandex indices:
List<Index> jandexIndexes = new ArrayList<Index>();
for (URL jandexUrl : handler.getDiscoveredJandexIndexUrls()) {
jandexIndexes.add(new
IndexReader(jandexUrl.openStream()).read());
}
- 3. BeanDeployer does expensive WeldClass loading only if class is
mentioned in the Jandex indices
- 3.a. first collapse the Jandex-indices to a simple list of class
names:
for (Index jandexIndex : jandexIndexes) {
for (List<AnnotationInstance> aiList :
jandexIndex.getAnnotations().values()) {
for (AnnotationInstance ai : aiList) {
AnnotationTarget at = ai.target();
if (at instanceof ClassInfo) {
jandexAnnotated.add(((ClassInfo)at).name().toString());
} else if (at instanceof MethodInfo) {
jandexAnnotated.add(((MethodInfo)at).declaringClass().name().toString());
} else if (at instanceof MethodParameterInfo) {
jandexAnnotated.add(((MethodParameterInfo)at).method().declaringClass().name().toString());
} else if (at instanceof FieldInfo) {
jandexAnnotated.add(((FieldInfo)at).declaringClass().name().toString());
}
}
}
}
- 3.b. query the aggregated list of Jandex-aware classes
public BeanDeployer addClass(String className) {
Class<?> clazz = loadClass(className);
if (clazz != null && isBeanCandidate(clazz) &&
jandexContains(className)) {
WeldClass<?> weldClass = loadWeldClass(clazz);
Yet unsolved issue is the question, wheter an index is found in the
corresponding Archive or not, so not to enforce Jandexing _all_ jars for
Forge.
Jandexing is very easy though: jandex -m <jar>
The abovementioned solutions have been implemented and produced a
functional and correctly working Forge instance.
AFAICS the solution for issue 2 could be a means to solve the reloading
of plugins too, but I didn't implement and verify this.
The sources are not available online, because the current implementation
is highly fragile and not safe for simple usage - too many changes in
APIs, dependencies, etc.
Comments welcome!
Regards,
Thomas
More information about the forge-dev
mailing list