Bug 243376 - [incremental] Incremental compilation in a scenario with a large number of projects
Summary: [incremental] Incremental compilation in a scenario with a large number of pr...
Status: RESOLVED FIXED
Alias: None
Product: AspectJ
Classification: Tools
Component: Compiler (show other bugs)
Version: DEVELOPMENT   Edit
Hardware: PC Mac OS X - Carbon (unsup.)
: P3 normal (vote)
Target Milestone: 1.6.2   Edit
Assignee: aspectj inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2008-08-06 22:54 EDT by Andrew Clement CLA
Modified: 2012-04-03 15:50 EDT (History)
1 user (show)

See Also:


Attachments
Patch to ensure that auto build does not occur after a full build. (15.60 KB, patch)
2008-08-09 00:21 EDT, Andrew Eisenberg CLA
no flags Details | Diff
Reworked the test on the previous patch (17.74 KB, patch)
2008-08-11 13:38 EDT, Andrew Eisenberg CLA
aclement: iplog+
Details | Diff
Turn on incremental build optimizations by default (6.42 KB, patch)
2008-10-10 17:46 EDT, Andrew Eisenberg CLA
aclement: iplog+
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Andrew Clement CLA 2008-08-06 22:54:09 EDT
We are working on a scenario that has 50 or so projects.  With only a couple made AspectJ projects and the rest being normal Java projects, the development experience is OK.  When they are all converted to AspectJ projects, and the aspects from the base AJ projects (which provide FFDC and Trace) are applied across *all of them* (by inclusion of those base projects on the aspectpath of all the other projects), the development experience slows to a crawl.

Observations so far:

- some of these projects include resources and incremental compilation treatment of resources hasn't been optimized.
- AJDT may be rebuilding the element map (ajdts version of the structure model) when not necessary

I've developed an AJ testcase for the first scenario and have a fix that seems to be behaving.
Comment 1 Andrew Eisenberg CLA 2008-08-09 00:21:37 EDT
Created attachment 109585 [details]
Patch to ensure that auto build does not occur after a full build.

I went pretty far into the bowels looking at this thing and here is the first patch of possibly several that will help make the AJDT experience of working many projects more pleasant.

When a project A has the binary output folder of another project B on its aspect path, a second auto build occurs after every full build.  The reason is that during a full build, the binary output folder of project B is wiped and then recreated.  So that after the build, project A sees that the output folder of B (on its Java build path) is new and this triggers an auto build.  This is an issue that comes from the Eclipse workspace and is out of the control of AJDT.

Apparently, this must have been happening since we started putting aspect path elements on the Java build path, but no one had noticed it.

The way around this is to use project references instead of binary folder references (this feature has only been available since AJDT 1.5.3).

This patch includes a test case to ensure that the double build does not happen when using a project reference.  Also, I made a few fixes to the way that projects are added to the aspect and in paths so that build errors do not occur when a project's out folder does not exist.
Comment 2 Andrew Eisenberg CLA 2008-08-11 13:38:54 EDT
Created attachment 109689 [details]
Reworked the test on the previous patch

This patch is a slight reworking of the test included in the previous version of the patch.  Now, the test executes the project and checks the output to ensure that the aspect has been properly applied.
Comment 3 Andrew Clement CLA 2008-08-11 17:51:27 EDT
can't seem to apply the part of the patch that changes UITestCase - was it created against HEAD?
Comment 4 Andrew Clement CLA 2008-08-11 19:16:12 EDT
managed to get the patch forced in.  Now committed.  I had to change the test slightly so it was OK on windoze
Comment 5 Andrew Clement CLA 2008-08-14 16:36:59 EDT
Addressing the resource copying mentioned in my first post on the bug. The compiler was deleting resources and recopying them each time (yikes!) - so now lets have AJDT handle the incremental changes and AspectJ will do the up front copy on first fresh build.  Changing this behaviour causes two aspectj tests to fail relating to resource copying, she mention of this bug in MoreOutputLocationManagerTests.

