https://github.com/brettwooldridge/HikariCP
Fast, simple, reliable. HikariCP is a "zero-overhead" production ready JDBC connection pool. At roughly 130Kb, the library is very light. Read about how we do it here.
https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole
ArrayList
One non-trivial (performance-wise) optimization was eliminating the use of an ArrayList<Statement>
instance in the ConnectionProxy
used to track open Statement
instances. When a Statement
is closed, it must be removed from this collection, and when the 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. Therefore, ArrayList<Statement>
was replaced with a custom class FastList
which eliminates range checking and performs removal scans from tail to head.
ConcurrentBag
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. The ConcurrentBag provides...
- A lock-free design
- ThreadLocal caching
- Queue-stealing
- Direct hand-off optimizations
Invocation: invokevirtual
vs invokestatic
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
{
return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}
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;
flags: ACC_PRIVATE, ACC_FINAL
Code:
stack=5, locals=3, args_size=3
0: getstatic #59 // Field PROXY_FACTORY:Lcom/zaxxer/hikari/proxy/ProxyFactory;
3: aload_0
4: aload_0
5: getfield #3 // Field delegate:Ljava/sql/Connection;
8: aload_1
9: aload_2
10: invokeinterface #74, 3 // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
15: invokevirtual #69 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
18: return
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:
public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}
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;
flags: ACC_PRIVATE, ACC_FINAL
Code:
stack=4, locals=3, args_size=3
0: aload_0
1: aload_0
2: getfield #3 // Field delegate:Ljava/sql/Connection;
5: aload_1
6: aload_2
7: invokeinterface #72, 3 // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
12: invokestatic #67 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
15: areturn
There are three things of note here:
- The
getstatic
call is gone. - The
invokevirtual
call is replaced with ainvokestatic
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.ethis
), and there is an additional (unseen) pop of that value from the stack whengetProxyPreparedStatement()
was called.
In all, this change removed a static field access, a push and pop from the stack, and made the invocation easier for the JIT to optimize because the callsite is guaranteed not to change.
Java 8 thru 11 maven artifact:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
Java 7 maven artifact (maintenance mode):
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java7</artifactId>
<version>2.4.13</version>
</dependency>
Java 6 maven artifact (maintenance mode):
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP-java6</artifactId>
<version>2.3.13</version>
</dependency>