Down the Rabbit Hole

Down the Rabbit Hole

?? Micro-optimizations 小優(yōu)化

HikariCP包含很多獨(dú)立的微小的優(yōu)化,這些優(yōu)化幾乎都無(wú)法評(píng)估測(cè)量,但是所有小優(yōu)化一起形成了一個(gè)整體的性能提升。其中的一些優(yōu)化是以幾毫秒平攤在數(shù)以百萬(wàn)計(jì)的調(diào)用。

HikariCP contains many micro-optimizations that individually are barely measurable, but together combine as a boost to overall performance. Some of these optimizations are measured in fractions of a millisecond amortized over millions of invocations.

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.

其中一個(gè)有意義的優(yōu)化就是消除在ConnectionProxy中用于追蹤活躍的Statement對(duì)象的ArrayList<Statement> 。當(dāng)Statement關(guān)閉了,它必須從集合中刪除,并且當(dāng)整個(gè)Connection關(guān)閉了,他必須迭代集合然后關(guān)閉所有的Statement實(shí)例,最終清理整個(gè)集合對(duì)象。java的ArrayList為了通常的使用,在每個(gè)get(int index)調(diào)用時(shí)都做了越界檢查,但是因?yàn)樵谶@里我們可以確保索引范圍,所有這個(gè)檢查是多余的支出。

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.

另外remove(Object) 的實(shí)現(xiàn)是從頭到尾掃描數(shù)組,然而通常的JDBC編程模型是在使用了Statements之后立即關(guān)閉(跟創(chuàng)建statement順序相反)。因此如果從尾部開(kāi)始掃描性能會(huì)更加好。因此ArrayList<Statement>由我們自己定義的一個(gè)類FastList來(lái)代替掉了,它消除了索引越界的檢查以及在刪除時(shí)執(zhí)行從尾到頭部的掃描操作。

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 無(wú)鎖設(shè)計(jì)

  • ThreadLocal caching 本地線程緩存

  • Queue-stealing 工作竊取隊(duì)列

  • Direct hand-off optimizations

...resulting in a high degree of concurrency, extremely low latency, and minimized occurrences of false-sharing.

Invocation: invokevirtual vs invokestatic

簡(jiǎn)而言之:原先的是單例工廠方法,改成類的靜態(tài)方法。即原先調(diào)用獲取代理連接等等的流程是 getStatic 獲取單例的工廠對(duì)象,然后invokeVirtual調(diào)用對(duì)象的具體方法來(lái)返回代理類。后面改進(jìn)之后直接invokeStatic調(diào)用類的靜態(tài)方法來(lái)返回代理對(duì)象,因此性能得到提升。

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

為了生成Connection, Statement, and ResultSet 的代理對(duì)象實(shí)例,HikariCP最開(kāi)始使用了單例工廠方法,在ConnectionProxy對(duì)象的一個(gè)靜態(tài)屬性(PROXY_FACTORY)中。

There was a dozen or so methods resembling the following:

項(xiàng)目中有許多如下類似的方法:

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.

    get static指令消失了

  • The invokevirtual call is replaced with a invokestatic call that is more easily optimized by the JVM.

    invokevirtual被替換成了invokestatic調(diào)用,更加容易被JVM優(yōu)化。(invokevirtual需要查詢虛方法表來(lái)確定方法的直接引用,invokestatic在類加載的解析階段就從符號(hào)引用轉(zhuǎn)成了直接引用 )

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

    方法stack的深度從5變成了4.這是因?yàn)橄惹暗?code>invokevirtual調(diào)用時(shí)需要在stack頂部Pop出一個(gè)ProxyFactory實(shí)例引用

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.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容