国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

關(guān)于零拷貝的一點(diǎn)認(rèn)識(shí)

荊兆峰 / 3144人閱讀

摘要:前言從字面意思理解就是數(shù)據(jù)不需要來(lái)回的拷貝,大大提升了系統(tǒng)的性能這個(gè)詞我們也經(jīng)常在,,,等框架中聽到,經(jīng)常作為其提升性能的一大亮點(diǎn)下面從的幾個(gè)概念開始,進(jìn)而在分析零拷貝。

前言

從字面意思理解就是數(shù)據(jù)不需要來(lái)回的拷貝,大大提升了系統(tǒng)的性能;這個(gè)詞我們也經(jīng)常在java nio,netty,kafka,RocketMQ等框架中聽到,經(jīng)常作為其提升性能的一大亮點(diǎn);下面從I/O的幾個(gè)概念開始,進(jìn)而在分析零拷貝。

I/O概念 1.緩沖區(qū)

緩沖區(qū)是所有I/O的基礎(chǔ),I/O講的無(wú)非就是把數(shù)據(jù)移進(jìn)或移出緩沖區(qū);進(jìn)程執(zhí)行I/O操作,就是向操作系統(tǒng)發(fā)出請(qǐng)求,讓它要么把緩沖區(qū)的數(shù)據(jù)排干(寫),要么填充緩沖區(qū)(讀);下面看一個(gè)java進(jìn)程發(fā)起read請(qǐng)求加載數(shù)據(jù)大致的流程圖:

進(jìn)程發(fā)起read請(qǐng)求之后,內(nèi)核接收到read請(qǐng)求之后,會(huì)先檢查內(nèi)核空間中是否已經(jīng)存在進(jìn)程所需要的數(shù)據(jù),如果已經(jīng)存在,則直接把數(shù)據(jù)copy給進(jìn)程的緩沖區(qū);如果沒有內(nèi)核隨即向磁盤控制器發(fā)出命令,要求從磁盤讀取數(shù)據(jù),磁盤控制器把數(shù)據(jù)直接寫入內(nèi)核read緩沖區(qū),這一步通過DMA完成;接下來(lái)就是內(nèi)核將數(shù)據(jù)copy到進(jìn)程的緩沖區(qū);
如果進(jìn)程發(fā)起write請(qǐng)求,同樣需要把用戶緩沖區(qū)里面的數(shù)據(jù)copy到內(nèi)核的socket緩沖區(qū)里面,然后再通過DMA把數(shù)據(jù)copy到網(wǎng)卡中,發(fā)送出去;
你可能覺得這樣挺浪費(fèi)空間的,每次都需要把內(nèi)核空間的數(shù)據(jù)拷貝到用戶空間中,所以零拷貝的出現(xiàn)就是為了解決這種問題的;
關(guān)于零拷貝提供了兩種方式分別是:mmap+write方式,sendfile方式;

2.虛擬內(nèi)存

所有現(xiàn)代操作系統(tǒng)都使用虛擬內(nèi)存,使用虛擬的地址取代物理地址,這樣做的好處是:
1.一個(gè)以上的虛擬地址可以指向同一個(gè)物理內(nèi)存地址,
2.虛擬內(nèi)存空間可大于實(shí)際可用的物理地址;
利用第一條特性可以把內(nèi)核空間地址和用戶空間的虛擬地址映射到同一個(gè)物理地址,這樣DMA就可以填充對(duì)內(nèi)核和用戶空間進(jìn)程同時(shí)可見的緩沖區(qū)了,大致如下圖所示:

省去了內(nèi)核與用戶空間的往來(lái)拷貝,java也利用操作系統(tǒng)的此特性來(lái)提升性能,下面重點(diǎn)看看java對(duì)零拷貝都有哪些支持。

3.mmap+write方式

