I've got a patch for this prepared, how would you like me to send it?
I spent a fair bit of time with kcachegrind trying to get it all in
there without affecting the style and structure of addendum, but I did
end up making a few tweaks - I hope that's OK, I promise they're
beneficial!
I did exactly what you suggested - factored out the cache bit of
AnnotationsBuilder into AddendumMemoryCache (which just masks an array
with no persistence between requests), which implements an
AddendumCache interface.
I added a mode setting to Addendum that determines how caching is
used. There are three possible values: debug, normal and performance.
"Debug" never caches anything, "normal" invalidates the cache if
either the reflection's file or any of the files containing the
annotations have been modified since the cache item was set (using the
file's mtime). "Performance" never checks mtimes. As a side note, I
added a test into AnnotationsBuilder that skipped all the mtime
malarkey when the cache was an instance of AddendumMemoryCache, but it
didn't improve performance at all so I left it out - I suppose if
users really cared they'd use a more persistent caching solution.
Speaking of more persistent caching solutions, included in the patch
are two simple classes that implement AddendumCache: AddendumFileCache
and AddendumAPCCache. AddendumFileCache is pretty braindead - it just
uses the md5 of the key (AnnotationsBuilder::createName()) as the file
name and serialises the parsed annotations array into it.
AddendumAPCCache is similarly braindead. I wanted to keep the caches
as lean as possible to encourage adapters. The caches I have added
aren't exactly comprehensive in their feature set; they do the
absolute bare minimum to allow users of addendum who don't have
another caching solution cooked up the option of benefiting from the
massive performance improvement without any significant effort.
My profiling experiments with this patch show that performance would
be improved substantially by caching the parsed annotation data for an
entire class in one hit. At the moment, the cache is hit for every
member for which we are requesting annotation data and easily 20 or
30% of the time is spent making many more calls to the caches than
would be made if it was only hitting for a full class. I didn't want
to tear the whole thing up trying to do that straight off the bat
though and I think this can evolve into something like that down the
line if you want.
Anyway, some basic numbers. I have a little program that has a 'mock
ORM' sort of thing (a fairly common use for annotations). There are 4
models and 6 annotations, many with @Targets, and a runner script that
iterates over the annotations for every property and method in all of
the models. I can zip this up and send it to you with the patch if you
like. I got the following numbers running ab -n 2000 -c 5 to get an
overall load figure:
Baseline PHP (echo "";): 2190.82 requests per second
Profiling test script, only models & annotation classes required, no
calls to addendum: 1072.01 rps
Addendum r77: 31.68 rps
Patched Addendum with no cache: 29.88 rps
Patched Addendum with APC cache in DEBUG mode: 20.43 rps, -31.62%
Patched Addendum with APC cache in NORMAL mode: 165.13 rps, +552.64%
Patched Addendum with APC cache in PERFORMANCE mode: 191.58 rps,
+641.16%
All tests pass on PHP 5.3.3 on OS X, PHP 5.2.11 Windows and PHP 5.3.?
Windows.