This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

Bug 268028 - Performance issue in (jpda) MainProjectManager.getMainProject() -> isDependent(lastSelectedProject, current)
Summary: Performance issue in (jpda) MainProjectManager.getMainProject() -> isDependen...
Status: NEW
Alias: None
Product: ide
Classification: Unclassified
Component: Performance (show other bugs)
Version: Dev
Hardware: PC Windows 7
: P3 normal (vote)
Assignee: Tomas Hurka
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-09-14 17:05 UTC by NukemBy
Modified: 2016-09-15 16:20 UTC (History)
0 users

See Also:
Issue Type: DEFECT
Exception Reporter:


Attachments
self-profiler for getMainProject (84.61 KB, image/jpeg)
2016-09-14 17:05 UTC, NukemBy
Details

Note You need to log in before you can comment on or make changes to this bug.
Description NukemBy 2016-09-14 17:05:16 UTC
Created attachment 162050 [details]
self-profiler for getMainProject

Method MainProjectManager.getMainProject() is called rather frequently in when doing Java development in NB. It is invoked per almost each user action to update UI (enabling/disabling of actions) and is invoked 4 times to start a unit test (2 * 2 threads, each invocation is around 120-150ms), see attached screenshot.

Origin of this issue is (https://netbeans.org/bugzilla/show_bug.cgi?id=267969 - Running single simple unit test in debug takes 6+ seconds) and it may happen that it will be resolved by fixing of that issue (https://netbeans.org/bugzilla/show_bug.cgi?id=268027 - FileUtil.normalizedRef cache is cleaned too frequently).

So ... what is going on inside:

        public Project getMainProject () {
            Project lastSelectedProject = lastSelectedProjectRef.get();
            Project current = currentProject.get();
            boolean isMain = isMainProject;
            if (isMain && lastSelectedProject != null &&
                    lastSelectedProject != current  &&
  --->              !isDependent(lastSelectedProject, current )) {  <---
                // If there's a main project set, but the current project has no
                // dependency on it, return the current project.
                return lastSelectedProject;
            } else {
                return current;
            }
        }

implementation of isDependent() collects source roots for both projects and checks that way if one project depends on another

    private static boolean isDependent(Project p1, Project p2) {
        Set<URL> p1Roots = getProjectRoots(p1);
        Set<URL> p2Roots = getProjectRoots(p2);

        for (URL root : p2Roots) {
            Set<URL> dependentRoots = SourceUtils.getDependentRoots(root);
            for (URL sr : p1Roots) {
                if (dependentRoots.contains(sr)) {
                    return true;
                }
            }
        }
        return false;
    }

The problem here is that 
- collection of source roots is rather CPU consuming operation (because of FileUtil.normalizeFile())
- 'isDependent' status actually changes infrequently - when configuration of some project change (and that may not happen during the whole day).

I think "the most correct" solution would be listening to changes in configurations of projects - and updating 'isDependent' status upon real changes. If that is technically hard to implement - caching of 'isDependent' status for few seconds would be sufficient (and refreshing it in 'only one background thread' - currently calculation of 'isDependent' is performed in parallel in each background tread which may need the 'getMainProject()').

For reference - one of the call stacks with MainProjectManager.getMainProject()
-------------------------------------------------------------------------------
"Refresh Editor Context"
    at org.openide.filesystems.FileUtil.normalizeFileCached(FileUtil.java:1638)
    at org.openide.filesystems.FileUtil.normalizeFile(FileUtil.java:1629)
    at org.netbeans.modules.masterfs.filebasedfs.FileBasedURLMapper.getFileObjects(FileBasedURLMapper.java:98)
    at org.netbeans.modules.masterfs.MasterURLMapper.getFileObjects(MasterURLMapper.java:65)
    at org.openide.filesystems.URLMapper.findFileObject(URLMapper.java:213)
    at org.netbeans.api.java.source.SourceUtils.getDependentRoots(SourceUtils.java:790)
    at org.netbeans.api.java.source.SourceUtils.getDependentRoots(SourceUtils.java:770)
    at org.netbeans.modules.debugger.jpda.projectsui.MainProjectManager.isDependent(MainProjectManager.java:173)
--> at org.netbeans.modules.debugger.jpda.projectsui.MainProjectManager.getMainProject(MainProjectManager.java:121)
    at org.netbeans.modules.debugger.jpda.projectsui.RunToCursorActionProvider.shouldBeEnabled(RunToCursorActionProvider.java:210)
    at org.netbeans.modules.debugger.jpda.projectsui.RunToCursorActionProvider.access$300(RunToCursorActionProvider.java:81)
Comment 1 NukemBy 2016-09-15 16:20:48 UTC
Root cause for this bug is here: https://netbeans.org/bugzilla/show_bug.cgi?id=268045