changes committed.
Comment 6 Andrew Clement CLA 2008-08-18 19:55:33 EDT
I decided to measure the AsmHierarchyBuilder code to see if that was a problem.  It has never been optimized but I had to use a nanosecond timer rather than millisecond to actually get any numbers out.  Typically it is taking less than 1ms per file.  When building the entire shadows project (our 'example' AspectJ project) - which is a copy of the JDT compiler with aspects applied - it spent just 1s of the full build time in AsmHierarchyBuilder.  And on an incremental build of one file, typically 1ms or less.  The target for incremental compilation should be about 500ms at worst - so 1ms indicates there are other places to look right now.  
Comment 7 Andrew Clement CLA 2008-08-22 22:01:42 EDT
I've been profiling incremental builds.  I'm using my main AspectJ demo project (shadows) and making a whitespace change to CompilationEngine.  Profiling revealed the hotspots I expected:

org.aspectj.asm.internal.AspectJElementHierarchy.findElementForHandle(IProgramElement, String) (15% 1.3s) called from AsmManager.removeRelationsTargettingThisType
File.getCanonicalFile() - called from ConfigParser.makeFile()
File.lastModified() - called from AjState.getModifiedFiles(long) (called through getFilesToCompile(boolean))
AbstractList.iterator() - called through AspectJElementHierarchy.findElementForHandle
AbstractList$Iter.next() - called through AspectJElementHierarchy.findElementForHandle
ZipFile$3.nextElement() - called through ClasspathJar.isPackage(String)

Of the about 5s to recompile the file we spent 20% of the time messing about with the model.  After adding some debug into the code I observed the following usage of the handle>programelement cache.

On a full build of the entire project the cache is accessed 3331 times, of which 848 are misses and 2499 are hits.

On an incremental white space change build it is accessed 13499 times (!!) of which 13631 hits and 456 misses.

This excess is due to the sub-optimal incremental model repair code that I'm now looking through.

Other observations so far are:
- the handles (Strings) are very long
- it is not exploited that the handle to an element is an extension of the handle to its parent element (so we can make intelligent navigation decisions when walking down a hierarchy rather than blindly looking at every node all over the tree)

There is a lot of scope here to speed things up and reduce memory usage.
Comment 8 Andrew Clement CLA 2008-08-22 22:13:26 EDT
oops, typo, should read:

13499 cache accesses on one incremental build 13163 hit/456 miss
Comment 9 Andrew Clement CLA 2008-08-23 00:21:45 EDT
- Changed to exploit knowledge that handles for parents are simply shorter variants of the handles for children - prevents exploration of the entire model for a handle.

- Removed unnecessary cache accesses (still more to do)

- optimized relationship implementation
Comment 10 Andrew Clement CLA 2008-08-23 00:41:46 EDT
I've put these changes into a private build to try it.  Typical times (in ms) for the:

17:07:33 Timer event: XXXXms: Total time spent in AJDE

line in the AJDT event trace were (before my changes):

3984/5125/4015

Now the times are:

2985/3828/3313/3203

