I've been doing
some performance analysis work looking at the start
time associated with the JEE product. I'm posting
this note as HTML with screen captures, so I'm not
sure the form in which it will arrive for all
recipients. As such, I'll mark each insertion point
with #<number> in case the images arrive as
attachments, presumably in the order I have them in
this note. Please use
https://bugs.eclipse.org/bugs/show_bug.cgi?id=484024
for followup discussions.
This work was driven from a self-hosted launch in
which I could prototype changes to Equinox and the
Platform in the workspace along with the full JEE
product in the target platform. (Yes, I used Oomph to
set that up easily.) I've already opened a number of
Bugzillas with Gerrit contributions for various small
performance improvements based on the measurements
I've done. Thanks to the reviewers for spending time
on them!
https://bugs.eclipse.org/bugs/show_bug.cgi?id=482642
https://bugs.eclipse.org/bugs/show_bug.cgi?id=482643
https://bugs.eclipse.org/bugs/show_bug.cgi?id=482666
https://bugs.eclipse.org/bugs/show_bug.cgi?id=482673
https://bugs.eclipse.org/bugs/show_bug.cgi?id=483986
https://bugs.eclipse.org/bugs/show_bug.cgi?id=481705
https://bugs.eclipse.org/bugs/show_bug.cgi?id=482680
https://bugs.eclipse.org/bugs/show_bug.cgi?id=482698
https://bugs.eclipse.org/bugs/show_bug.cgi?id=483989
https://bugs.eclipse.org/bugs/show_bug.cgi?id=483991
One of the premises underlying most efforts to make
the UI startup faster has been to be more lazy. One
aspect of that is to avoid starting bundles,
especially bundles that are expensive to start. If we
trace profile the time between showing the workspace
prompt and the time when the IDE is finally displayed,
we can measure that important aspect. Here's what we
see happening on the main (display/UI) thread starting
up JEE: #1

An important thing to note is that starting a bundle
is not in and of itself expensive. It's what else
that bundle does during startup that can be
expensive. Especially important is what it does on
the main thread because that's the bottleneck for
displaying the IDE to the user. It's also important
to note that many bundles inevitably need to be
started, forced by things such as contributing a
property tester, one of the things to help the IDE be
more lazy. (Or is there a way to contribute a tester
without a bundle start?)
While bundle startup isn't necessarily expensive,
loading many classes is definitely expensive, again
especially if it's on the main thread. Here we see a
partial picture (class initialization can be expensive
too) of the class loading involved on the main thread:
#2

Here's the class initialization cost on the main
thread: #3

EMF package initialization loads all interfaces of the
model, and loads all models which it depends, so it's
quite expensive, primarily because of class loading.
An alternative approach one might consider is rather
than being more lazy, is being more eager, as long as
that eagerness is done on a different thread that can
work in parallel while the main thread is busy doing
the most important thing, that is, displaying the IDE
to the user.
I've prototyped a very rough design in
org.eclipse.ui.internal.ide.application.IDEApplication.start(IApplicationContext)
that uses a thread pool to start bundles that must
necessarily start. Those changes (details described
below) have a rather dramatic impact on bundle start
times on the main thread: #4

Also if we look at class loading, that's also
dramatically reduced: #5

As is the cost of class initialization: #6

