Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [cross-project-issues-dev] Improve Startup Performance of Eclipse

Ed,

 

We’ve been looking into this kind of optimization inside Papyrus some time ago. There are several things that must be taken into account (And which at one point prevented us from going further). You already mentioned some of them:

 

-          Some bundles assume that they are being loaded in the UI Thread and will fail otherwise

o   Doing preloading of your own bundle in a Job will eventually start some required bundles in your worker thread. If these bundles don’t expect to be started on a worker, they will fail, and failing during startup is really bad, as it cannot be recovered (Not without restarting Eclipse)

o   This is probably not the case in general (As your dependencies need to be started before yourself), but when manipulating extension points it will happen (And other kinds of dynamic class loading – we do that a lot with models referencing Java classes).

-          One JFace Preference-related class wasn’t thread safe [1] (It has been fixed very recently in Neon), so many bundles use a Display.syncExec() to workaround the issue. Doing so in a Worker Thread during IDE startup will most certainly cause a deadlock

o   Solution: never call Display.syncExec() in your Activator (Directly or indirectly). But then again, we have to ensure that all bundles respect this rule, because you never know when/by which bundle/in which thread your own bundle will be started (e.g. Papyrus might depend on JDT and start in a worker thread. JDT is loaded in a worker thread and loads extensions ; a foreign plug-in contributes something to JDT without expecting to be started in a Worker Thread -> potential crash or deadlock)

-          Property Testers are often declared in UI Bundles and are used with forcePluginActivation

o   UI Bundles have a lot of dependencies, so many plug-ins are being started

o   Solution: Property Testers should be declared in core plug-ins (As much as possible), and used in UI plug-ins. UI plug-ins will not need to be started until the action is executed (Rather than tested for enablement)

o   This is usually easy to solve (No big risk in this case), but we need to be aware of this. Startup Property Testers are usually forgotten when looking for startup plug-ins

 

So in general, the risk is not so much that your very own bundle is at risk, but that it indirectly loads one that doesn’t expect to be loaded in your context. And it’s very difficult to anticipate/identify these cases (Yet they have very dramatic consequences). Once identified, however, they are usually easily fixed

 

[1] 342711: [JFace] PreferenceConverter is not thread-safe

https://bugs.eclipse.org/bugs/show_bug.cgi?id=342711

 

Regards,
Camille

 

De : cross-project-issues-dev-bounces@xxxxxxxxxxx [mailto:cross-project-issues-dev-bounces@xxxxxxxxxxx] De la part de Ed Merks
Envoyé : mercredi 9 décembre 2015 15:53
À : Cross project issues <cross-project-issues-dev@xxxxxxxxxxx>
Objet : [cross-project-issues-dev] Improve Startup Performance of Eclipse

 

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. 

  1. 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.
  2. 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?


Back to the top