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 ofEclipse

Ed, thanks for looking into this. My understanding is that we (Mylyn) have deliberately avoided relying on IStartup extensions because the user can disable them and that would result UI contributions showing up without having things properly initialized (the code you cited is 7 years old and I don't know why it is there). Is using IStartup better than scheduling a Job to do the initialization?

Sam


--
Sam Davis
Software Engineer, Tasktop Dev
Committer, Eclipse Mylyn
http://tasktop.com

On Tue, Dec 15, 2015 at 11:02 AM, John Arthorne <John_Arthorne@xxxxxxxxxx> wrote:
Ed, thanks for this extensive summary of the intractable problem of improving startup performance ;)  A few minor comments:
 
 - We have been hitting similar issues with Orion and server side applications, where our problem has not been the raw time taken to perform startup work, but the relative timing of when to perform startup work in order to minimize impact. A downside of lazy activation is you often end up with little control over when things are started, and sometimes slightly more eager initialization at an appropriate time has advantages.
 
 - For bundle startup, we have definitely been taking the approach of removing all heavy work from Bundle start methods, preferring instead to fork a job from the start method to do most of the heavy lifting. This has the advantage you noted of taking initialization work off the critical path, and just as importantly avoids tying up OSGi class loading locks which reduces contention with work in other threads.
 
 - I think the trick with "eager activation" is that this is not a decision that can be made at a local bundle level. Every bundle author will probably think their bundle deserves eager activation, but you just have to look at the Eierlegende Wollmilchsau to see the global impact of that kind of local optimization. However, if you are using for example the Java EPP package, it probably makes sense to eagerly initialize the Java model, and similarly with CDT initialization when using the C/C++ EPP package. I think an ideal approach here would be a *mechanism* that allows a bundle to participate in eager activation in a controlled way (on the correct thread, etc), but have the *policy* of which bundles to actively start be made at the level of a product configuration. This is a classic case of Jeff McAffer's mantra about the importance of separating policy from mechanism in an open-ended modular system.
 
John
 
----- Original message -----
On Wed, Dec 9, 2015 at 3:53 PM, Ed Merks <ed.merks@xxxxxxxxx> wrote:
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?

_______________________________________________
cross-project-issues-dev mailing list
cross-project-issues-dev@xxxxxxxxxxx
To change your delivery options, retrieve your password, or unsubscribe from this list, visit
https://dev.eclipse.org/mailman/listinfo/cross-project-issues-dev
 
 
--
Eclipse Platform UI and e4 project co-lead
CEO vogella GmbH

Haindaalwisch 17a, 22395 Hamburg
Amtsgericht Hamburg: HRB 127058
Geschäftsführer: Lars Vogel, Jennifer Nerlich de Vogel
USt-IdNr.: DE284122352
Fax (032) 221739404, Email: lars.vogel@xxxxxxxxxxx, Web: http://www.vogella.com
_______________________________________________
cross-project-issues-dev mailing list
cross-project-issues-dev@xxxxxxxxxxx
To change your delivery options, retrieve your password, or unsubscribe from this list, visit
https://dev.eclipse.org/mailman/listinfo/cross-project-issues-dev
 


_______________________________________________
cross-project-issues-dev mailing list
cross-project-issues-dev@xxxxxxxxxxx
To change your delivery options, retrieve your password, or unsubscribe from this list, visit
https://dev.eclipse.org/mailman/listinfo/cross-project-issues-dev


Back to the top