So quite a reduced time - and more to come.  I'd like to see it halved at least.
Comment 11 Andrew Clement CLA 2008-08-23 20:41:27 EDT
More observations.  We really need to address this crappy interface between AJDT and the compiler.  At least it doesn't write a file out now to communicate between the two, but what it does isn't much better than that.  On every build the compiler builds a brand new object to hold the configuration of the compile that AJDT wants to do, it does this by asking AJDT for the inpath/classpath/aspectpath/options/sourcefiles/etc/etc/etc.  Even if only one source file has changed it asks for everything and reprocesses it.  Particularly annoying is that the compiler proceeds to go through all the source files to see which one has changed, after AJDT already did that (it just didn't tell AspectJ what it found out).  This means in the case of me rebuilding shadows, my white space change leads to the compiler processing the 1146 source files again into a config object (when it is the same set of sources as before) and then asking all of them if their lastmodtime has changed - rather than AJDT (which has just processed the delta of eclipse changes) telling me the one file that changed.  lastModifiedTime() is not a cheap operation, nor is getCanonicalFile and both of those are called 1146 times in my case.  

I know these numbers are worse on larger projects (and this bug is intended to cover small projects) - but I think fixing this will benefit every kind of project so is worth doing.  (this is all to reduce the 'time spent in AJDE' message - i've still not looked into any time wasted in AJDT).
Comment 12 Andrew Eisenberg CLA 2008-08-24 00:51:52 EDT
There's a lot of room for improvement on the AJDT side as well.  After every build, AJDT asks the compiler for the new project model and discards the old one (actually, it saves the previous model to do model comparisons).  Not every part of the model needs to be recreated, but it is tricky to tease out what does and does not.

I think spending a week or so to clean up this interface can save a lot of time on both sides.

I don't know if this will be appropriate for the 1.6.0 release, though.
Comment 13 Andrew Clement CLA 2008-08-26 16:15:43 EDT
continuing on from comment 10, where the times were:

2985/3828/3313/3203

I changed the ICompilerConfiguration interface so that it could return the list of modified files (if the caller knew them).  I also changed AjBuildManager.performCompilation to maintain a name environment rather than trash it between compilations (need to check ongoing memory consumption of this mechanism).  Times reduced to:

1359/2281/1281/1313

And seeing as how the file I'm white space changing is affected by aspects and contains 5 inner types that also get woven, that's not bad.  I then switched to whitespace changes on a file in the same project that is not affected by aspects: CodeSnippetEnvironment

With current 1.6 dev build:

Time spent in AJDE: 1704/1750/1718/1735

With all the changes I'm proposing:

Time spent in AJDE: 109/94/79/93/78

Hmmm - slight improvement...

It will take a little while to fully integrate my changes, there are some necessary AJDT changes and currently it doesn't notice if other features of the project change (like the classpath) - but that can be fixed and it would be nice to move to a model where AspectJ can ask AJDT "has anything interesting changed?" rather than always ask for all information and process it.

When the AJDE times are this low, the full info is:

13:8:56 Build kind = AUTOBUILD
13:8:56 Project=shadows.org.eclipse.jdt.core, kind of build requested=Incremental AspectJ compilation
13:8:56 Considering this as a changed file: C:\asc\ws\debugws-ajdt16\shadows.org.eclipse.jdt.core\eval\org\aspectj\org\eclipse\jdt\internal\eval\CodeSnippetEnvironment.java
13:8:56 build: Examined delta - source file changes in required project shadows.org.eclipse.jdt.core
13:8:56 Preparing for build: planning to be an incremental build
13:8:56 Starting incremental compilation loop 1 of possibly 5
13:8:56 Timer event: 62ms: Time to first compiled message
13:8:56 Timer event: 78ms: Time to first woven message
13:8:56 AspectJ reports build successful, build was: INCREMENTAL
13:8:56 AJDE Callback: finish. Was full build: false
13:8:56 Timer event: 78ms: Total time spent in AJDE
13:8:57 Timer event: 125ms: Create element map (1640 rels in project: shadows.org.eclipse.jdt.core)
13:8:57 Types affected during build = 1
13:8:57 Timer event: 78ms: Add markers (827 markers)
13:8:57 Timer event: 1203ms: Total time spent in AJBuilder.build()

So although the time spent in AJDE was 78milliseconds, the time spent in AJBuilder.build was 1203ms.  Where does this time get spent?  A profile of it reveals:

AJBuilder.build(int,Map,IProgressMonitor)            (if this is 100%)
- AJBuilder.doRefreshAfterBuild(IProject,IProject[],IJavaProject) 80%
- AJModel.createMap(IProject,boolean,boolean)                     11%
- AjCompiler.build()                                               6% < -- thats the 78ms
- AJBuilder.postCallListeners(boolean)                             2%



Comment 14 Andrew Clement CLA 2008-10-02 14:35:34 EDT
Closing to cover work done for 1.6.2 AspectJ.  Single project scenario enhancements (which also impact multi-project scenarios to a degree) are in.  There is still work to do looking at multi project setups, but that will be covered in a new bug.
Comment 15 Andrew Eisenberg CLA 2008-10-10 17:46:25 EDT
Created attachment 114843 [details]
Turn on incremental build optimizations by default

This patch will keep the incremental build optimizations preference item around, it will turn it on by default.