Blender : Boosting Guice with annotation processing

Blender : Boosting Guice with annotation processing

Blender is a new annotation processor that we introduced to speed up Guice performances at runtime. RoboGuice is the first library to benefit of it : 30-70% speed gain, 30% memory gain.

1f9cd9b9bad9aa6115a1ed72433c180d?s=128

stephanenicolas

July 02, 2014
Tweet

Transcript

  1. Introducing Blender an annotaton processor to speed up Guice &

    RoboGuice !
  2. 2 Stéphane NICOLAS And others : RoboDemo, Quality Analysis Tools

    for Android, android-maven-plugin, RoboGuice, BoundBox, etc.. BoundBox
  3. 3 https://jobs.groupon.com/careers/

  4. Why Blender ? • Guice is great, simple to use

    and robust • But Guice is slow, especially at startup • Guice also consumes a lot of memory All these drawbacks can be tolerated on a standalone Java app, or a web server, but not on a mobile device. RoboGuice, the extension of Guice for Android, sufers from it.
  5. Why are Guice & RoboGuice memory hungry and slow ?

    Afer some memory and speed profling, we realized that Guice & RoboGuice are : • Memory Hungry because : • All classes and their ancestors are loaded into memory at runtme to determine the injecton points. • All constructors, felds and methods are loaded into memory to fnd injecton points. • Slow because : • All classes are scanned at runtme to determine the injecton points. • RoboGuice is also quite slow as it ofers a lot of default bindings that may not all be used inside a given app
  6. How does Blender come into play ? Let's say we

    got a class like this : public class A { @Inject B b; } Dependency Injecton (DI) can be decomposed in 3 subproblems : • Finding all injecton points (e.g. the feld B of class A) • Declaring and resolving a binding (done in a Guice module) • Assigning the resolved binding value to the injecton point (assigning the value of feld B of an instance A)
  7. How does Blender come into play ? It should possible

    to determine injecton points at compile-tme, and completely avoid scanning classes to fnd injecton points, we could just « feed » them to Guice at runtme. Annotaton processing could be used to detect all constructors, methods, and felds that will be used as injecton points. For RoboGuice only Moreover, an annotaton processor could track all classes that are going to be injected, and discard any binding that is not used by a given app. (This works on Guice too, but RoboGuice really benefts of it.) That’s exactly what Blender does.
  8. Blender’s overview Java File Annotaton Database Compile tme Classes containing

    Injecton Points Classes bound in Guice modules Guice Classes with injected stuf Runtme
  9. Using (robo)blender <!– Runtime dependency --> <dependency> <groupid>org.roboguice</groupid> <artifactid>roboguice</artifactid> <version>${roboguice.version}</version>

    </dependency> <!– Compile-time dependency --> <dependency> <groupId>org.roboguice</groupId> <artifactId>roboblender</artifactId> <version>${roboguice.version}</version> <scope>provided</scope> </dependency> compile 'org.roboguice.roboguice:$ROBOGUICE_VERSION' provided 'org.roboguice.roboblender:$ROBOGUICE_VERSION' (Robo)Blender can be added as a scope-provided dependency, it will then boost your applicaton performances. But, it’s purely optonal : if not present, (Robo)Guice will stll run fne, but you won’t beneft from performance boosts.
  10. Blender’s annotaton database Annotaton Database The annotaton database is built

    at compile tme via an annotaton processor that is triggered by @Inject (and siblings) annotatons in java source code. It contains : • The list of all injecton points : • for each @Inject (and siblings) annotaton class • for each class that contains injecton points • either the name of an injected feld or the signature of a method/constructor that receives injectons, for itself or for one or more of its parameters • The list of all classes that are injected, i.e. those for which Guice may have to provide a binding for. Ex: public class A { @Inject private B b; public void m(@Inject B b) {} } The annotaton database contains : • Injected felds = @Inject -> { A -> {b}} • Injected methods= @Inject -> { A -> {m:B}} • Bound classes = {B}
  11. Guice usage of the Annotated Database Annotaton Database Guice uses

    the annotated database to • Filter the classes that are scanned for injecton points : only the classes that contain an injecton point for a given annotaton class will be kept (additonal fltering can be added via an extension mechanism, used by RoboGuice). • The classes that pass the flter will not be entrely scanned as they used to : the annotaton database will be used to only process an injecton on the felds, constructors and methods that actually contain an injecton point for a given annotaton class. • Filter the bindings created in modules. Only the bindings of boundable/injectable classes will be retained, the others will be discarded. Multple annotaton databases support Multple annotaton databases can be used by Guice. For instance, RoboGuice’s annotaton database and an app’s annotaton database are merged together before being used by RoboGuice. Guice
  12. Example of Guice usage of the Annotated Database Annotaton Database

    Guice Ex: public class A { @Inject private B b; private C c; public void m(@Inject B b) {} public void m(); } The annotaton database contains : Injected felds = • @Inject -> { A -> {b}} • Injected methods = • @Inject -> { A -> {m:B}} • Bound classes = • {B} Guice will : • Filter out all any annotaton class scan except for @Inject • Filter out all classes except A when looking for injecton point. • Only process for injecton : • the feld private B b; (not C c) • the method m(@Inject B b) (not n()) • Only bind class B to something, discarding any other bindings. Compile tme Runtme
  13. Blender’s Implementaton details Annotaton Database The annotaton database is generated

    as code This allows to be fully compatble with all java like platorms, mostly Android. It would have been more natural to generate data in a binary format, but Android’s resource loading mechanism would have required complex build twists. We use Apache velocity template engine to generate the annotaton databases. The annotaton database is based on pure java data structures This choice was induced by the fact that a custom data structure, which would provide us with a more meaning full code, would require to introduce a separate maven module that would be used by both Blender and Guice, in order to avoid a module dependency cycle if one of them would contain the shared data structure. The annotaton database’s package is customizable By default, the annotaton database is generated in the default package. This can be customized by passing a parameter to the Blender annotaton processor.
  14. Blender’s Implementaton details Guice can now create Hierarchy Traversal Filters

    Such flters are used to discard classes when scanning them for injecton points. Filter have a simple interface, similar to : • boolean IsWorthScanning(Annotation, Class c) • List<String> getInjectedFieldNames(Annotation, Class c) • (if it returns null, then we scan all fields) Filters can be used by the class InjectPoint and TypeListeners A TypeListener can easily focus only on felds, constructors and methods of classes that contain the @Inject annotaton class it targets, and discard any other as quickly as possible. Filters can be customized via a Hierarchy Traversal Filter Factory The default is to apply a flter that doesn’t flter out any class. That was the default with Guice : all classes should be scanned to fnd injecton points. This default flter is used when no annotaton database is found at runtme. Classes containing Injecton Points Guice Filters
  15. Blender’s Implementaton details Guice can now discard a binding in

    a module via a NoOpBinder A NoOpBinder will simply do nothing when asked to bind a class to its bound implementaton class. A module will use the informaton from the annotaton data base to determine wether a class is bindable or not. Guice doesn’t discard any binding by default By default, if a module is not passed an annotaton database, it will accept all bindings. This allows to preserve Guice’s default behavior and remain fully compatble with older versions. Guice can now discard TypeListeners if they’re not used Thanks to the annotaton database, a module can check if there is any injecton point for a given @Inject annotaton subclass. If not, then the TypeListener for this annotaton class is simply not registered. Classes bound in Guice modules Guice NoOp Binder
  16. Blender’s Filter Implementaton details Pruning hierarchy traversal Filters are used

    to prune hierarchy traversal during injecton point lookup : while (current.getRawType() != Object.class)  while (filter.isWorthScanning(@Inject.class.getName(), current.getRawType()) Filter have a simple interface, similar to : • public boolean isWorthScanning(Class<?> c) • public boolean isWorthScanningForFields(String AnnotationClassName, Class<?> c) • public Set<Field> getAllFields(String annotationClassName, Class<?> c) • public boolean isWorthScanningForMethods(String AnnotationClassName, Class<?> c) • public Set<Method> getAllMethods(String annotationClassName, Class<?> c) • public boolean isWorthScanningForConstructors(String AnnotationClassName, Class<?> c) • public Set<Constructor<?>> getAllConstructors(String annotationClassName, Class<?> c) • public void reset() The default flter By default, Guice hierarchy flter doesn’t discard any class. All classes appear to be « worth scanning » for any annotaton. An extension point Filters are an extension point in Guice. For instance, RoboGuice uses its own flter to keep all classes that extend RoboGuice classes and discard any of their superclasses.
  17. Blender’s Filter Implementaton details The annotated flter The annotaton flter

    uses informaton from the Annotaton Database to discard classes that « are not worth scanning » for a given annotaton class. Decorator design patern Annotated flters decorates a non-annotated flter. This allows to quickly provide the informaton stored in the annotaton database and fall back on the classic fltering mechanism in some cases. (see next slide)
  18. RoboBlender’s Filter : an example to prune class hierarchy Object

    Android Context RoboActvity MyRoboActvity Is worth scanning according to Annotated flter. Only the annotated felds are looped through for injecton points lookup. Is not worth scanning according to RoboGuice flter. Is not even considered. Is worth scanning according to Annotated flter. Only the annotated felds are looped through for injecton points lookup. Filtering of class hierarchy when looking up for injecton points in class MyRoboActvity.
  19. Blender’s extension mechanisms Guice’s Blender RoboGuice’s RoboBlender Guice’s Blender annotaton

    processor can be extended by inheritance. The RoboGuice’s RoboBlender maven module takes advantage of this to process diferent @Inject annotaton classes like @InjectView, @InjectFragment, @Observes, etc. Inheritance
  20. Blender’s Performance Achievements Speed improvements Our sample app for RoboGuice’s

    startup tme, on a Nexus 4, has decreased from 840 ms to 210 ms. That’s a 75% speed gain. Memory improvement Less classes are scanned by refecton, only felds, constructors and methods are processed for injecton. On our sample applicaton for RoboGuice, memory has been reduced from 105K to 102k. That represents a 3 % memory gain. During runtme, the sample app allocated more than 5Mb that will be garbage collected. With Blender, this comes down to 0.9Mb. That’s a 82% improvement. Garbage collector saved tme As we consume less memory for useless members via refecton, the garbage collector actvity has decreased from 171 ms to 64 ms. That represents a 63% garbage collecton tme gain.
  21. Blender’s Pitalls & Things to know RoboBlender is optonal RoboBlender

    is purely optional in RoboGuice 3. It can be disabled during build via an environment variable. RoboBlender requires injecton by annotatons It is possible to inject things programmatically via Guice and RoboGuice. In that case, RoboBlender will not consider that the classes you inject are injectable and will discards its bindings. To bypass this, you can force a module to honour a binding for a given class. RoboBlender stll has a few minor issues with advanced features Namely, assisted injections do not work yet with RoboBlender. This will be fixed soon. Nevertheless, we advise against using this feature on Android for performance reasons. Also, avoid TypeLitterals on Android, they are far too slow. htps://github.com/roboguice/roboguice/wiki/RoboBlender-wiki
  22. RoboGuice 4 plans : performance release RoboBlender pushed further We

    plan to extend RoboBlender far beyond its current scope in order to completely remove the need for introspection inside Guice. Details will follow. Byte code weaving @InjectExtra, @InjectResource, @InjectExtra We plan to drop using Guice for these annotatons and implement them in an optmal way via byte code weaving. You can fnd our current experiments, which are already stable at : → htps://github.com/stephanenicolas/injects RoboGuice Gradle Plugin RoboGuice's version 4.0 will also include a brand new Gradle plugin to make this complex build process work with a single gradle confguraton line in your build.gradle fle.
  23. RoboGuice 4 plans : Guice refecton free What you code

    What we weave in your class What we generate via an annotation processor public class A { @Inject B b; } public class A { @Inject B b; static __inject(A a B b) { a.b = b; } } new Field("A", "B", "b") { set( A a, B b) { injector.inject( a, b); } } class Injector { Inject( A a, B b) { A.__inject(a, b); } } What we generate via an annotation processor and weave later on. Generated by annotation processor Generated by byte-code weaving 1 2 2 3 3
  24. RoboGuice 4 plans : Guice refecton free What you code

    public class A { @Inject B b; } 1 As before, you simply annotate the fields that will be injected.
  25. RoboGuice 4 plans : Guice refecton free What we generate

    via an annotation processor new Field("A", "B", "b") { set( A a, B b) { injector.inject( a, b); } } class Injector { Inject( A a, B b) { } } What we generate via an annotation processor and weave later on. 2 2 Thanks to an annotation processor, we generate all the Field objects that represent the information hold by the fields that you annotated. The Field class that is generated, offers the exact same API that java.lang.Field has, it will be 100% compatible with this class so that library using Java's core reflection will have very few changes to perform to be compatible with our new reflection approach. At the beginning the injector's inject method is generated empty. This allows all annotation processor generated classes to compile. Later on, during step 3, we will fill this method.
  26. RoboGuice 4 plans : Guice refecton free What we weave

    in your class public class A { @Inject B b; static __inject(A a B b) { a.b = b; } } class Injector { Inject( A a, B b) { A.__inject(a, b); } } What we generate via an annotation processor and weave later on. Generated by byte-code weaving 3 3 During step 3, we weave byte code in 2 different locations : First, we add some methods to your classes so that we can access all the fields of your classes, whatever the visibility modifer you used. Second, we fill the injector method so that it calls the weaved accessors in your classes.
  27. RoboGuice 4 plans : Guice refecton free What is the

    advantage of this approach ? We want to make reflection reflection-free. Our main idea is that if we can circumvent the main problem of reflection : slowness, and still offer a compatible API, many android libraries will benefit of it :  Guice & RoboGuice  Jackson 1 & 2 & Gson  OrmLite  etc
  28. Introducing Blender, an annotaton processor to speed up Guice &

    RoboGuice Thanks for your attention ! Any questions ?