使用mmap+write方式代替原來(lái)的read+write方式,mmap是一種內(nèi)存映射文件的方法,即將一個(gè)文件或者其它對(duì)象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和進(jìn)程虛擬地址空間中一段虛擬地址的一一對(duì)映關(guān)系;這樣就可以省掉原來(lái)內(nèi)核read緩沖區(qū)copy數(shù)據(jù)到用戶緩沖區(qū),但是還是需要內(nèi)核read緩沖區(qū)將數(shù)據(jù)copy到內(nèi)核socket緩沖區(qū),大致如下圖所示:

4.sendfile方式

sendfile系統(tǒng)調(diào)用在內(nèi)核版本2.1中被引入,目的是簡(jiǎn)化通過網(wǎng)絡(luò)在兩個(gè)通道之間進(jìn)行的數(shù)據(jù)傳輸過程。sendfile系統(tǒng)調(diào)用的引入,不僅減少了數(shù)據(jù)復(fù)制,還減少了上下文切換的次數(shù),大致如下圖所示:

數(shù)據(jù)傳送只發(fā)生在內(nèi)核空間,所以減少了一次上下文切換;但是還是存在一次copy,能不能把這一次copy也省略掉,Linux2.4內(nèi)核中做了改進(jìn),將Kernel buffer中對(duì)應(yīng)的數(shù)據(jù)描述信息(內(nèi)存地址,偏移量)記錄到相應(yīng)的socket緩沖區(qū)當(dāng)中,這樣連內(nèi)核空間中的一次cpu copy也省掉了;

Java零拷貝 1.MappedByteBuffer

java nio提供的FileChannel提供了map()方法,該方法可以在一個(gè)打開的文件和MappedByteBuffer之間建立一個(gè)虛擬內(nèi)存映射,MappedByteBuffer繼承于ByteBuffer,類似于一個(gè)基于內(nèi)存的緩沖區(qū),只不過該對(duì)象的數(shù)據(jù)元素存儲(chǔ)在磁盤的一個(gè)文件中;調(diào)用get()方法會(huì)從磁盤中獲取數(shù)據(jù),此數(shù)據(jù)反映該文件當(dāng)前的內(nèi)容,調(diào)用put()方法會(huì)更新磁盤上的文件,并且對(duì)文件做的修改對(duì)其他閱讀者也是可見的;下面看一個(gè)簡(jiǎn)單的讀取實(shí)例,然后在對(duì)MappedByteBuffer進(jìn)行分析:

public class MappedByteBufferTest {

    public static void main(String[] args) throws Exception {
        File file = new File("D://db.txt");
        long len = file.length();
        byte[] ds = new byte[(int) len];
        MappedByteBuffer mappedByteBuffer = new FileInputStream(file).getChannel().map(FileChannel.MapMode.READ_ONLY, 0,
                len);
        for (int offset = 0; offset < len; offset++) {
            byte b = mappedByteBuffer.get();
            ds[offset] = b;
        }
        Scanner scan = new Scanner(new ByteArrayInputStream(ds)).useDelimiter(" ");
        while (scan.hasNext()) {
            System.out.print(scan.next() + " ");
        }
    }
}

主要通過FileChannel提供的map()來(lái)實(shí)現(xiàn)映射,map()方法如下:

    public abstract MappedByteBuffer map(MapMode mode,
                                         long position, long size)
        throws IOException;
        

分別提供了三個(gè)參數(shù),MapMode,Position和size;分別表示:
MapMode:映射的模式,可選項(xiàng)包括:READ_ONLY,READ_WRITE,PRIVATE;
Position:從哪個(gè)位置開始映射,字節(jié)數(shù)的位置;
Size:從position開始向后多少個(gè)字節(jié);

