Bug 475604 - DeclareImages in WorkbenchImages hits filesystem repeatedly
Summary: DeclareImages in WorkbenchImages hits filesystem repeatedly
Status: RESOLVED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: IDE (show other bugs)
Version: 4.6   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: 4.17 M1   Edit
Assignee: Alex Blewitt CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on: 477391 483865
Blocks: 563542
  Show dependency tree
 
Reported: 2015-08-21 14:09 EDT by Alex Blewitt CLA
Modified: 2020-06-08 10:38 EDT (History)
8 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Alex Blewitt CLA 2015-08-21 14:09:48 EDT
The org.eclipse.ui.internal.Workbench class has a method:

    private final static void declareImage(String key, String path,
            boolean shared) {
        URL url = BundleUtility.find(PlatformUI.PLUGIN_ID, path);

This is used to load images in a static initialiser with many different arguments:

    private final static void declareImages() {
		// Overlays
		declareImage(ISharedImages.IMG_DEC_FIELD_ERROR, PATH_OVERLAY + "error_ovr.png", true); //$NON-NLS-1$
		declareImage(ISharedImages.IMG_DEC_FIELD_WARNING, PATH_OVERLAY + "warning_ovr.png", true); //$NON-NLS-1$
...

It's called from the workbench startup: Workbench::init method:

		activityHelper = ActivityPersistanceHelper.getInstance();
		StartupThreading.runWithoutExceptions(new StartupRunnable() {

			@Override
			public void runWithException() {
				WorkbenchImages.getImageRegistry();
			}
		});

This causes a lot of file traffic for all images even when they're not necessary.
Comment 1 Alex Blewitt CLA 2015-08-21 14:13:31 EDT
The reason why it does this, by the way, is because it delegates to a 'find' method that attempts to do filesystem resolution by name (specifically, the FindSupport::find method)

		// Worry about variable substitution
		IPath rest = path.removeFirstSegments(1);
		if (first.equalsIgnoreCase("$nl$")) //$NON-NLS-1$
			return findNL(b, rest, override, multiple);
		if (first.equalsIgnoreCase("$os$")) //$NON-NLS-1$
			return findOS(b, rest, override, multiple);
		if (first.equalsIgnoreCase("$ws$")) //$NON-NLS-1$
			return findWS(b, rest, override, multiple);
		if (first.equalsIgnoreCase("$files$")) //$NON-NLS-1$
			return null;

The net result is that a given file does a lot of lookups to determine if the files are there. Admittedly this is likely to use the list of a JAR file which may be cached but most of the time it's not necessary.

Note that the result of this is an ImageDescriptor which is a pointer to the file anyway; it doesn't load any data until the getData is called anyway, which means the metadata lookup for the files can be excessive.
Comment 2 Alex Blewitt CLA 2015-08-21 14:17:09 EDT
The solution is to use a DeferredImageDescriptor that just represents the file name, and then use the getImageData call to do the Platform.find() lookup at that point.

It would mean that an error in specifying the image wouldn't be visible until the image file data was requested (right now, an error would be displayed if it tried to load any of them I think - or at least, a red image square) but this could be mitigated by tests.
Comment 3 Alex Blewitt CLA 2015-08-21 14:40:13 EDT
Admittedly, on my test system the initializeImages() only takes ~25ms in total (so there's not likely to be a huge win) but it's on the critical path for startup.

To put things in perspective, this change:

  // URL url = BundleUtility.find(PlatformUI.PLUGIN_ID, path);
  URL url = new URL("platform:/plugin/" + PlatformUI.PLUGIN_ID + "/" + path);

in WorkbenchImages::declareImage

drops the time from ~25ms to ~10ms. Part of that's due to the reduction in method calls, but mainly it's to do with the fact we're not hitting bundle.getEntry() on a whole bunch of those file names.

I'm not suggesting this is necessarily the best fix (we don't support $os$ or similar with the above hack) but just documenting the kind of improvement that might be possible.
Comment 4 Alex Blewitt CLA 2015-08-21 14:47:24 EDT
BTW when I was testing against the .jar the declareImages was taking ~50ms to run. I suspect that's because bundle.getEntry() has to do more work when it's a JAR, but when running in debug in the workspace it's just mapping the calls down to File calls which thus can be executed faster (it's relying on the OS to navigate the filesystem instead of Java which will navigate the JAR entry)
Comment 5 Brian de Alwis CLA 2015-08-26 16:36:33 EDT
Deferring would also make sense if providing replacement images in a fragment…
Comment 6 Alex Blewitt CLA 2015-09-03 16:11:36 EDT
I had a proof-of-concept for the deferred images using a wrapper:

    private final static void declareImage(String key, String path,
            boolean shared) {
        URL url = BundleUtility.find(PlatformUI.PLUGIN_ID, path);
        ImageDescriptor desc = ImageDescriptor.createFromURL(url);
        declareImage(key, desc, shared);
    }

replaced with something along the lines of:

    private final static void declareImage(String key, String path,
            boolean shared) {
        ImageDescriptor desc = new DefferedImageDescriptor(String bundle, String path);
        declareImage(key, desc, shared);
    }
static class DefferedImageDescriptor {
  DefferedImageDescriptor(String bundle, String path) {
    this.bundle = bundle;
    this.path = path;
  }
  ImageData getImageData() {
    return ImageDescriptor.createFromURL(BundleUtility.find(PlatformUI.PLUGIN_ID,path).getImageData();
  }
}

That way the lookup is deferred until the getImageData is called.
Comment 7 Alex Blewitt CLA 2015-09-03 16:14:27 EDT
BTW I note that JFace has some similar lookups in JFaceResources.initializeImages and JFaceResources.declareImage

	private static final void declareImage(Object bundle, String key, String path, Class<?> fallback,
			String fallbackPath) {

		ImageDescriptor descriptor = null;

		if (bundle != null) {
			URL url = FileLocator.find((Bundle) bundle, new Path(path), null);
			if (url != null)
				descriptor = ImageDescriptor.createFromURL(url);
		}

		// If we failed then load from the backup file
		if (descriptor == null)
			descriptor = ImageDescriptor.createFromFile(fallback, fallbackPath);

		imageRegistry.put(key, descriptor);
Comment 8 Alex Blewitt CLA 2015-09-03 16:15:32 EDT
(Or to put it another way, a deferred JFace image lookup class might be worth putting in JFace, and then use that standard in both JFace and other dependent classes)
Comment 9 Lars Vogel CLA 2015-09-14 13:04:19 EDT
(In reply to Alex Blewitt from comment #8)
> (Or to put it another way, a deferred JFace image lookup class might be
> worth putting in JFace, and then use that standard in both JFace and other
> dependent classes)

+1. Can you please create a new bug for that and potentially provide an implementation for it?
Comment 10 Alex Blewitt CLA 2015-09-14 13:09:02 EDT
Yeah, it's on the backlog ...
Comment 11 Lars Vogel CLA 2015-09-14 13:12:21 EDT
(In reply to Lars Vogel from comment #9)
> (In reply to Alex Blewitt from comment #8)
> > (Or to put it another way, a deferred JFace image lookup class might be
> > worth putting in JFace, and then use that standard in both JFace and other
> > dependent classes)
> 
> +1. Can you please create a new bug for that and potentially provide an
> implementation for it?

Adding Stefan, as he might also be interested in this.
Comment 12 Alex Blewitt CLA 2015-09-14 14:17:15 EDT
Created bug 477391 for the JFace enhancement.
Comment 13 Alex Blewitt CLA 2015-10-06 12:55:12 EDT
No plans for me to work on this at the moment, re-assigning to inbox.
Comment 14 Lars Vogel CLA 2015-10-07 04:09:38 EDT

*** This bug has been marked as a duplicate of bug 477391 ***
Comment 15 Stefan Xenos CLA 2015-12-08 16:51:42 EST
This depends on bug 477391, it's not a dupe. I'd like to give Sergey the appropriate credit for fixing it.
Comment 16 Stefan Xenos CLA 2015-12-09 12:29:24 EST
Reopened since bug 477391 was reverted.
Comment 17 Eclipse Genie CLA 2020-05-30 14:45:30 EDT
New Gerrit change created: https://git.eclipse.org/r/163890
Comment 18 Alex Blewitt CLA 2020-05-30 16:14:41 EDT
I saw the issue with the Pin Editor, so made a change to the MenuHelper to fix the issue, and verified that that specific menu item is fixed. I don't know if there might be any other knock-on effects that I'm unsure about.

https://git.eclipse.org/r/#/c/163890/1/bundles/org.eclipse.ui.workbench/Eclipse+UI/org/eclipse/ui/internal/menus/MenuHelper.java
Comment 19 Lars Vogel CLA 2020-06-05 06:13:05 EDT
I guess this may make a bigger impact on Windows, AFAICS file system access is super slow on Windows.

Alex, please set milestone and assign bug to you.
Comment 21 Lars Vogel CLA 2020-06-08 10:38:41 EDT
Thanks, Alex.