亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

FileChannel 和 MappedByteBuffer 實現上有什么不同?為什么性能差這么多?

FileChannel 和 MappedByteBuffer 實現上有什么不同?為什么性能差這么多?

HUX布斯 2019-01-16 02:48:46
環境 mac 10.142.2 GHz Intel Core i7APPLE SSD AP0512M (底下有小伙伴測出相反的結果,跟硬盤有關系) 問題描述 看RocketMQ源碼的時候看到數據寫到MappedFile有兩種方式: 先寫入 writeBuffer, 再將writeBuffer 寫入到 FileChannel 再調用 force()刷盤; 數據直接寫入MappedByteBuffer, 調用force() 刷盤; 我的問題是為什么不直接采用第二種方法?于是我通過下面的代碼驗證兩種方式的寫性能。 相關代碼 import java.io.*; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class MMapTest { static File file= new File("./test.txt"); static ByteBuffer buffer; static int fileSize = 8 * 1024 * 1024; static boolean del = true; public static void main(String[] args) { init(1); deleteFile(); int[] sizes = {128,256,512,4096,8192,1024*16,1024*32,1024*128,1024*512}; try { for (int size : sizes) { testDBChannel(size); testMappedByteBuffer(size); System.out.println(); } } catch (IOException e) { e.printStackTrace(); } } private static void init(int size) { buffer = ByteBuffer.allocateDirect(size); } private static void deleteFile() { file.delete(); } private static void testDBChannel(int size) throws IOException { init(size); RandomAccessFile rw = new RandomAccessFile(file, "rw"); FileChannel channel = rw.getChannel(); int writeSize = 0; Long start = System.currentTimeMillis(); while (writeSize < fileSize) { buffer.clear(); buffer.put(new byte[size]); buffer.flip(); channel.position(writeSize); channel.write(buffer); channel.force(false); writeSize += size; } //channel.force(false); System.out.println("DirectBuffer + FileChannel write " + size + " bytes every time cost: " + (System.currentTimeMillis() - start) + "ms"); if(del) deleteFile(); } private static void testMappedByteBuffer(int size) throws IOException { init(size); RandomAccessFile rw = new RandomAccessFile(file, "rw"); FileChannel channel = rw.getChannel(); MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize); int writeSize = 0; Long start = System.currentTimeMillis(); while (writeSize < fileSize) { map.put(new byte[size]); map.force(); writeSize += size; } //map.force(); System.out.println("MappedByteBuffer write " + size + " bytes every time cost: " + (System.currentTimeMillis() - start) + "ms"); if(del) deleteFile(); } } 輸出: DirectBuffer + FileChannel write 128 bytes every time cost: 3577ms MappedByteBuffer write 128 bytes every time cost: 13518ms DirectBuffer + FileChannel write 256 bytes every time cost: 1968ms MappedByteBuffer write 256 bytes every time cost: 7044ms DirectBuffer + FileChannel write 512 bytes every time cost: 1001ms MappedByteBuffer write 512 bytes every time cost: 3037ms DirectBuffer + FileChannel write 1024 bytes every time cost: 659ms MappedByteBuffer write 1024 bytes every time cost: 1274ms DirectBuffer + FileChannel write 4096 bytes every time cost: 214ms MappedByteBuffer write 4096 bytes every time cost: 331ms DirectBuffer + FileChannel write 8192 bytes every time cost: 137ms MappedByteBuffer write 8192 bytes every time cost: 168ms DirectBuffer + FileChannel write 16384 bytes every time cost: 77ms MappedByteBuffer write 16384 bytes every time cost: 86ms DirectBuffer + FileChannel write 32768 bytes every time cost: 44ms MappedByteBuffer write 32768 bytes every time cost: 58ms DirectBuffer + FileChannel write 131072 bytes every time cost: 16ms MappedByteBuffer write 131072 bytes every time cost: 25ms DirectBuffer + FileChannel write 524288 bytes every time cost: 10ms MappedByteBuffer write 524288 bytes every time cost: 21ms 我的理解是兩種方式都都是將數據寫入到pageCache中再刷盤的,為什么耗時差這么多,具體兩種方式的實現原理是什么? 一般情況下使用RocketMQ 都是異步刷盤,會利用OS的pageCache機制達到很高的性能;上面描述的這個問題是針對同步刷盤情況,按照 @Tyrael 第一種測試,SATA盤情況下,mbb 的性能 是要高于 db+fc 的,更加讓我懷疑為什么不直接用mbb。
查看完整描述

1 回答

?
嚕嚕噠

TA貢獻1784條經驗 獲得超7個贊

MappedByteBuffer
在寫盤時會自己創建一個DirectBuffer暫存的。因為寫盤時操作系統要求傳遞的內存地址不能變,但是java堆管理在gc中可能把數據搬來搬去,所以需要一個堆外的DirectBuffer來存臨時數據。相比直接DirectBuffer操作,是增加了一步的。

以上是看錯了問題。
我測試了三種情況,macpro 14mid,阿里云ecs,macpro18。從測試情況上看應該是跟系統硬件,系統本身有關系。但是沒有什么規律的樣子

2019-01-15更新

這幾天參閱了大量資料,總結了一個應該是正確答案的答案:
關于java8下,linux與mac得到的結果不同的解答,
FileChannel的force方法與MappedByteBuffer的force方法,最終調用的是系統中的fsync與msync方法。

  • mac中,fsync方法明確說明了該方法不一定強制寫盤(man page里也沒有明確說寫到哪了,我認為應該是寫到了kernel自己的緩存里,此時fsync就返回成功了,然后kernel再緩慢的寫盤 可以參考這篇文章中間的關于macos下的說明),但是同時提供了一個F_FULLFSYNC fcntl來進行強制寫盤操作,msync沒有說明,可以認為是強制寫盤。
  • linux中, fsync方法明確說明了該方法只有寫盤后才返回否則一直阻塞。這跟msync其實是類似的效果了。也有人說這種情況下,msync類似于fsync。這也是我在評論里有一個耗時差不多的情況的原因。但是從理論上,msync應該快于fsync,我懷疑是測試代碼并沒有達到fsync的瓶頸。

關于java11下FileChannel慢的要死的問題,
這里我發現其實是FileChannel#write()方法拖慢了節奏,嘗試著找了一下java8與java11在c下的write0方法的實現,看write0()代碼上都是一樣的,所以也沒找出具體原因。懷疑可能跟write0調用的write方法有區別。

總結。
jni的效率其實跟系統強關聯,不同系統實現不同,可能會導致不同的結果,所以評論中的問題關于要用哪個,我建議是,在實際的機器環境上寫測試腳本測試一下,哪個符合你的需求,就用哪個。然后以后的機器環境做統一。

查看完整回答
反對 回復 2019-02-12
  • 1 回答
  • 0 關注
  • 1192 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號