Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

stephanenicolas

July 02, 2014
Tweet

More Decks by stephanenicolas

Other Decks in Programming

Transcript

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

    View Slide

  2. 2
    Stéphane NICOLAS
    And others : RoboDemo, Quality Analysis Tools for Android,
    android-maven-plugin, RoboGuice, BoundBox, etc..
    BoundBox

    View Slide

  3. 3
    https://jobs.groupon.com/careers/

    View Slide

  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.

    View Slide

  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

    View Slide

  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)

    View Slide

  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.

    View Slide

  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

    View Slide

  9. Using (robo)blender


    org.roboguice
    roboguice
    ${roboguice.version}



    org.roboguice
    roboblender
    ${roboguice.version}
    provided

    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.

    View Slide

  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}

    View Slide

  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

    View Slide

  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

    View Slide

  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.

    View Slide

  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 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

    View Slide

  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

    View Slide

  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 getAllFields(String annotationClassName, Class> c)
    • public boolean isWorthScanningForMethods(String AnnotationClassName, Class> c)
    • public Set getAllMethods(String annotationClassName, Class> c)
    • public boolean isWorthScanningForConstructors(String AnnotationClassName, Class> c)
    • public Set> 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.

    View Slide

  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)

    View 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.

    View Slide

  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

    View Slide

  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.

    View Slide

  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

    View Slide

  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.

    View Slide

  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

    View Slide

  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.

    View Slide

  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.

    View Slide

  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.

    View Slide

  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

    View Slide

  28. Introducing Blender,
    an annotaton processor to speed up Guice & RoboGuice
    Thanks for your attention !
    Any questions ?

    View Slide