One Connection Cycle is defined as single
One Statement Cycle is defined as single
- In order to make HikariCP as fast as it is, we went down to bytecode-level engineering, and beyond.
- We pulled out every trick we know to help the JIT help you.
- We studied the bytecode output of the compiler, and even the assembly output of the JIT to limit key routines to less than the JIT inline-threshold.
- We flattened inheritance hierarchies, shadowed member variables, eliminated casts.
- One non-trivial (performance-wise) optimization was eliminating the use of an ArrayList
instance in the ConnectionProxy used to track open Statement instances.
- When a Statement is closed, it must be removed from this collection
- When a Connection is closed, it must iterate the collection and close any open Statement instances, and finally must clear the collection.
- The Java ArrayList, wisely for general purpose use, performs a range check upon every get(int index) call.
- However, because we can provide guarantees about our ranges, this check is merely overhead.
- Additionally, the remove(Object) implementation performs a scan from head to tail.
- However common patterns in JDBC programming are to close Statements immediately after use, or in reverse order of opening.
- For these cases, a scan that starts at the tail will perform better.
- HikariCP contains a custom lock-free collection called a ConcurrentBag.
- The idea was borrowed from the C# .NET ConcurrentBag class, but the internal implementation quite different.
- A lock-free design
- ThreadLocal caching
- Direct hand-off optimizations
- high degree of concurrency
- extremely low latency
- minimized occurrences of false-sharing
In order to generate proxies for Connection, Statement, and ResultSet instances, HikariCP was initially using a singleton factory, held in the case of ConnectionProxy in a static field (PROXY_FACTORY).
There was a dozen or so methods resembling the following:
public final PreparedStatement prepareStatement(String sql, String columnNames) throws SQLException
Using the original singleton factory, the generated bytecode looked like this:
public final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String) throws java.sql.SQLException;
You can see that first there is a getstatic call to get the value of the static field PROXY_FACTORY, as well as (lastly) the invokevirtual call to getProxyPreparedStatement() on the ProxyFactory instance.
We eliminated the singleton factory (which was generated by Javassist) and replaced it with a final class having static methods (whose bodies are generated by Javassist).
The Java code became:
Where getProxyPreparedStatement() is a static method defined in the ProxyFactory class. The resulting bytecode is:
private final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String) throws java.sql.SQLException;
There are three things of note here:
- The getstatic call is gone.
- The invokevirtual call is replaced with a invokestatic call that is more easily optimized by the JVM
- Lastly, possibly not noticed at first glance is that the stack size is reduced from 5 elements to 4 elements.
- This is because in the case of invokevirtual there is an implicit passing of the instance of ProxyFactory on the stack (i.e this), and there is an additional (unseen) pop of that value from the stack when getProxyPreparedStatement() was called.
2019-08-16 08:10:49.897 INFO 21357 --- [ main] com.zaxxer.hikari.HikariDataSource : MyHikariCp - Starting...