<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          零拷貝(Zero-Copy)的原理

          共 8474字,需瀏覽 17分鐘

           ·

          2021-07-06 12:14

          先提出兩個問題: IO過程中,哪些步驟進行了拷貝?哪些地方零拷貝? Java支持哪些零拷貝?

          帶著這倆問題,我們一起來看下面的探究。

          哪里聽說過零拷貝?真的0次拷貝嗎?

          相信大家伙在以往的學(xué)習(xí)中,或多或少在下面這些組件、框架中有聽說過零拷貝 (Zero-Copy)?

          Kafka Netty rocketmq nginx apache

          什么是零拷貝?

          零拷貝(英語: Zero-copy) 技術(shù)是指計算機執(zhí)行操作時,CPU不需要先將數(shù)據(jù)從某處內(nèi)存復(fù)制到另一個特定區(qū)域。這種技術(shù)通常用于通過網(wǎng)絡(luò)傳輸文件時節(jié)省CPU周期和內(nèi)存帶寬。

          ?零拷貝技術(shù)可以減少數(shù)據(jù)拷貝和共享總線操作的次數(shù),消除傳輸數(shù)據(jù)在存儲器之間不必要的中間拷貝次數(shù),從而有效地提高數(shù)據(jù)傳輸效率 ?零拷貝技術(shù)減少了用戶進程地址空間和內(nèi)核地址空間之間因為上:下文切換而帶來的開銷 可以看出沒有說不需要拷貝,只是說減少冗余[不必要]的拷貝。

          LinuxI/O機制及零拷貝介紹

          IO中斷與DMA

          IO中斷,需要CPU響應(yīng),需要CPU參與,因此效率比較低。

          用戶進程需要讀取磁盤數(shù)據(jù),需要CPU中斷,發(fā)起IO請求,每次的IO中斷,都帶來CPU的上下文切換。

          因此出現(xiàn)了——DMA。

          DMA(Direct Memory Access,直接內(nèi)存存取) 是所有現(xiàn)代電腦的重要特色,它允許不同速度的硬件裝置來溝通,而不需要依賴于CPU 的大量中斷負(fù)載。 DMA控制器,接管了數(shù)據(jù)讀寫請求,減少CPU的負(fù)擔(dān)。這樣一來,CPU能高效工作了。 現(xiàn)代硬盤基本都支持DMA。

          Linux IO流程

          實際因此IO讀取,涉及兩個過程: 1、DMA等待數(shù)據(jù)準(zhǔn)備好,把磁盤數(shù)據(jù)讀取到操作系統(tǒng)內(nèi)核緩沖區(qū); 2、用戶進程,將內(nèi)核緩沖區(qū)的數(shù)據(jù)copy到用戶空間。 這兩個過程,都是阻塞的。

          傳統(tǒng)數(shù)據(jù)傳送

          比如:讀取文件,再用socket發(fā)送出去 傳統(tǒng)方式實現(xiàn): 先讀取、再發(fā)送,實際經(jīng)過1~4四次copy。


          buffer = File.read 
          Socket.send(buffer)

          1、第一次:將磁盤文件,讀取到操作系統(tǒng)內(nèi)核緩沖區(qū); 2、第二次:將內(nèi)核緩沖區(qū)的數(shù)據(jù),copy到application應(yīng)用程序的buffer; 3、第三步:將application應(yīng)用程序buffer中的數(shù)據(jù),copy到socket網(wǎng)絡(luò)發(fā)送緩沖區(qū)(屬于操作系統(tǒng)內(nèi)核的緩沖區(qū)); 4、第四次:將socket buffer的數(shù)據(jù),copy到網(wǎng)卡,由網(wǎng)卡進行網(wǎng)絡(luò)傳輸。

          傳統(tǒng)方式,讀取磁盤文件并進行網(wǎng)絡(luò)發(fā)送,經(jīng)過的四次數(shù)據(jù)copy是非常繁瑣的。實際IO讀寫,需要進行IO中斷,需要CPU響應(yīng)中斷(帶來上下文切換),盡管后來引入DMA來接管CPU的中斷請求,但四次copy是存在“不必要的拷貝”的。

          重新思考傳統(tǒng)IO方式,會注意到實際上并不需要第二個和第三個數(shù)據(jù)副本。應(yīng)用程序除了緩存數(shù)據(jù)并將其傳輸回套接字緩沖區(qū)之外什么都不做。相反,數(shù)據(jù)可以直接從讀緩沖區(qū)傳輸?shù)教捉幼志彌_區(qū)。

          顯然,第二次和第三次數(shù)據(jù)copy 其實在這種場景下沒有什么幫助反而帶來開銷,這也正是零拷貝出現(xiàn)的背景和意義。

          傳統(tǒng)數(shù)據(jù)傳送所消耗的成本:4次拷貝,4次上下文切換。 4次拷貝,其中兩次是DMA copy,兩次是CPU copy。如下圖所示 拷貝是個IO過程,需要系統(tǒng)調(diào)用。

          注意一點的是 內(nèi)核從磁盤上面讀取數(shù)據(jù) 是 不消耗CPU時間的,是通過磁盤控制器完成;稱之為DMA Copy。 網(wǎng)卡發(fā)送也用DMA。

          零拷貝的出現(xiàn)

          目的:減少IO流程中不必要的拷貝 零拷貝需要OS支持,也就是需要kernel暴露api。虛擬機不能操作內(nèi)核,

          Linux支持的(常見)零拷貝

          一、mmap內(nèi)存映射

          data loaded from disk is stored in a kernel buffer by DMA copy. Then the pages of the application buffer are mapped to the kernel buffer, so that the data copy between kernel buffers and application buffers are omitted.

          DMA加載磁盤數(shù)據(jù)到kernel buffer后,應(yīng)用程序緩沖區(qū)(application buffers)和內(nèi)核緩沖區(qū)(kernel buffer)進行映射,數(shù)據(jù)再應(yīng)用緩沖區(qū)和內(nèi)核緩存區(qū)的改變就能省略。

          mmap內(nèi)存映射將會經(jīng)歷:3次拷貝: 1次cpu copy,2次DMA copy; 以及4次上下文切換

          二、sendfile

          linux 2.1支持的sendfile

          when calling the sendfile() system call, data are fetched from disk and copied into a kernel buffer by DMA copy. Then data are copied directly from the kernel buffer to the socket buffer. Once all data are copied into the socket buffer, the sendfile() system call will return to indicate the completion of data transfer from the kernel buffer to socket buffer. Then, data will be copied to the buffer on the network card and transferred to the network.

          當(dāng)調(diào)用sendfile()時,DMA將磁盤數(shù)據(jù)復(fù)制到kernel buffer,然后將內(nèi)核中的kernel buffer直接拷貝到socket buffer; 一旦數(shù)據(jù)全都拷貝到socket buffer,sendfile()系統(tǒng)調(diào)用將會return、代表數(shù)據(jù)轉(zhuǎn)化的完成。 socket buffer里的數(shù)據(jù)就能在網(wǎng)絡(luò)傳輸了。

          sendfile會經(jīng)歷:3次拷貝,1次CPU copy 2次DMA copy; 以及2次上下文切換

          三、Sendfile With DMA Scatter/Gather Copy

          Then by using the DMA scatter/gather operation, the network interface card can gather all the data from different memory locations and store the assembled packet in the network card buffer.

          Scatter/Gather可以看作是sendfile的增強版,批量sendfile。

          Scatter/Gather會經(jīng)歷2次拷貝: 0次cpu copy,2次DMA copy

          IO請求批量化 DMA scatter/gather:需要DMA控制器支持的。 DMA工作流程:cpu發(fā)送IO請求給DMA,DMA然后讀取數(shù)據(jù)。 IO請求:相當(dāng)于可以看作包含一個物理地址。 從一系列物理地址(10)讀數(shù)據(jù):普通的DMA (10請求) dma scatter/gather:一次給10個物理地址, 一個請求就可以(批量處理)。

          4、splice

          Linux 2.6.17 支持splice

          it does not need to copy data between kernel space and user space. When using this approach, data are copied from disk to kernel buffer first. Then the splice() system call allows data to move between different buffers in kernel space without the copy to user space. Unlike the method sendfile() with DMA scatter/gather copy, splice() does not need support from hardware.

          數(shù)據(jù)從磁盤讀取到OS內(nèi)核緩沖區(qū)后,在內(nèi)核緩沖區(qū)直接可將其轉(zhuǎn)成內(nèi)核空間其他數(shù)據(jù)buffer,而不需要拷貝到用戶空間。 如下圖所示,從磁盤讀取到內(nèi)核buffer后,在內(nèi)核空間直接與socket buffer建立pipe管道。 和sendfile()不同的是,splice()不需要硬件支持。

          注意splice和sendfile的不同,sendfile是將磁盤數(shù)據(jù)加載到kernel buffer后,需要一次CPU copy,拷貝到socket buffer。 而splice是更進一步,連這個CPU copy也不需要了,直接將兩個內(nèi)核空間的buffer進行set up pipe。

          splice會經(jīng)歷 2次拷貝: 0次cpu copy 2次DMA copy; 以及2次上下文切換

          Linux零拷貝機制對比 無論是傳統(tǒng)IO方式,還是引入零拷貝之后,2次DMA copy 是都少不了的。因為兩次DMA都是依賴硬件完成的。

          零拷貝的廣義狹義之分

          實際上,零拷貝時有廣義和狹義之分的。 廣義零拷貝:能減少拷貝次數(shù),減少不必要的數(shù)據(jù)拷貝,就算作“零拷貝”。 這是目前,對零拷貝最為廣泛的定義,我們需要知道的是,這是廣義上的零拷貝,并不是操作系統(tǒng) 意義上的零拷貝。

          零拷貝的廣義性

          最早的零拷貝定義,來源于

          Linux 2.4內(nèi)核新增 sendfile 系統(tǒng)調(diào)用,提供了零拷貝。磁盤數(shù)據(jù)通過 DMA 拷貝到內(nèi)核態(tài) Buffer 后,直接通過 DMA 拷貝到 NIC Buffer(socket buffer),無需 CPU 拷貝。這也是零拷貝這一說法的來源。這是真正操作系統(tǒng) 意義上的零拷貝(也就是狹義零拷貝)。

          但是我們知道,由OS內(nèi)核提供的 操作系統(tǒng)意義上的零拷貝,發(fā)展到目前也并沒有很多種,也就是這樣的零拷貝并不是很多;

          隨著發(fā)展,零拷貝的概念得到了延伸,就是目前的減少不必要的數(shù)據(jù)拷貝都算作零拷貝的范疇;

          糟糕的是,一些開發(fā)者、機構(gòu)、某些框架,在產(chǎn)品推廣或競爭中“濫用”零拷貝這個概念,包裝并美其名曰“性能...有多高,采用零拷貝...”

          尤其在框架孵化、推廣初期,和競對爭奪市場時,這樣的宣傳似乎會讓不是很內(nèi)行的人 不明覺厲。

          今天提及的目的,是要大家明白,在看到xxx框架底層采用零拷貝時,或許并不是真正意義上的零拷貝,或許只是借用概念。

          在此說明,并不是否認(rèn)某些框架借用概念的行為,畢竟隨著發(fā)展,零拷貝的概念得到了延伸,容納了新的東西。

          想要強調(diào)的是,作為一線技術(shù)者,應(yīng)該不被幾句宣傳蒙蔽雙眼;需要清晰的知道,數(shù)據(jù)合并以減少拷貝和內(nèi)核提供的API、在性能提升方面還是有天壤之別的。

          若能稍作深入了解,便能識透其真相,究竟只是偏向于優(yōu)化數(shù)據(jù)操作,還是真正切合場景、靈活運用了操作系統(tǒng)意義上的零拷貝,都會浮出水面了。

          后文,也會對目前使用了零拷貝的常見框架進行分析。


          Java零拷貝機制解析

          Linux提供的領(lǐng)拷貝技術(shù) Java并不是全支持,支持2種(內(nèi)存映射mmap、sendfile);

          NIO提供的內(nèi)存映射 MappedByteBuffer
          • 首先要說明的是,JavaNlO中 的Channel (通道)就相當(dāng)于操作系統(tǒng)中的內(nèi)核緩沖區(qū),有可能是讀緩沖區(qū),也有可能是網(wǎng)絡(luò)緩沖區(qū),而Buffer就相當(dāng)于操作系統(tǒng)中的用戶緩沖區(qū)。

          MappedByteBuffer mappedByteBuffer = new RandomAccessFile(file, "r") 
                                          .getChannel()
                                         .map(FileChannel.MapMode.READ_ONLY, 0, len);

          底層就是調(diào)用Linux mmap()實現(xiàn)的。

          NIO中的FileChannel.map()方法其實就是采用了操作系統(tǒng)中的內(nèi)存映射方式,底層就是調(diào)用Linux mmap()實現(xiàn)的。

          將內(nèi)核緩沖區(qū)的內(nèi)存和用戶緩沖區(qū)的內(nèi)存做了一個地址映射。這種方式適合讀取大文件,同時也能對文件內(nèi)容進行更改,但是如果其后要通過SocketChannel發(fā)送,還是需要CPU進行數(shù)據(jù)的拷貝。 使用MappedByteBuffer,小文件,效率不高;一個進程訪問,效率也不高。

          MappedByteBuffer只能通過調(diào)用FileChannel的map()取得,再沒有其他方式。 FileChannel.map()是抽象方法,具體實現(xiàn)是在 FileChannelImpl.c 可自行查看JDK源碼,其map0()方法就是調(diào)用了Linux內(nèi)核的mmap的API。 使用 MappedByteBuffer類要注意的是:mmap的文件映射,在full gc時才會進行釋放。當(dāng)close時,需要手動清除內(nèi)存映射文件,可以反射調(diào)用sun.misc.Cleaner方法。

          NIO提供的sendfile
          • FileChannel.transferTo()方法直接將當(dāng)前通道內(nèi)容傳輸?shù)搅硪粋€通道,沒有涉及到Buffer的任何操作,NIO中 的Buffer是JVM堆或者堆外內(nèi)存,但不論如何他們都是操作系統(tǒng)內(nèi)核空間的內(nèi)存

          • transferTo()的實現(xiàn)方式就是通過系統(tǒng)調(diào)用sendfile() (當(dāng)然這是Linux中的系統(tǒng)調(diào)用)

          //使用sendfile:讀取磁盤文件,并網(wǎng)絡(luò)發(fā)送
          FileChannel sourceChannel = new RandomAccessFile(source, "rw").getChannel();
          SocketChannel socketChannel = SocketChannel.open(sa);
          sourceChannel.transferTo(0, sourceChannel.size(), socketChannel);

          ZeroCopyFile實現(xiàn)文件復(fù)制

          class ZeroCopyFile {

             public void copyFile(File src, File dest) {
                 try (FileChannel srcChannel = new FileInputStream(src).getChannel();
                      FileChannel destChannel = new FileInputStream(dest).getChannel()) {

                     srcChannel.transferTo(0, srcChannel.size(), destChannel);
                } catch (IOException e) {
                     e.printStackTrace();
                }
            }
          }

          注意:Java NIO提供的FileChannel.transferTo 和 transferFrom 并不保證一定能使用零拷貝。實際上是否能使用零拷貝與操作系統(tǒng)相關(guān),如果操作系統(tǒng)提供 sendfile 這樣的零拷貝系統(tǒng)調(diào)用,則這兩個方法會通過這樣的系統(tǒng)調(diào)用充分利用零拷貝的優(yōu)勢,否則并不能通過這兩個方法本身實現(xiàn)零拷貝。


          Kafka中的零拷貝

          Kafka兩個重要過程都使用了零拷貝技術(shù),且都是操作系統(tǒng)層面的狹義零拷貝,一是Producer生產(chǎn)的數(shù)據(jù)存到broker,二是 Consumer從broker讀取數(shù)據(jù)。

          • Producer生產(chǎn)的數(shù)據(jù)持久化到broker,采用mmap文件映射,實現(xiàn)順序的快速寫入;

          • Customer從broker讀取數(shù)據(jù),采用sendfile,將磁盤文件讀到OS內(nèi)核緩沖區(qū)后,直接轉(zhuǎn)到socket buffer進行網(wǎng)絡(luò)發(fā)送。

          深入學(xué)習(xí)請移步 https://www.jianshu.com/p/1c27da322767

          Netty中的零拷貝

          Netty中的Zero-copy與上面我們所提到到OS層面上的Zero-copy不太一樣, Netty的Zero-copy完全是在用戶態(tài)(Java層面)的,它的Zero-copy的更多的是偏向于優(yōu)化數(shù)據(jù)操作這樣的概念。

          Netty的Zero-copy體現(xiàn)在如下幾個個方面:

          • Netty提供了CompositeByteBuf類,它可以將多個ByteBuf合并為一個邏輯上的ByteBuf,避免了各個ByteBuf之間的拷貝。

          • 通過wrap操作,我們可以將byte[]數(shù)組、ByteBuf、 ByteBuffer 等包裝成一個 Netty ByteBuf對象,進而避免了拷貝操作。

          • ByteBuf支持slice 操作,因此可以將ByteBuf分解為多個共享同一個存儲區(qū)域的ByteBuf,避免了內(nèi)存的拷貝。

          • 通過FileRegion包裝的FileChannel.tranferTo實現(xiàn)文件傳輸,可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo)Channel,避免了傳統(tǒng)通過循環(huán)write方式導(dǎo)致的內(nèi)存拷貝問題。

          認(rèn)真閱讀的讀者,一定能夠知道: 前三個都是 廣義零拷貝,都是減少不必要數(shù)據(jù)copy;偏向于應(yīng)用層數(shù)據(jù)優(yōu)化的操作。 FileRegion包裝的FileChannel.tranferTo,才是真正的零拷貝。下面我們分別來看其每一種實現(xiàn)。 下面的分析,也不會刻意區(qū)別廣義零拷貝和狹義零拷貝,讀者只需要了解二者的區(qū)別,及其各自的實現(xiàn)對我們應(yīng)用程序的影響即可。

          通過CompositeByteBuf實現(xiàn)零拷貝
          • 將多個ByteBuf合并為一個邏輯上的ByteBuf,簡單理解就是類似于用一個鏈表,把分散的多個ByteBuf通過引用連接起來;

          • 分散的多個ByteBuf在內(nèi)存中可能是大小各異、互不相連的區(qū)域,通過鏈表串聯(lián)起來,作為一整塊邏輯上的大區(qū)域。

          • 而在實際數(shù)據(jù)讀取時,還是會去各自每一小塊上讀取。

          通過wrap操作實現(xiàn)零拷貝
          • 將byte[]數(shù)組、ByteBuf、 ByteBuffer 等包裝成一個 Netty ByteBuf對象;

          • 這個比較簡單,看過ByteBuf源碼的同學(xué)一定會知道,ByteBuf其實就是組合(包含)了byte[];

          • 通過 Unpooled.wrappedBuffer 方法來將 bytes 包裝成為一個 UnpooledHeapByteBuf 對象, 而在包裝的過程中, 是不會有拷貝操作的. 即最后我們生成的生成的 ByteBuf 對象是和 bytes 數(shù)組共用了同一個存儲空間, 對 bytes 的修改也會反映到 ByteBuf 對象中.

          通過slice操作實現(xiàn)零拷貝
          • 將ByteBuf分解為多個共享同一個存儲區(qū)域的ByteBuf

          • slice恰好是將一整塊區(qū)域,劃分成邏輯上獨立的小區(qū)域;

          • 在讀取每個邏輯小區(qū)域時,實際會去按slice(int index, int length) index和length去讀取原內(nèi)存buffer的數(shù)據(jù)。

          通過FileRegion實現(xiàn)零拷貝
          • FileRegion包裝的FileChannel.tranferTo實現(xiàn)文件傳輸,可以直接將文件緩沖區(qū)的數(shù)據(jù)發(fā)送到目標(biāo)Channel;

          • 這是操作系統(tǒng)級別的零拷貝

          擴展閱讀 https://pdfs.semanticscholar.org/6a35/60046cb8d3258669c86072a7cab05e1d2300.pdf

          瀏覽 122
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品色哟哟哟 | 黑丝草逼 | 自拍第五页 | 豆花视频在线观看国产豆花 | 成人才看的在线视频 |