diff options
author | Paul Phillips <paulp@improving.org> | 2011-11-22 04:34:09 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-11-22 04:34:09 +0000 |
commit | e86f07fdd4d74f19d5206ded07739b4fa17957ad (patch) | |
tree | cbd19662b877cc3e41f54d07cb293ba7c5c6bd8e /src/compiler/scala/reflect/internal/AnnotationInfos.scala | |
parent | eb0643210f2007775df3d116f237056ec5916874 (diff) | |
download | scala-e86f07fdd4d74f19d5206ded07739b4fa17957ad.tar.gz scala-e86f07fdd4d74f19d5206ded07739b4fa17957ad.tar.bz2 scala-e86f07fdd4d74f19d5206ded07739b4fa17957ad.zip |
Long-standing performance mystery solved.
I noticed a long time ago that calls to def annotations in Symbols
figured way, way too high in profiling output, but my earlier efforts to
modify it failed because I didn't understand the "accidental" service
it was supplying. Here is the key piece of the former implementation of
annotations:
- val annots1 = initialize.rawannots map {
- case x: LazyAnnotationInfo => x.annot()
- case x: AnnotationInfo => x
- } filterNot (_.atp.isError)
The first thing you might notice is that because it calls initialize,
any call to either annotations or (more frequently) a method like
"hasAnnotation" causes a symbol to be initialized. The upshot is that
taking away tens of thousands of calls to initialize means a certain
amount of "free lunch" is over.
The second thing is that this implementation lead to the allocation of
a new list on every call to annotations. 99.999% of the time it's the
same elements in the list. The fact that rawannots is typed as a list of
"AnnotationInfoBase" which may as well be AnyRef means you can't even
use mapConserve, but even mapConserve would be an abuse of the garbage
collector given how infrequently there is any change.
So here's what we have now:
1) Annotations are delivered from trees to symbols by way of an
externally positioned map, not a field on the symbol. It's done once.
The only overhead on a call to annotations now is a null check.
2) I added a small sprinkling of calls to initialize in sensible
locations.
3) The profiler impact is hard to believe, but this is reproducible.
For whatever reason the non-profiler wall clock time impact is not as
impressive.
My profiling target was the compilation of these 15 files:
src/library/scala/collection/generic/G*.scala
Before this patch, heap usage peaked at 60MB. After, 35MB. 40% drop in
profiler measured time elapsed. (Again, it's not like that outside the
profiler.) About a 55% drop in number of allocations. About a 40% drop
in total size of allocations.
+----------------------+------------------+-----------------+-----------------+
| Name | Time Diff (ms) | Old Time (ms) | New Time (ms) |
+----------------------+------------------+-----------------+-----------------+
| +---<All threads> | -19,569 | 52,496 | 32,926 |
+----------------------+------------------+-----------------+-----------------+
+----------------------------+--------------------+-----------------------+
| Packages and Classes | Objects (+/-) | Size (+/-) |
+----------------------------+--------------------+-----------------------+
| +---<Objects by classes> | -877,387 -56 % | -26,425,512 -37 % |
| | | | |
| +---char[] | -43,308 -2 % | -2,756,744 -3 % |
| | | | |
| +---java | -67,064 -3 % | -2,027,264 -2 % |
| | | | |
| +---scala | -745,099 -48 % | -19,021,760 -26 % |
+----------------------------+--------------------+-----------------------+
Diffstat (limited to 'src/compiler/scala/reflect/internal/AnnotationInfos.scala')
-rw-r--r-- | src/compiler/scala/reflect/internal/AnnotationInfos.scala | 4 |
1 files changed, 4 insertions, 0 deletions
diff --git a/src/compiler/scala/reflect/internal/AnnotationInfos.scala b/src/compiler/scala/reflect/internal/AnnotationInfos.scala index c096d14b76..a86853b453 100644 --- a/src/compiler/scala/reflect/internal/AnnotationInfos.scala +++ b/src/compiler/scala/reflect/internal/AnnotationInfos.scala @@ -13,6 +13,10 @@ import pickling.ByteCodecs trait AnnotationInfos extends api.AnnotationInfos { self: SymbolTable => import definitions.{ ThrowsClass, isMetaAnnotation } + // Annotations which are en route from Modifiers to a Symbol. + // They are removed from this map when the Symbol comes to claim them. + val pendingSymbolAnnotations = perRunCaches.newMap[Symbol, List[AnnotationInfoBase]]() + // Common annotation code between Symbol and Type. // For methods altering the annotation list, on Symbol it mutates // the Symbol's field directly. For Type, a new AnnotatedType is |