2014年7月14日 星期一

那一種Java的記憶體在使用上會比較快呢?

最近在研究java的memory mapped file的技術的時候,看到了一篇網路上的文章是有關於java幾種不同記憶體技術(Heap, OffHead, ByteBuffer與DirectByteBuffer)在讀寫的throughput上的比較。

有與趣看英文的測試原文者請參考以下Blog ”Which memory is faster Heap or ByteBuffer or Direct? (http://www.javacodegeeks.com/2013/08/which-memory-is-faster-heap-or-bytebuffer-or-direct.html)”。

雖然這幾年有愈來愈多的基於JVM的程式語言被開發出來, 但是談到memory的使用與管理的時候, 回到基本上的了解卻還是必要的。在這篇文章裡我們還是驗證一下幾種memory allocation在Java下的差異與讀寫的速度。

Java 記憶體的allocation

到底Java提供那些記憶體allocation的型態呢?

Heap Memory

大家都知道Java的JVM啟動的時候需要配置”Xmx”參數, 而這個參數就定義了這個JVM最大可用的Memory pool。基本上我們在Java裡頭用”new”這個關鍵字所初始的物件 都會在這個Memory pool當中。

在這次的驗證中, 我們將使用Java的array來看看Head Memory的read/write throughtput。

Non Direct ByteBuffer

在Java中, 我們當用ByteBuffer.allocate()方法來取得一個ByteBuffer物件, 而這個ByteBuffer物件它包裝了對byte array的相關操作, 當我們處理的對象是bytes而不是物件的情形下, ByteBuffer是一個很好用的類別。

Direct ByteBuffer

Direct ByteBuffer在JDK 1.4這個版本時被release出來。在Java Doc裡頭是這樣講的:

“A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system’s native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.”

歸納以上的說明, Direct ByteBuffer的特點有:

  • 它allocated到的memory區塊是不在之前談到的JVM head記憶體
  • 它在JVM的Garbage Collector的範圍之外
  • 它適合用在這個Buffer會活的很 久而且Buffer的size比較大的場景

MappedByteBuffer

常常我們使用記憶體技術來設計程式的時候, 如何把記憶體裡頭的資料persistent地保存下來, 一直是一個課題。而Memory mapped file (記憶體區塊的檔案映射)技術就是大家可以拿出來使用的高明招式了。

在Java中, MemoryMapped file也是利用了以上所提及的Direct ByteBuffer, 在這次的驗證中我們也來看看MappedByteBuffer的read/write throughput會是如何。

OffHeap Memory

Java為了避免像我這種初階的程式設計師對於記憶體管理的設計不當而把Server都搞掛了, 於是使用了GC來自動處理記憶體管理的複雜問題。雖然很方便, 可是也因此而讓GC成為寫high performance/high throughput的程式時的一種不容易去控制的變動因素。 雖然如此, Java還是提供一個Unsafe的類別來讓我們自己去allocate與unallocate memory(唉 !人就是這樣, 你愈告訴我那個地方很危險, 我就是愈想去玩!!)。

讓我們深入地去看看這幾種記憶體allocation的read/write throughput吧!

在這次的驗證中, 我們將在記憶體read/write 一種13個byte的message (int -4 bytes, long – 8 bytes 及byte – 1 byte)。而且這次的驗證中, 我們不去評測memory allocation/deallocation的速度而是將專注在讀寫的效率之上。

循序寫(Sequential Write)的效率

X軸: Test Round

Y軸: 每秒可以完成多少次寫入的動作(百萬次為一單元) -- op/second (in Millions)

  1. DirectByteBuffer, MappedByteBuffer與OffHeap的效率大致差異不多, 大約落在280 Million/Second
  2. Heap的表現中規中矩 160 Million/Second
  3. ByteBuffer的表現則最不好, 大約只有50 Million/Second
  4. DirectByteBuffer, MappedByteBuffer與OffHeap的效率比Heap快80%, 比ByteBuffer快了460%

循序讀(Sequential Read)的效率

X軸: Test Round

Y軸: 每秒可以完成多少次讀出的動作(百萬次為一單元) -- op/second (in Millions)

  1. DirectByteBuffer與MappedByteBuffer效率大致接近, 大約落在18000 Million/Second
  2. OffHead的效率只有10000 Million/Second, 比DirectByteBuffer或MappedByteBuffer慢了80% (怎麼會這樣呢? 我百思不得其解)。
  3. Heap的表現很差, 平均只有900 Million/Second
  4. ByteBuffer的表現則慘不忍睹地只有120 Million/Second

隨機讀(Random Read)的效率

X軸: Test Round

Y軸: 每秒可以完成多少次讀出的動作(百萬次為一單元) -- op/second (in Millions)

  1. DirectByteBuffer, MappedByteBuffer, ByteBuffer與OffHeap效率大致接近, 大約落在3000 Million/Second
  2. Heap的表現跟循序讀取的效能差不多, 平均只有900 Million/Second

結論

  • DirectByteBuffer與 MappedByteBuffer在讀寫的表現都令人讚賞, 尤其是循序讀的效能突出令人驚豔
  • Heap (Java Array)的效能穩定, 但沒有特別的看頭
  • OffHeap在循序讀取的效能上有點令我跌破眼鏡, 照理來說不應有如此之現象(我強烈懷疑something wrong!? 如果你有任何發現請告訴本草民)
  • ByteBuffer的效能也很怪, 難怪在很多的開放源碼裡頭都使用DirectByteBuffer (不僅快, 而且還可以Zero-copy來減少因Buffer於Socket與Java Heap中間的copy過來與copy過去而讓效能下降的問題)

測試源始碼在這裡

測試後的分析資料在這裡

沒有留言: