家人們,這期的知識點可能會讓你掉幾根頭發,為了盡可能讓你理解,進入正題前我們先來看個栗子
給女朋友送禮物
如果,我是說如果啊,你有一個女朋友(沒有的不要灰心,認真看完文章可以找博主領取)
情人節來了,你想在網上給女朋友買個禮物,但是吧,網上現成的禮物直接送過去顯得沒誠意,為了避免被發好人卡,你想先買回來DIY一下再送
受疫情影響,快遞員不能進小區,你也不能出小區,快遞員先把禮物放到小區快遞點,你去快遞點拿,拿回家DIY完后再放回快遞點,快遞員再從快遞點拿到禮物送到女朋友家
簡單畫了個流程圖

看看這個流程,有太多不合理的地方,人的時間和精力是寶貴的,應該用來打游戲,而不是送 / 拿快遞
時間過了大概好幾年,物流公司研發了送快遞機器人,從此以后快遞小哥不用再上門送件,于是流程變成了

有了快遞機器人,快遞員解放了,但是你還需要去快遞點拿禮物,DIY完了還得送回快遞點,顯然也不合理,有這時間我玩把中單亞索不香嗎
于是你給禮物店老板提了個需求:能不能幫我DIY,然后直接寄到我女朋友家
老板覺得有道理,推出了新服務:需要DIY的用戶,只需要在訂單備注即可
于是流程變成了

你下單的時候,備注讓店家幫你DIY,完事兒直接寄到女朋友家,女朋友收到快遞感動到流了兩斤眼淚
至此,你的兩次出門也省了
回顧一下
從送禮物整個程序來看,最開始快遞員需要跑路兩次,你也需要跑路兩次,到現在只需要機器人跑路兩次
簡單來說四次跑路變成了兩次跑路,而這兩次還都是機器人來完成,所以整個程序的人為跑路次數由4次變成了0次,這個程序我姑且稱之 為零跑路
ok該來的始侄訓是來了,上面這個例子只是想引起你的興趣,想要理解零拷貝,我們現在把術語代入
- 你家 ==> 應用程式快取
- 快遞點 ==> 作業系統內核快取
- durex旗艦店 ==> 磁盤
- 女朋友家 ==> 目標磁盤或者socket
為什么要實作零拷貝
背景:檔案操作屬于危險操作,作業系統不可能直接把權限交給應用程式或者用戶,只有內核才有權限
應用程式操作檔案如復制、傳輸時,需要經歷一下幾個步驟:
從磁盤把檔案復制到系統內核
從系統內核復制到應用程式
從應用程式復制到系統內核
從系統內核復制到磁盤、socket等消費端
簡單畫個流程圖
說明:本文重點在于幫助大家理解零拷貝,關于作業系統內核方面的知識過于偏題,所以流程圖畫的很簡單,如 “作業系統內核快取” 其實還可細分為 “PageCache” 和 “Socket 緩沖區”,類似這些細節就不畫了,而且畫復雜了看著頭疼
但請放心,雖然簡單,但重點都沒省略,絲毫不影響理解零拷貝

上面1、2、3、4幾個步驟都是由cpu來完成,我們知道,cpu運算效率和磁盤不是一個量級
對服務器而言,cpu資源是很稀缺的,這四個復制操作一直占用cpu,顯然會拉低整個系統性能
于是計算機界大佬們想了個辦法,在主板上加了一塊元件,叫 DMA(全稱Direct Memory Access Controller,直接記憶體訪問控制器),它是一塊物理硬體,專門用于磁盤到作業系統內核快取之間的資料復制
于是流程變成

紅字解釋:目前的網卡幾乎都支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術(可以用ethtool -k eth0 | grep scatter-gather命令查看是否支持),關于這點了解即可,不需要深入研究,這已經是嵌入式開發領域的知識點了,咱沒必要花太多時間在上面
如果到現在有點懵,沒關系,你只需要記住,有了DMA,cpu不再親自參與整個資料復制或傳輸程序,只需要給DMA發送指令,告知它應該把哪個檔案復制或傳輸到哪里即可,等DMA完成資料操作后通知cpu
再來說說DMA吧,DMA的實作依賴于作業系統和硬體:
作業系統方面,os提供了sendfile()函式

它接收四個引數:target端檔案描述符、source端檔案描述符、偏移量、資料長度
我們沒有必要研究如何呼叫這個函式,因為Java已經幫我們做好了封裝

你現在不需要管這個方法在哪個類中,我后面會寫代碼演示
kafka為什么能做到百萬級吞吐量,靠的就是零拷貝模型,如果你仔細看過kafka原始碼,你會發現它最終呼叫了transferTo()方法
硬體方面,主板上增加了DMAC元件,其實不止主板,計算機發展到今天,幾乎所有牽涉到io操作的地方都集成了DMAC
至此,我們可以做個小總結:所謂的零拷貝技術,其實可以理解成軟體、硬體、語言的結合,目的在于減少cpu等待時間,提升資料傳輸效率
Talk is cheap,Show me the code
我們以復制檔案為例,測驗一下零拷貝模型帶來的性能提升
新建一個測驗檔案

600MB

先來嘗試下傳統io復制(代碼放在最后)

600MB花了800ms,這已經算是io中很快的了,如果用不帶buffer的普通io,時間還得加3倍左右
再來看看零拷貝的nio

300ms左右,相對io提升了一倍多,看著不咋地,你想想高并發場景下,動不動上萬次類似操作,這節約下來的時間足以讓甲方激動到拍打輪椅
代碼:
public class ZeroCopyTest {
public static void main(String[] args) throws IOException, InterruptedException {
long start = System.currentTimeMillis();
File source = new File("D:/fileTest/copy.txt");
File target = new File("D:/fileTest/copy2.txt");
ioCopy(source, target);
// ioCopyWithBuffer(source, target);
// nioCopy(source, target);
System.out.println(System.currentTimeMillis() - start);
Thread.sleep(200);
target.delete();
}
public static void ioCopy(File source, File target) throws IOException {
try (InputStream is = new FileInputStream(source);
OutputStream os = new FileOutputStream(target)) {
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
}
}
public static void ioCopyWithBuffer(File source, File target) throws IOException {
try (InputStream is = new BufferedInputStream(new FileInputStream(source));
OutputStream os = new BufferedOutputStream(new FileOutputStream(target))) {
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
}
}
public static void nioCopy(File source, File target) throws IOException {
try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
FileChannel targetChannel = new FileOutputStream(target).getChannel()) {
for (long count = sourceChannel.size(); count > 0; ) {
long transferred = sourceChannel.transferTo(sourceChannel.position(), count, targetChannel);
sourceChannel.position(sourceChannel.position() + transferred);
count -= transferred;
}
}
}
}
兩根頭發又無了,嚶嚶嚶~
前面說的領女朋友,其實,是騙你們的

ok我話說完
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/430295.html
標籤:其他
上一篇:Azure云平臺 GPS大資料解決方案 EventHub+Azure Databricks+Azure Cosmos DB Cassandra
