來源:古時的風(fēng)箏
我們總會在各種地方看到零拷貝,那零拷貝到底是個什么東西。
接下來,讓我們來理一理啊。
拷貝說的是計算機(jī)里的 I/O 操作,也就是數(shù)據(jù)的讀寫操作。計算機(jī)可是一個復(fù)雜的家伙,包括軟件和硬件兩大部分,軟件主要指操作系統(tǒng)、驅(qū)動程序和應(yīng)用程序。硬件那就多了,CPU、內(nèi)存、硬盤等等一大堆東西。
(資料圖片僅供參考)
這么復(fù)雜的設(shè)備要進(jìn)行讀寫操作,其中繁瑣和復(fù)雜程度可想而知。
傳統(tǒng)I/O的讀寫過程如果要了解零拷貝,那就必須要知道一般情況下,計算機(jī)是如何讀寫數(shù)據(jù)的,我把這種情況稱為傳統(tǒng) I/O。
數(shù)據(jù)讀寫的發(fā)起者是計算機(jī)中的應(yīng)用程序,比如我們常用的瀏覽器、辦公軟件、音視頻軟件等。
而數(shù)據(jù)的來源呢,一般是硬盤、外部存儲設(shè)備或者是網(wǎng)絡(luò)套接字(也就是網(wǎng)絡(luò)上的數(shù)據(jù)通過網(wǎng)口+網(wǎng)卡的處理)。
過程本來是很復(fù)雜的,所以大學(xué)課程里要通過《操作系統(tǒng)》、《計算機(jī)組成原理》來專門講計算機(jī)的軟硬件。
簡化版讀操作流程那么細(xì)的沒辦法講來,所以,我們把這個讀寫過程簡化一下,忽略大多數(shù)細(xì)節(jié),只講流程。
圖片
上圖是應(yīng)用程序進(jìn)行一次讀操作的過程。
應(yīng)用程序先發(fā)起讀操作,準(zhǔn)備讀取數(shù)據(jù)了;內(nèi)核將數(shù)據(jù)從硬盤或外部存儲讀取到內(nèi)核緩沖區(qū);內(nèi)核將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶緩沖區(qū);應(yīng)用程序讀取用戶緩沖區(qū)的數(shù)據(jù)進(jìn)行處理加工;詳細(xì)的讀寫操作流程下面是一個更詳細(xì)的 I/O 讀寫過程。這個圖可好用極了,我會借助這個圖來厘清 I/O 操作的一些基礎(chǔ)但非常重要的概念。
圖片
先看一下這個圖,上面紅粉色部分是讀操作,下面藍(lán)色部分是寫操作。
如果一下子看著有點兒迷糊的話,沒關(guān)系,看看下面幾個概念就清楚了。
應(yīng)用程序就是安裝在操作系統(tǒng)上的各種應(yīng)用。
系統(tǒng)內(nèi)核系統(tǒng)內(nèi)核是一些列計算機(jī)的核心資源的集合,不僅包括CPU、總線這些硬件設(shè)備,也包括進(jìn)程管理、文件管理、內(nèi)存管理、設(shè)備驅(qū)動、系統(tǒng)調(diào)用等一些列功能。
外部存儲外部存儲就是指硬盤、U盤等外部存儲介質(zhì)。
內(nèi)核態(tài)內(nèi)核態(tài)是操作系統(tǒng)內(nèi)核運行的模式,當(dāng)操作系統(tǒng)內(nèi)核執(zhí)行特權(quán)指令時,處于內(nèi)核態(tài)。在內(nèi)核態(tài)下,操作系統(tǒng)內(nèi)核擁有最高權(quán)限,可以訪問計算機(jī)的所有硬件資源和敏感數(shù)據(jù),執(zhí)行特權(quán)指令,控制系統(tǒng)的整體運行。內(nèi)核態(tài)提供了操作系統(tǒng)管理和控制計算機(jī)硬件的能力,它負(fù)責(zé)處理系統(tǒng)調(diào)用、中斷、硬件異常等核心任務(wù)。用戶態(tài)這里的用戶可以理解為應(yīng)用程序,這個用戶是對于計算機(jī)的內(nèi)核而言的,對于內(nèi)核來說,系統(tǒng)上的各種應(yīng)用程序會發(fā)出指令來調(diào)用內(nèi)核的資源,這時候,應(yīng)用程序就是內(nèi)核的用戶。
用戶態(tài)是應(yīng)用程序運行的模式,當(dāng)應(yīng)用程序執(zhí)行普通的指令時,處于用戶態(tài)。在用戶態(tài)下,應(yīng)用程序只能訪問自己的內(nèi)存空間和受限的硬件資源,無法直接訪問操作系統(tǒng)的敏感數(shù)據(jù)或控制計算機(jī)的硬件設(shè)備。用戶態(tài)提供了一種安全的運行環(huán)境,確保應(yīng)用程序之間相互隔離,防止惡意程序?qū)ο到y(tǒng)造成影響。模式切換計算機(jī)為了安全性考慮,區(qū)分了內(nèi)核態(tài)和用戶態(tài),應(yīng)用程序不能直接調(diào)用內(nèi)核資源,必須要切換到內(nèi)核態(tài)之后,讓內(nèi)核來調(diào)用,內(nèi)核調(diào)用完資源,再返回給應(yīng)用程序,這個時候,系統(tǒng)在切換會用戶態(tài),應(yīng)用程序在用戶態(tài)下才能處理數(shù)據(jù)。
上述過程其實一次讀和一次寫都分別發(fā)生了兩次模式切換。
圖片
內(nèi)核緩沖區(qū)內(nèi)核緩沖區(qū)指內(nèi)存中專門用來給內(nèi)核直接使用的內(nèi)存空間??梢园阉斫鉃閼?yīng)用程序和外部存儲進(jìn)行數(shù)據(jù)交互的一個中間介質(zhì)。
應(yīng)用程序想要讀外部數(shù)據(jù),要從這里讀。應(yīng)用程序想要寫入外部存儲,要通過內(nèi)核緩沖區(qū)。
用戶緩沖區(qū)用戶緩沖區(qū)可以理解為應(yīng)用程序可以直接讀寫的內(nèi)存空間。因為應(yīng)用程序沒法直接到內(nèi)核讀寫數(shù)據(jù), 所以應(yīng)用程序想要處理數(shù)據(jù),必須先通過用戶緩沖區(qū)。
磁盤緩沖區(qū)磁盤緩沖區(qū)是計算機(jī)內(nèi)存中用于暫存從磁盤讀取的數(shù)據(jù)或?qū)?shù)據(jù)寫入磁盤之前的臨時存儲區(qū)域。它是一種優(yōu)化磁盤 I/O 操作的機(jī)制,通過利用內(nèi)存的快速訪問速度,減少對慢速磁盤的頻繁訪問,提高數(shù)據(jù)讀取和寫入的性能和效率。
PageCachePageCache 是 Linux 內(nèi)核對文件系統(tǒng)進(jìn)行緩存的一種機(jī)制。它使用空閑內(nèi)存來緩存從文件系統(tǒng)讀取的數(shù)據(jù)塊,加速文件的讀取和寫入操作。當(dāng)應(yīng)用程序或進(jìn)程讀取文件時,數(shù)據(jù)會首先從文件系統(tǒng)讀取到 PageCache 中。如果之后再次讀取相同的數(shù)據(jù),就可以直接從 PageCache 中獲取,避免了再次訪問文件系統(tǒng)。同樣,當(dāng)應(yīng)用程序或進(jìn)程將數(shù)據(jù)寫入文件時,數(shù)據(jù)會先暫存到 PageCache 中,然后由 Linux 內(nèi)核異步地將數(shù)據(jù)寫入磁盤,從而提高寫入操作的效率。再說數(shù)據(jù)讀寫操作流程上面弄明白了這幾個概念后,再回過頭看一下那個流程圖,是不是就清楚多了。
讀操作首先應(yīng)用程序向內(nèi)核發(fā)起讀請求,這時候進(jìn)行一次模式切換了,從用戶態(tài)切換到內(nèi)核態(tài);內(nèi)核向外部存儲或網(wǎng)絡(luò)套接字發(fā)起讀操作;將數(shù)據(jù)寫入磁盤緩沖區(qū);系統(tǒng)內(nèi)核將數(shù)據(jù)從磁盤緩沖區(qū)拷貝到內(nèi)核緩沖區(qū),順便再將一份(或者一部分)拷貝到 PageCache;內(nèi)核將數(shù)據(jù)拷貝到用戶緩沖區(qū),供應(yīng)用程序處理。此時又進(jìn)行一次模態(tài)切換,從內(nèi)核態(tài)切換回用戶態(tài);寫操作應(yīng)用程序向內(nèi)核發(fā)起寫請求,這時候進(jìn)行一次模式切換了,從用戶態(tài)切換到內(nèi)核態(tài);內(nèi)核將要寫入的數(shù)據(jù)從用戶緩沖區(qū)拷貝到 PageCache,同時將數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū);然后內(nèi)核將數(shù)據(jù)寫入到磁盤緩沖區(qū),從而寫入磁盤,或者直接寫入網(wǎng)絡(luò)套接字。瓶頸在哪里但是傳統(tǒng)I/O有它的瓶頸,這才是零拷貝技術(shù)出現(xiàn)的緣由。瓶頸是啥呢,當(dāng)然是性能問題,太慢了。尤其是在高并發(fā)場景下,I/O性能經(jīng)常會卡脖子。
那是什么地方耗時了呢?
數(shù)據(jù)拷貝在傳統(tǒng) I/O 中,數(shù)據(jù)的傳輸通常涉及多次數(shù)據(jù)拷貝。數(shù)據(jù)需要從應(yīng)用程序的用戶緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū),然后再從內(nèi)核緩沖區(qū)復(fù)制到設(shè)備或網(wǎng)絡(luò)緩沖區(qū)。這些數(shù)據(jù)拷貝過程導(dǎo)致了多次內(nèi)存訪問和數(shù)據(jù)復(fù)制,消耗了大量的 CPU 時間和內(nèi)存帶寬。
用戶態(tài)和內(nèi)核態(tài)的切換由于數(shù)據(jù)要經(jīng)過內(nèi)核緩沖區(qū),導(dǎo)致數(shù)據(jù)在用戶態(tài)和內(nèi)核態(tài)之間來回切換,切換過程中會有上下文的切換,如此一來,大大增加了處理數(shù)據(jù)的復(fù)雜性和時間開銷。
每一次操作耗費的時間雖然很小,但是當(dāng)并發(fā)量高了以后,積少成多,也是不小的開銷。所以要提高性能、減少開銷就要從以上兩個問題下手了。
這時候,零拷貝技術(shù)就出來解決問題了。
什么是零拷貝問題出來數(shù)據(jù)拷貝和模態(tài)切換上。
但既然是 I/O 操作,不可能沒有數(shù)據(jù)拷貝的,只能減少拷貝的次數(shù),還有就是盡量將數(shù)據(jù)存儲在離應(yīng)用程序(用戶緩沖區(qū))更近的地方。
而區(qū)分用戶態(tài)和內(nèi)核態(tài)有其他更重要的原因,不可能單純?yōu)榱?I/O 效率就改變這種設(shè)計吧。那也只能盡量減少切換的次數(shù)。
零拷貝的理想狀態(tài)就是操作數(shù)據(jù)不用拷貝,但是顯示情況下并不一定真的就是一次復(fù)制操作都沒有,而是盡量減少拷貝操作的次數(shù)。
要實現(xiàn)零拷貝,應(yīng)該從下面這三個方面入手:
盡量減少數(shù)據(jù)在各個存儲區(qū)域的復(fù)制操作,例如從磁盤緩沖區(qū)到內(nèi)核緩沖區(qū)等;盡量減少用戶態(tài)和內(nèi)核態(tài)的切換次數(shù)及上下文切換;使用一些優(yōu)化手段,例如對需要操作的數(shù)據(jù)先緩存起來,內(nèi)核中的 PageCache 就是這個作用;實現(xiàn)零拷貝方案直接內(nèi)存訪問(DMA)DMA 是一種硬件特性,允許外設(shè)(如網(wǎng)絡(luò)適配器、磁盤控制器等)直接訪問系統(tǒng)內(nèi)存,而無需通過 CPU 的介入。在數(shù)據(jù)傳輸時,DMA 可以直接將數(shù)據(jù)從內(nèi)存?zhèn)鬏數(shù)酵庠O(shè),或者從外設(shè)傳輸數(shù)據(jù)到內(nèi)存,避免了數(shù)據(jù)在用戶態(tài)和內(nèi)核態(tài)之間的多次拷貝。
圖片
DMA1
如上圖所示,內(nèi)核將數(shù)據(jù)讀取的大部分?jǐn)?shù)據(jù)讀取操作都交個了 DMA 控制器,而空出來的資源就可以去處理其他的任務(wù)了。
sendfile一些操作系統(tǒng)(例如 Linux)提供了特殊的系統(tǒng)調(diào)用,如 sendfile,在網(wǎng)絡(luò)傳輸文件時實現(xiàn)零拷貝。通過 sendfile,應(yīng)用程序可以直接將文件數(shù)據(jù)從文件系統(tǒng)傳輸?shù)骄W(wǎng)絡(luò)套接字或者目標(biāo)文件,而無需經(jīng)過用戶緩沖區(qū)和內(nèi)核緩沖區(qū)。
如果不用sendfile,如果將A文件寫入B文件。
需要先將A文件的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū),再從內(nèi)核緩沖區(qū)拷貝到用戶緩沖區(qū);然后內(nèi)核再將用戶緩沖區(qū)的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū),之后才能寫入到B文件;而用了sendfile,用戶緩沖區(qū)和內(nèi)核緩沖區(qū)的拷貝都不用了,節(jié)省了一大部分的開銷。
共享內(nèi)存使用共享內(nèi)存技術(shù),應(yīng)用程序和內(nèi)核可以共享同一塊內(nèi)存區(qū)域,避免在用戶態(tài)和內(nèi)核態(tài)之間進(jìn)行數(shù)據(jù)拷貝。應(yīng)用程序可以直接將數(shù)據(jù)寫入共享內(nèi)存,然后內(nèi)核可以直接從共享內(nèi)存中讀取數(shù)據(jù)進(jìn)行傳輸,或者反之。
圖片
通過共享一塊兒內(nèi)存區(qū)域,實現(xiàn)數(shù)據(jù)的共享。就像程序中的引用對象一樣,實際上就是一個指針、一個地址。
內(nèi)存映射文件(Memory-mapped Files)內(nèi)存映射文件直接將磁盤文件映射到應(yīng)用程序的地址空間,使得應(yīng)用程序可以直接在內(nèi)存中讀取和寫入文件數(shù)據(jù),這樣一來,對映射內(nèi)容的修改就是直接的反應(yīng)到實際的文件中。
當(dāng)文件數(shù)據(jù)需要傳輸時,內(nèi)核可以直接從內(nèi)存映射區(qū)域讀取數(shù)據(jù)進(jìn)行傳輸,避免了數(shù)據(jù)在用戶態(tài)和內(nèi)核態(tài)之間的額外拷貝。
雖然看上去感覺和共享內(nèi)存沒什么差別,但是兩者的實現(xiàn)方式完全不同,一個是共享地址,一個是映射文件內(nèi)容。
Java 實現(xiàn)零拷貝的方式Java 標(biāo)準(zhǔn)的 IO 庫是沒有零拷貝方式的實現(xiàn)的,標(biāo)準(zhǔn)IO就相當(dāng)于上面所說的傳統(tǒng)模式。只是在 Java 推出的 NIO 中,才包含了一套新的 I/O 類,如ByteBuffer和Channel,它們可以在一定程度上實現(xiàn)零拷貝。
ByteBuffer:可以直接操作字節(jié)數(shù)據(jù),避免了數(shù)據(jù)在用戶態(tài)和內(nèi)核態(tài)之間的復(fù)制。
Channel:支持直接將數(shù)據(jù)從文件通道或網(wǎng)絡(luò)通道傳輸?shù)搅硪粋€通道,實現(xiàn)文件和網(wǎng)絡(luò)的零拷貝傳輸。
借助這兩種對象,結(jié)合 NIO 中的API,我們就能在 Java 中實現(xiàn)零拷貝了。
首先我們先用傳統(tǒng) IO 寫一個方法,用來和后面的 NIO 作對比,這個程序的目的很簡單,就是將一個100M左右的PDF文件從一個目錄拷貝到另一個目錄。
public static void ioCopy() { try { File sourceFile = new File(SOURCE_FILE_PATH); File targetFile = new File(TARGET_FILE_PATH); try (FileInputStream fis = new FileInputStream(sourceFile); FileOutputStream fos = new FileOutputStream(targetFile)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } } System.out.println("傳輸 " + formatFileSize(sourceFile.length()) + " 字節(jié)到目標(biāo)文件"); } catch (IOException e) { e.printStackTrace(); }}
下面是這個拷貝程序的執(zhí)行結(jié)果,109.92M,耗時1.29秒。
FileChannel.transferTo() 和 transferFrom()“
傳輸 109.92 M 字節(jié)到目標(biāo)文件 耗時: 1.290 秒
”
FileChannel 是一個用于文件讀寫、映射和操作的通道,同時它在并發(fā)環(huán)境下是線程安全的,基于 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 getChannel() 方法可以創(chuàng)建并打開一個文件通道。FileChannel 定義了 transferFrom() 和 transferTo() 兩個抽象方法,它通過在通道和通道之間建立連接實現(xiàn)數(shù)據(jù)傳輸?shù)摹?/p>
這兩個方法首選用 sendfile 方式,只要當(dāng)前操作系統(tǒng)支持,就用 sendfile,例如Linux或MacOS。如果系統(tǒng)不支持,例如windows,則采用內(nèi)存映射文件的方式實現(xiàn)。
transferTo()下面是一個 transferTo 的例子,仍然是拷貝那個100M左右的 PDF,我的系統(tǒng)是 MacOS。
public static void nioTransferTo() { try { File sourceFile = new File(SOURCE_FILE_PATH); File targetFile = new File(TARGET_FILE_PATH); try (FileChannel sourceChannel = new RandomAccessFile(sourceFile, "r").getChannel(); FileChannel targetChannel = new RandomAccessFile(targetFile, "rw").getChannel()) { long transferredBytes = sourceChannel.transferTo(0, sourceChannel.size(), targetChannel); System.out.println("傳輸 " + formatFileSize(transferredBytes) + " 字節(jié)到目標(biāo)文件"); } } catch (IOException e) { e.printStackTrace(); }}
只耗時0.536秒,快了一倍。
傳輸 109.92 M 字節(jié)到目標(biāo)文件 耗時: 0.536 秒
transferFrom()下面是一個 transferFrom 的例子,仍然是拷貝那個100M左右的 PDF,我的系統(tǒng)是 MacOS。
public static void nioTransferFrom() { try { File sourceFile = new File(SOURCE_FILE_PATH); File targetFile = new File(TARGET_FILE_PATH); try (FileChannel sourceChannel = new RandomAccessFile(sourceFile, "r").getChannel(); FileChannel targetChannel = new RandomAccessFile(targetFile, "rw").getChannel()) { long transferredBytes = targetChannel.transferFrom(sourceChannel, 0, sourceChannel.size()); System.out.println("傳輸 " + formatFileSize(transferredBytes) + " 字節(jié)到目標(biāo)文件"); } } catch (IOException e) { e.printStackTrace(); }}
執(zhí)行時間
傳輸 109.92 M 字節(jié)到目標(biāo)文件 耗時: 0.603 秒
Memory-Mapped FilesJava 的 NIO 也支持內(nèi)存映射文件(Memory-mapped Files),通過FileChannel.map()實現(xiàn)。
下面是一個FileChannel.map()的例子,仍然是拷貝那個100M左右的 PDF,我的系統(tǒng)是 MacOS。
public static void nioMap(){ try { File sourceFile = new File(SOURCE_FILE_PATH); File targetFile = new File(TARGET_FILE_PATH); try (FileChannel sourceChannel = new RandomAccessFile(sourceFile, "r").getChannel(); FileChannel targetChannel = new RandomAccessFile(targetFile, "rw").getChannel()) { long fileSize = sourceChannel.size(); MappedByteBuffer buffer = sourceChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize); targetChannel.write(buffer); System.out.println("傳輸 " + formatFileSize(fileSize) + " 字節(jié)到目標(biāo)文件"); } } catch (IOException e) { e.printStackTrace(); } }
執(zhí)行時間:
傳輸 109.92 M 字節(jié)到目標(biāo)文件 耗時: 0.663 秒
關(guān)鍵詞:
大眾朗逸多久換一次機(jī)油比較好(大眾朗逸多久換一次機(jī)油?)
大眾朗逸(參數(shù)|詢價)5000公里或者半年換一次機(jī)油,機(jī)油的作用是:緩解
金輝集團(tuán):“21金輝02”將于7月31日進(jìn)行回售兌付 發(fā)行總額8.5億元
本期債券簡稱為“21金輝02”,債券代碼為188473,發(fā)行總額為8 50億元,
金嶺礦業(yè):7月11日融資買入181.08萬元,融資融券余額1.57億元
7月11日,金嶺礦業(yè)(000655)融資買入181 08萬元,融資償還174 77萬元
臺軍緊盯:38架次解放軍軍機(jī)持續(xù)在臺海周邊活動
38架次解放軍軍機(jī)持續(xù)在臺海周邊活動
篩選:北京那個醫(yī)院割包皮好{口碑榜}北京看男科去哪家醫(yī)院好
篩選:北京那個醫(yī)院割包皮好{口碑榜}北京看男科去哪家醫(yī)院好得少精
關(guān)于我們 加入我們 聯(lián)系我們 商務(wù)合作 粵ICP備2022077823號
創(chuàng)氪網(wǎng) www.m.cn-everich.com 版權(quán)所有 技術(shù)支持:廣州中創(chuàng)互聯(lián)網(wǎng)信息服務(wù)有限公司
投稿投訴聯(lián)系郵箱:317 493 128 @qq.com