重點(diǎn)看一下MapMode,請(qǐng)兩個(gè)分別表示只讀和可讀可寫,當(dāng)然請(qǐng)求的映射模式受到Filechannel對(duì)象的訪問權(quán)限限制,如果在一個(gè)沒有讀權(quán)限的文件上啟用READ_ONLY,將拋出NonReadableChannelException;PRIVATE模式表示寫時(shí)拷貝的映射,意味著通過put()方法所做的任何修改都會(huì)導(dǎo)致產(chǎn)生一個(gè)私有的數(shù)據(jù)拷貝并且該拷貝中的數(shù)據(jù)只有MappedByteBuffer實(shí)例可以看到;該過程不會(huì)對(duì)底層文件做任何修改,而且一旦緩沖區(qū)被施以垃圾收集動(dòng)作(garbage collected),那些修改都會(huì)丟失;大致瀏覽一下map()方法的源碼:

    public MappedByteBuffer map(MapMode mode, long position, long size)
        throws IOException
    {
            ...省略...
            int pagePosition = (int)(position % allocationGranularity);
            long mapPosition = position - pagePosition;
            long mapSize = size + pagePosition;
            try {
                // If no exception was thrown from map0, the address is valid
                addr = map0(imode, mapPosition, mapSize);
            } catch (OutOfMemoryError x) {
                // An OutOfMemoryError may indicate that we"ve exhausted memory
                // so force gc and re-attempt map
                System.gc();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException y) {
                    Thread.currentThread().interrupt();
                }
                try {
                    addr = map0(imode, mapPosition, mapSize);
                } catch (OutOfMemoryError y) {
                    // After a second OOME, fail
                    throw new IOException("Map failed", y);
                }
            }

            // On Windows, and potentially other platforms, we need an open
            // file descriptor for some mapping operations.
            FileDescriptor mfd;
            try {
                mfd = nd.duplicateForMapping(fd);
            } catch (IOException ioe) {
                unmap0(addr, mapSize);
                throw ioe;
            }

            assert (IOStatus.checkAll(addr));
            assert (addr % allocationGranularity == 0);
            int isize = (int)size;
            Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
            if ((!writable) || (imode == MAP_RO)) {
                return Util.newMappedByteBufferR(isize,
                                                 addr + pagePosition,
                                                 mfd,
                                                 um);
            } else {
                return Util.newMappedByteBuffer(isize,
                                                addr + pagePosition,
                                                mfd,
                                                um);
            }
     }

大致意思就是通過native方法獲取內(nèi)存映射的地址,如果失敗,手動(dòng)gc再次映射;最后通過內(nèi)存映射的地址實(shí)例化出MappedByteBuffer,MappedByteBuffer本身是一個(gè)抽象類,其實(shí)這里真正實(shí)例話出來(lái)的是DirectByteBuffer;

2.DirectByteBuffer

DirectByteBuffer繼承于MappedByteBuffer,從名字就可以猜測(cè)出開辟了一段直接的內(nèi)存,并不會(huì)占用jvm的內(nèi)存空間;上一節(jié)中通過Filechannel映射出的MappedByteBuffer其實(shí)際也是DirectByteBuffer,當(dāng)然除了這種方式,也可以手動(dòng)開辟一段空間:

ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(100);

如上開辟了100字節(jié)的直接內(nèi)存空間;

3.Channel-to-Channel傳輸

經(jīng)常需要從一個(gè)位置將文件傳輸?shù)搅硗庖粋€(gè)位置,F(xiàn)ileChannel提供了transferTo()方法用來(lái)提高傳輸?shù)男剩紫瓤匆粋€(gè)簡(jiǎn)單的實(shí)例:

public class ChannelTransfer {
    public static void main(String[] argv) throws Exception {
        String files[]=new String[1];
        files[0]="D://db.txt";
        catFiles(Channels.newChannel(System.out), files);
    }

    private static void catFiles(WritableByteChannel target, String[] files)
            throws Exception {
        for (int i = 0; i < files.length; i++) {
            FileInputStream fis = new FileInputStream(files[i]);
            FileChannel channel = fis.getChannel();
            channel.transferTo(0, channel.size(), target);
            channel.close();
            fis.close();
        }
    }
}

