-
Notifications
You must be signed in to change notification settings - Fork 3k
Down the Rabbit Hole
In order to make HikariCP as fast as it is, we went down to bytecode-level engineering. We pulled out every
trick we know to help the JIT help you. We tried to limit key routines to less then the JIT inline-threshold,
we chased down and eliminated as many invokeinterface or invokespecial bytecode operations as
possible, flattened inheritance hierarchies, shadowed member variables, eliminated casts. Trusting no one, we
dissected the JVM classes we used and finding them wanting replaced where necessary. We studied operating
system thread schedulers and JIT compiler output. We think it shows in our results. But like rust, we never
sleep; there are still a few unexplored paths we intend to go down in the future.
There is not only beauty in simplicity, but if done right, speed.
Pretty much every connection pool, dare we say every pool available, has to "wrap" your real Connection,
Statement, PreparedStatement, etc. instances and intercept methods like close() so that the
Connection isn't actually closed but instead is returned to the pool. Statement and it's subclasses
must be wrapped, and SQLException caught and inspected to see if the exception reflects a disconnection
that warrants ejecting the Connection from the pool.
What this means is "delegation". The Connection wrapper cares about intercepting close() or
execute(sql), but for all of the other methods of Connection it simply delegates. Something like:
public Clob createClob() {
return delegate.createClob();
}
The first iteration of HikariCP also did this, and it still provides a "fallback" mode. An interface like
PreparedStatement contains some 50+ methods, only 4 of which we are interested in intercepting. Rather
than creating a wrapper class that has 50+ "delegate" methods like the above, we use Javassist to generate
all of the delegate methods. While this provides no inherent performance increase, it means that our
"proxy" (wrapper) class only need contain the overridden methods. The Statement proxy class in HikariCP
is only ~130 lines of code, including comments. This is in keeping with out minimalist ethos.
Our delegates perform quite admirably:
| Pool | Med (ms) | Avg (ms) | Max (ms) |
|---|---|---|---|
| BoneCP | 5049 | 3249 | 6929 |
| HikariCP | 13 | 11 | 58 |
And yet. Looking at bytecode for all of the delegate methods, with their getfield, checkcast, and
invokeinterface op codes really touched our nerve. Is it possible to go faster?
Can we actually eliminate delegation itself?
"I've always been mad, I know I've been mad, like the
most of us...very hard to explain why you're mad,
even if you're not mad..."
But how? How could we eliminate delegation and still intercept the methods we need? Even still, we need to
wrap every "delegate" (non-intercepted) method with a try..catch to interrogate SQLExceptions, which is a kind of interception now isn't it?