If I instrument org.eclipse.ui.internal.Workbench to
do wall clock measurements (goodness knows one must
take profile measurements with a grain of salt), and
compare the normal startup times
clinit=1218
init=6
start=4327
earlystart=453
total=5551
with startup that does eager background initialization
clinit=1469
init=4
start=3539
earlystart=2
total=5012
we really do see a 10% improvement. These labels mean
the following:
- clinit - the time between when the user hits OK
on the open workspace dialog and the time when the
constructor
org.eclipse.ui.internal.Workbench.Workbench(Display,
WorkbenchAdvisor, MApplication, IEclipseContext)
has finished calling super()
- init - the time between clinit and when when
org.eclipse.ui.internal.Workbench.runUI() is
called
- start - the time it takes to create the
workbench in order to display it
- earlystart - the time spent early starting all
the bundles registered with IStartup
- total - the total (note that the UI is already
visible and painted before early start begins and
that early start happens on a background thread).
I could imagine the platform core runtime providing an
extension point for eager early startup that would be
used to perform early eager initialization on multiple
background threads. It could support two different
phases of eagerness.
- Initialization that can be performed before the
workspace location is known, i.e., that can happen
before ResourcesPlugin.start is called.
Unfortunately very little can be done at this
point because most things depend on the workspace
location being established.
- Initialization that can be performed once the
workspace location is known.
The former is useful for plugins that have no direct
or indirect core.resources dependencies; these can be
kicked off while the workspace prompt is showing. The
later is useful for kicking off initialization as soon
as the user has chosen the workspace location, at
which point the UI thread starts the processing to
bring up the IDE.
These are relatively expensive things and can be done
while the workspace prompt is being shown, i.e.,
ICU4J's expensive class initialization:
{
Map<String, String> bundles = new
LinkedHashMap<>();
bundles.put("com.ibm.icu",
"com.ibm.icu.text.Collator#getInstance()");
//$NON-NLS-1$//$NON-NLS-2$
queue.put(bundles);
}
{
Map<String, String> bundles = new
LinkedHashMap<>();
bundles.put("com.ibm.icu",
"com.ibm.icu.text.DateFormat#getDateTimeInstance()");
//$NON-NLS-1$//$NON-NLS-2$
queue.put(bundles);
}
The basic idea is that we reflectively call the
specified methods or reflectively access the specified
fields. The methods above were chosen based on the
following observation: #7

When offloaded to a background thread, their cost is
gone from the main thread: #8