通過FileChannel的transferTo()方法將文件數(shù)據(jù)傳輸?shù)絊ystem.out通道,接口定義如下:

    public abstract long transferTo(long position, long count,
                                    WritableByteChannel target)
        throws IOException;

幾個(gè)參數(shù)也比較好理解,分別是開始傳輸?shù)奈恢茫瑐鬏數(shù)淖止?jié)數(shù),以及目標(biāo)通道;transferTo()允許將一個(gè)通道交叉連接到另一個(gè)通道,而不需要一個(gè)中間緩沖區(qū)來(lái)傳遞數(shù)據(jù);
注:這里不需要中間緩沖區(qū)有兩層意思:第一層不需要用戶空間緩沖區(qū)來(lái)拷貝內(nèi)核緩沖區(qū),另外一層兩個(gè)通道都有自己的內(nèi)核緩沖區(qū),兩個(gè)內(nèi)核緩沖區(qū)也可以做到無(wú)需拷貝數(shù)據(jù);

Netty零拷貝

netty提供了零拷貝的buffer,在傳輸數(shù)據(jù)時(shí),最終處理的數(shù)據(jù)會(huì)需要對(duì)單個(gè)傳輸?shù)膱?bào)文,進(jìn)行組合和拆分,Nio原生的ByteBuffer無(wú)法做到,netty通過提供的Composite(組合)和Slice(拆分)兩種buffer來(lái)實(shí)現(xiàn)零拷貝;看下面一張圖會(huì)比較清晰:

TCP層HTTP報(bào)文被分成了兩個(gè)ChannelBuffer,這兩個(gè)Buffer對(duì)我們上層的邏輯(HTTP處理)是沒有意義的。 但是兩個(gè)ChannelBuffer被組合起來(lái),就成為了一個(gè)有意義的HTTP報(bào)文,這個(gè)報(bào)文對(duì)應(yīng)的ChannelBuffer,才是能稱之為”Message”的東西,這里用到了一個(gè)詞”Virtual Buffer”。
可以看一下netty提供的CompositeChannelBuffer源碼:

public class CompositeChannelBuffer extends AbstractChannelBuffer {

    private final ByteOrder order;
    private ChannelBuffer[] components;
    private int[] indices;
    private int lastAccessedComponentId;
    private final boolean gathering;
    
    public byte getByte(int index) {
        int componentId = componentId(index);
        return components[componentId].getByte(index - indices[componentId]);
    }
    ...省略...

components用來(lái)保存的就是所有接收到的buffer,indices記錄每個(gè)buffer的起始位置,lastAccessedComponentId記錄上一次訪問的ComponentId;CompositeChannelBuffer并不會(huì)開辟新的內(nèi)存并直接復(fù)制所有ChannelBuffer內(nèi)容,而是直接保存了所有ChannelBuffer的引用,并在子ChannelBuffer里進(jìn)行讀寫,實(shí)現(xiàn)了零拷貝。

其他零拷貝

RocketMQ的消息采用順序?qū)懙絚ommitlog文件,然后利用consume queue文件作為索引;RocketMQ采用零拷貝mmap+write的方式來(lái)回應(yīng)Consumer的請(qǐng)求;
同樣kafka中存在大量的網(wǎng)絡(luò)數(shù)據(jù)持久化到磁盤和磁盤文件通過網(wǎng)絡(luò)發(fā)送的過程,kafka使用了sendfile零拷貝方式;

總結(jié)

零拷貝如果簡(jiǎn)單用java里面對(duì)象的概率來(lái)理解的話,其實(shí)就是使用的都是對(duì)象的引用,每個(gè)引用對(duì)象的地方對(duì)其改變就都能改變此對(duì)象,永遠(yuǎn)只存在一份對(duì)象。

參考

<>

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/77506.html

