Bug 78860 - [Progress] UIJob uses lots of resources
Summary: [Progress] UIJob uses lots of resources
Status: VERIFIED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 3.0   Edit
Hardware: PC Windows XP
: P2 normal (vote)
Target Milestone: 3.1 M7   Edit
Assignee: Tod Creasey CLA
QA Contact:
URL:
Whiteboard:
Keywords: performance
Depends on:
Blocks:
 
Reported: 2004-11-17 13:22 EST by Jim Adams CLA
Modified: 2005-05-10 15:09 EDT (History)
4 users (show)

See Also:


Attachments
Allocation backtrace of HashSet instances (5.74 KB, text/html)
2004-11-22 14:39 EST, John Arthorne CLA
no flags Details
Patch making the changes John suggests (9.02 KB, patch)
2005-02-14 15:22 EST, Tod Creasey CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Jim Adams CLA 2004-11-17 13:22:07 EST
I had a memory monitor plugin that graphed the available memory over time. I 
wrote it using java.util.Timer. The graph looked flat. I re-implemented it 
using UIJob. The graph became a saw-tooth. The following code illustrates the 
problem.
Create a plugin project. Add the org.eclipse.ui.ActionSets extension and 
choose the Hello World example. In the run method, add the following code:
UIJob j = new UIJob("Tester"){
		    private int count = 10000;

            public IStatus runInUIThread(IProgressMonitor monitor)
            {
                long total = Runtime.getRuntime().totalMemory()/1024;
    			long free = Runtime.getRuntime().freeMemory()/1024;
    			System.out.println("Total= "+total+" free= "+free);
    			if (--count > 0)
    			    schedule(1000);
                return Status.OK_STATUS;
            }};
            j.schedule(1000);

Run the workbench and run the action set. The output should be something like 
this:
Total= # free= #
The free value should not be changing. In this instance it is going down 
continually until there is a GC when it jumps back up.
Comment 1 John Arthorne CLA 2004-11-18 10:26:54 EST
I will profile to see the severity, but some object creations are expected here.
 For the graph to be completely flat it would have to be implemented without any
object creations. Since a UIJob reports progress feedback in the status line,
including animation and messages, this isn't practical.  Even the
System.out.println here creates several garbage objects that will consume memory
(depending on scavenging GC characteristics of the VM being used).
Comment 2 John Arthorne CLA 2004-11-22 14:38:44 EST
Three are three principal areas responsible for most garbage creation when
running this sample program:

1) Many ProgressManager methods use HashSet objects as temporary structures.
Each HashSet contains a HashMap object, and Entry[] object, and an Entry object
for each item that is added. Iterating over HashSets creates several extra
objects.  In several cases a Set is not needed and an ArrayList could be used
instead, avoiding most of the object creations.  Related ProgressManager methods
are: getRootElements, addListenerToFamily, removeListener, busyListenersForJob
(especially the system job case can easily be optimized).  I ran the job 85
times and this created the following objects:

346 HashSet
346 HashMap
368 HashMap$Entry
660 HashMap$KeyIterator
317 HashMap$KeySet

2) ProgressManager.listeners is copied each time someone needs to iterate over
them - typically four times per job execution as it goes through the stages of
the job lifecycle (scheduled, aboutToRun, running, done). Listener list could
instead be copied when a listener is added or removed rather than on iteration
(see org.eclipse.core.runtime ListenerList as an example). This iteration
creates many Object[] instances (see attached profile output).

3) FinishedJobs.getJobInfos creates two Object[] instances and a Comparator,
even  though there are no kept jobs in this case.  Starting the method with
something like the following should eliminate all of these objects in most cases:

if (keptjobinfos.isEmpty())
  return EMPTY_INFOS;

where EMPTY_INFOS is a private static final JobTreeElement[] of size zero.
Comment 3 John Arthorne CLA 2004-11-22 14:39:14 EST
Created attachment 16052 [details]
Allocation backtrace of HashSet instances
Comment 4 John Arthorne CLA 2004-11-22 14:44:26 EST
Moving to UI since the majority of garbage objects are created there.  This is
really only a minor performance issue.
Comment 5 Pat McCarthy CLA 2004-11-30 10:37:55 EST
Comment/Q on this text:
  Since a UIJob reports progress feedback in the status line,
  including animation and messages, this isn't practical. 

How is a UIJob reporting feedback?  Way back in Mx days of 3.0 a UIJob could 
report feedback (the blue progress bar - min the middle of the status line 
back then, moved for a UIJob).  But we don't see such feedback anymore in the 
final 3.0 build.  Even if a UIJob uses the monitor to define work/report 
progress nothing is visualized to the user.  At least my fiddling with the API 
that used to report progress no longer does in 3.0.1.

If we can't see it - should it be done in the code?
Comment 6 John Arthorne CLA 2004-12-03 12:04:55 EST
Re comment #5: Because UIJob executes in the UI thread, the UI is essentially
dead for that period.  No other painting or updates can occur.  For this reason
there is effectively no progress shown for UIJobs.  We don't currently optimize
this case (we actually build all the progres infrastructure but it just never
paints).
Comment 7 Tod Creasey CLA 2005-02-14 15:22:15 EST
Created attachment 17929 [details]
Patch making the changes John suggests
Comment 8 Tod Creasey CLA 2005-02-14 15:24:23 EST
Upping to P2 as I now have a patch
Comment 9 Tod Creasey CLA 2005-03-07 11:57:23 EST
Adding my name to the cc list as we are now tracking performance issues more
closely. Please remove the performance keyword if this is not a performance bug.
Comment 10 Tod Creasey CLA 2005-04-05 15:35:45 EDT
Running the patch with a job rescheduled jobs resulted in the following stats

Patched version
202HashSet
189 HashMap
529 HashMap$Entry
860 HashMap$KeyIterator
133 HashMap$KeySet

3.1 M6
1033HashSet
972 HashMap
603 HashMap$Entry
1796 HashMap$KeyIterator
947 HashMap$KeySet
Comment 11 Tod Creasey CLA 2005-04-05 15:43:49 EDT
Patch released for build >20050405
Comment 12 Nick Edgar CLA 2005-04-12 11:51:48 EDT
I have a few questions/suggestions on the patch:
- suggest using Collections.EMPTY_LIST or Collections.EMPTY_SET for the empty
collection, since new ArrayList(0) is not immutable
- listeners are being notified while holding a lock, which can potentially lead
to deadlocks
- instead of:
            Iterator families = familyListeners.keySet().iterator();
            if(!families.hasNext())
            	return EMPTY_COLLECTION;
suggest using:
            if(families.isEmpty())
            	return EMPTY_COLLECTION;
since it creates less garbage.

Also, should consider using the JFace ListenerList, or copying the one from
runtime.  They have different performance characteristics.  The one in runtime
copies on write, the JFace one copies on read, but creates no garbage if empty.
Comment 13 Tod Creasey CLA 2005-04-15 15:32:20 EDT
Thanks for the tips Nick - I'll apply them now. The problem with using the 
JFace listener lists is that the JFace one is not synchonized. See Bug 91598 for
a discussion of this issue.
Comment 14 Tod Creasey CLA 2005-04-15 15:33:40 EDT
Suggested changes released for build >20050415
Comment 15 Tod Creasey CLA 2005-05-10 15:09:36 EDT
Verified in 20050510