These are expensive things that can be done in
parallel before the UI needs them:
{
Map<String, String> bundles =
new LinkedHashMap<>();
bundles.put("org.eclipse.core.resources",
"org.eclipse.core.resources.ResourcesPlugin");
//$NON-NLS-1$//$NON-NLS-2$
queue.put(bundles);
}
Thread.yield();
{
Map<String, String> bundles =
new LinkedHashMap<>();
bundles.put("org.eclipse.e4.ui.model.workbench",
//$NON-NLS-1$
"org.eclipse.e4.ui.model.application.impl.ApplicationPackageImpl#eINSTANCE");
//$NON-NLS-1$
bundles.put("org.eclipse.emf.ecore",
"org.eclipse.emf.ecore.EcorePackage#eINSTANCE");
//$NON-NLS-1$//$NON-NLS-2$
bundles.put("org.eclipse.emf.ecore.xmi", //$NON-NLS-1$
"org.eclipse.emf.ecore.xmi.XMIPlugin$Implementation$Activator");
//$NON-NLS-1$
queue.put(bundles);
}
Thread.yield();
{
Map<String, String> bundles =
new LinkedHashMap<>();
bundles.put("org.eclipse.pde.core",
"org.eclipse.pde.internal.core.PDECore");
//$NON-NLS-1$//$NON-NLS-2$
queue.put(bundles);
}
Thread.yield();
{
Map<String, String> bundles =
new LinkedHashMap<>();
bundles.put("org.eclipse.jdt.core",
"org.eclipse.jdt.core.JavaCore"); //$NON-NLS-1$
//$NON-NLS-2$
queue.put(bundles);
}
Thread.yield();
{
Map<String, String> bundles =
new LinkedHashMap<>();
bundles.put("
org.eclipse.mylyn.commons.net",
"org.eclipse.mylyn.commons.net.WebUtil#init()");
//$NON-NLS-1$//$NON-NLS-2$
queue.put(bundles);
}
Thread.yield();
{
Map<String, String> bundles =
new LinkedHashMap<>();
bundles.put("org.eclipse.team.core",
"org.eclipse.team.internal.core.TeamPlugin");
//$NON-NLS-1$//$NON-NLS-2$
queue.put(bundles);
}
The above logic is what results in the improved
performance shown in the fourth screen capture (#4)
earlier in this note.
One thing that hinders more eager parallel
initialization is that many UI bundles assume they
will be started on the main thread, i.e., they fail
with SWT invalid thread access exceptions if one
attempts to start them on a background thread. E.g.,
PDE's, JDT's, and Mylyn Tasks' UI plugins cannot be
initialized on a background thread, (though likely
they could offload significant costs to worker
threads).
Another issue is that just starting a bundle doesn't
necessarily involve loading many of its classes, which
is generally a good thing, but it's hard to drive
more eager class loading as much as would be
beneficial for this prototyping effort. I did try
loading all classes in each bundle being eagerly
loaded, but this is just way more classes than are
actually needed and, in the end, hurts overall
performance.
Based on the above observations, one of the things I
am changing in Oomph is to move logic that previously
was kicked off from
org.eclipse.oomph.setup.ui.SetupUIPlugin.Implementation.start(BundleContext)
to be kicked off instead from
org.eclipse.oomph.setup.ui.EarlyStartup.earlyStartup()
so it happens after the IDE is displayed to the user.
Also, with the following simple changes, I could also
force much of the class loading to happen early on the
worker thread:
static void performStartup()
{
if (SetupTaskPerformer.REMOTE_DEBUG ||
SynchronizerManager.ENABLED ||
RecorderManager.INSTANCE != null)
{
// This is only to force class loading on a
background thread.
}
I.e., before dispatching work to the main thread:
if (!PropertiesUtil.isProperty(PREF_HEADLESS))
{
final Display display = Display.getDefault();
display.asyncExec(new Runnable()
{
Certainly other UI bundles could benefit from this
approach, e.g., the Mylyn Task UI bundle, without need
for any architectural changes to Eclipse.
One thing I noticed wondering why PDEPlugin was
started so early was to noticed that it's called from
org.eclipse.ui.internal.Workbench.initializeDefaultServices()
which has this code:
StartupThreading.runWithoutExceptions(new
StartupRunnable() {
@Override
public void runWithException() {
// this currently instantiates all
players ... sigh
sourceProviderService.readRegistry();
ISourceProvider[] sp =
sourceProviderService.getSourceProviders();
for (int i = 0; i < sp.length; i++)
{
evaluationService.addSourceProvider(sp[i]);
if (!(sp[i] instanceof
ActiveContextSourceProvider)) {
contextService.addSourceProvider(sp[i]);
}
}
}
})
Note the "sigh". And yes indeed, in
org.eclipse.ui.internal.services.WorkbenchServiceRegistry.getSourceProviders()
it creates instances of all the source providers and
PDE is one of those. It provides
org.eclipse.pde.internal.ui.views.imagebrowser.ActiveImageSourceProvider
so the single most expensive bundle to start in JEE
must be started early, on the UI thread.
A general take away is to set a breakpoint in your UI
bundle activators to see when they are called. Often
this will be unavoidable, but what you do when the
start method is called should be more carefully
considered. In particular, ask yourself, what do I
really need to do on the UI thread as soon as my
bundle starts? Not so much hopefully. And even if
it must be done on the UI thread, can it be deferred
until IStartup.earlyStart() is called? You can
always dispatch a runnable to the display thread when
early start is called, so that the user at least has a
pretty IDE to look at and click on while you make it
better in the background. Better still is to do as
much work as possible or all work in your early start
method, or to delegate to a Job for that purpose.
Your goodness can wait, but the user should not.
It would probably be good to do this for
org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin. It
has code like this:
// XXX reconsider if this is necessary
public static class TasksUiStartup implements
IStartup {
public void earlyStartup() {
// ignore
}
}
No, this isn't necessary because you contribute
org.eclipse.mylyn.internal.tasks.ui.util.TaskPropertyTester
so you will be started on the UI thread very early.
What could you defer to earlyStart()? What could you
do on the worker thread in earlyStart() instead of in
the display thread?