相關(guān)文章

  • 《你不知道的javascript》筆記_對(duì)象&原型

    摘要:上一篇你不知道的筆記寫在前面這是年第一篇博客,回顧去年年初列的學(xué)習(xí)清單,發(fā)現(xiàn)僅有部分完成了。當(dāng)然,這并不影響年是向上的一年在新的城市穩(wěn)定連續(xù)堅(jiān)持健身三個(gè)月早睡早起游戲時(shí)間大大縮減,學(xué)會(huì)生活。 上一篇:《你不知道的javascript》筆記_this 寫在前面 這是2019年第一篇博客,回顧去年年初列的學(xué)習(xí)清單,發(fā)現(xiàn)僅有部分完成了。當(dāng)然,這并不影響2018年是向上的一年:在新的城市穩(wěn)定、...

    seasonley 評(píng)論0 收藏0
  • [基礎(chǔ)學(xué)python]關(guān)于類的初步認(rèn)識(shí)

    摘要:反對(duì)者在某些領(lǐng)域?qū)Υ擞枰苑裾J(rèn)。下面再引用一段來(lái)自維基百科中關(guān)于的歷史。類的更嚴(yán)格的定義是由某種特定的元數(shù)據(jù)所組成的內(nèi)聚的包。類還可以有運(yùn)行時(shí)表示形式元對(duì)象,它為操作與類相關(guān)的元數(shù)據(jù)提供了運(yùn)行時(shí)支持。 在開始部分,請(qǐng)看官非常非常耐心地閱讀下面幾個(gè)枯燥的術(shù)語(yǔ)解釋,本來(lái)這不符合本教程的風(fēng)格,但是,請(qǐng)看官諒解,因?yàn)榱形粚?lái)一定要閱讀枯燥的東西的。這些枯燥的屬于解釋,均來(lái)自維基百科。 1、問題...

    王巖威 評(píng)論0 收藏0
  • 基于拷貝技術(shù)的的java NIO文件下載服務(wù)器

    摘要:什么是零拷貝我們首先來(lái)認(rèn)識(shí)一下傳統(tǒng)的操作。因?yàn)樵谶@套體系里,不僅僅提供了非阻塞的編程模型,而且提供了類似零拷貝,內(nèi)存映射這樣的新技術(shù)對(duì)于操作系統(tǒng)來(lái)說早就有了。 什么是零拷貝?我們首先來(lái)認(rèn)識(shí)一下傳統(tǒng)的I/O操作。假如說用戶進(jìn)程現(xiàn)在要把一個(gè)文件復(fù)制到另一個(gè)地方。那么用戶程序必須先把這個(gè)文件讀入內(nèi)存,然后再把內(nèi)存里的數(shù)據(jù)寫入另一個(gè)文件。不過文件讀入內(nèi)存也不是直接讀入用戶進(jìn)程的內(nèi)存,而是先讀入...

    Keven 評(píng)論0 收藏0
  • JavaScript專題系列文章

    摘要:專題系列共計(jì)篇,主要研究日常開發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫一個(gè)函數(shù),輸入,返回。 JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...

    Maxiye 評(píng)論0 收藏0
  • 詳細(xì)講解:知識(shí)證明 之 ZCash 完整的匿名交易流程

    摘要:存儲(chǔ)所有經(jīng)過不可逆函數(shù)后生成的值列表存儲(chǔ)的是已經(jīng)被消費(fèi)的中的隨機(jī)數(shù)生成的值。為和分別生成隨機(jī)數(shù)和將的公鑰設(shè)置到里面去,代表收款人是。至此,的匿名交易流程形成了閉環(huán)。在這里應(yīng)用到了零知識(shí)證明,它的代碼是根據(jù)理論完成的,同時(shí)也參考了。 作者:林冠宏 / 指尖下的幽靈 掘金:juejin.im/user/587f0d… 博客:www.cnblogs.com/linguanh/ GitH...

    niceforbear 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<