Skip to main content

Speeding up dependency injection

ActiveJ Specializer optimization

ActiveJ Specializer is a library that optimizes code for the JVM. You can simply combine it with ActiveJ Inject and speed up your code by up to 30% To set up ActiveJ Specializer, simply use Injector.useSpecializer() before instantiating the Injector. ActiveJ Inject compiles bindings at runtime into a highly efficient representation. When combined with ActiveJ Specializer, bindings can be turned into bytecode, which will be just as efficient as manually written code. This way, you get maximum performance in real-life projects without having to maintain complex manual code.

How it works

To understand how ActiveJ Specializer works, you can read the specializer documentation. Here we will use a simple example to try to explain how the specializer works in relation to ActiveJ Inject.

The specializer transforms class instances into a special classes in which instance methods are replaced with static methods and instance fields are replaced with static fields. Instances that are specialized are actually instances of a CompiledBinding class.

Suppose we have a module that defines the following binding:

@ProvidesInteger length(String string) {    return string.length();}

The compiled bindings here may look like this (a simplified version):

public class CompiledBindingImpl<R, U> implements CompiledBinding<R> {    private final CompiledBinding<U> otherBinding;
    private R instance;
    public CompiledBindingImpl(CompiledBinding<U> otherBinding) {        this.otherBinding = otherBinding;    }
    public R getInstance() {        if (instance != null) {            return instance;        } else {            U otherInstance = otherBinding.getInstance();            this.instance = createInstance(otherInstance);            return this.instance;        }    }
    protected R createInstance(U otherInstance) {        // ...    }}

If we use Injector.useSpecializer() before creating the injector, we get a specialized compiled binding (a simplified version):

public class CompiledBindingInteger implements CompiledBinding<Integer> {    private static Integer instance;
    public Integer getInstance() {        return getInstanceSpecialized();    }
    public static Integer getInstanceSpecialized() {        if (instance != null) {            return instance;        } else {            String stringInstance = CompiledBindingString.getInstanceSpecialized();            this.instance = createInstance(stringInstance);            return this.instance;        }    }
    public static Integer createInstance(String otherInstance) {        // ...    }}

Because the specializer replaced instance methods with static methods and instance fields with static fields, this allows for additional JVM optimizations and also neglects the overhead of dynamic dispatching. Also, this may seem like minimal optimization, but imagine if you have a huge dependency graph and need to create instances at runtime. For example, when using ActiveJ Inject to handle HTTP requests, when you need to instantiate new objects at every HTTP request. In this scenario, the performance boost of using the specializer would be significant.

Speeding up RPC request processing

We created an example of using ActiveJ Inject + ActiveJ Specializer to improve the performance of an RPC server that uses dependency injection to process RPC requests. To learn more about ActiveJ RPC you can visit this page.

The dependency graph on the server is similar to the one show in the Cookbook example. Each incoming RPC request is handled within the RPC scope.

The example also comes with a dedicated benchmark tool that you can run to measure server performance.

Try running the server and the benchmark tool as-is and measure the performance. Then comment out the Injector.useSpecializer() line in ScopedRpcServerExample and run the server and the benchmark tool again. You should see significant performance gains over not using ActiveJ Specializer. We have measured server performance gains of up to 2x.