Zero Copy 简介
生活随笔
收集整理的這篇文章主要介紹了
Zero Copy 简介
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
Zero Copy 簡(jiǎn)介
標(biāo)簽: buffersocketdescriptoruserapplicationlinux內(nèi)核 2012-05-04 13:33 5154人閱讀 評(píng)論(3) 收藏 舉報(bào)許多web應(yīng)用都會(huì)向用戶(hù)提供大量的靜態(tài)內(nèi)容,這意味著有很多data從硬盤(pán)讀出之后,會(huì)原封不動(dòng)的通過(guò)socket傳輸給用戶(hù)。這種操作看起來(lái)可能不會(huì)怎么消耗CPU,但是實(shí)際上它是低效的:kernal把數(shù)據(jù)從disk讀出來(lái),然后把它傳輸給user級(jí)的application,然后application再次把同樣的內(nèi)容再傳回給處于kernal級(jí)的socket。這種場(chǎng)景下,application實(shí)際上只是作為一種低效的中間介質(zhì),用來(lái)把disk file的data傳給socket。
data每次穿過(guò)user-kernel boundary,都會(huì)被copy,這會(huì)消耗cpu,并且占用RAM的帶寬。幸運(yùn)的是,你可以用一種叫做Zero-Copy的技術(shù)來(lái)去掉這些無(wú)謂的copy。應(yīng)用程序用zero copy來(lái)請(qǐng)求kernel直接把disk的data傳輸給socket,而不是通過(guò)應(yīng)用程序傳輸。Zero copy大大提高了應(yīng)用程序的性能,并且減少了kernel和user模式的上下文切換。
Java的libaries在linux和unix中支持zero copy,一個(gè)關(guān)鍵的api是java.nio.channel.FileChannel的transferTo()方法。我們可以用transferTo()來(lái)把bytes直接從調(diào)用它的channel傳輸?shù)搅硪粋€(gè)writable byte channel,中間不會(huì)使data經(jīng)過(guò)應(yīng)用程序。本文首先描述傳統(tǒng)的copy是怎樣坑爹的,然后再展示zero-copy技術(shù)在性能上是多么的給力以及為什么給力。
Date transfer: The traditional approach
考慮一下這個(gè)場(chǎng)景,通過(guò)網(wǎng)絡(luò)把一個(gè)文件傳輸給另一個(gè)程序。這個(gè)操作的核心代碼就是下面的兩個(gè)函數(shù):
Listing 1. Copying bytes from a file to a socket
File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);
盡管看起來(lái)很簡(jiǎn)單,但是在OS的內(nèi)部,這個(gè)copy操作要經(jīng)歷四次user mode和kernel mode之間的上下文切換,甚至連數(shù)據(jù)都被拷貝了四次!Figure 1描述了data是怎么移動(dòng)的。
Figure 2 描述了上下文切換
Figure 2. Traditional context switches
其中的步驟如下:
read() 引入了一次從user mode到kernel mode的上下文切換。實(shí)際上調(diào)用了sys_read() 來(lái)從文件中讀取data。第一次copy由DMA完成,將文件內(nèi)容從disk讀出,存儲(chǔ)在kernel的buffer中。
然后data被copy到user buffer中,此時(shí)read()成功返回。這是觸發(fā)了第二次context switch: 從kernel到user。至此,數(shù)據(jù)存儲(chǔ)在user的buffer中。
send() socket call 帶來(lái)了第三次context switch,這次是從user mode到kernel mode。同時(shí),也發(fā)生了第三次copy:把data放到了kernel adress space中。當(dāng)然,這次的kernel buffer和第一步的buffer是不同的兩個(gè)buffer。
最終 send() system call 返回了,同時(shí)也造成了第四次context switch。同時(shí)第四次copy發(fā)生,DMA將data從kernel buffer拷貝到protocol engine中。第四次copy是獨(dú)立而且異步的。
使用kernel buffer做中介(而不是直接把data傳到user buffer中)看起來(lái)比較低效(多了一次copy)。然而實(shí)際上kernel buffer是用來(lái)提高性能的。在進(jìn)行讀操作的時(shí)候,kernel buffer起到了預(yù)讀cache的作用。當(dāng)寫(xiě)請(qǐng)求的data size比kernel buffer的size小的時(shí)候,這能夠顯著的提升性能。在進(jìn)行寫(xiě)操作時(shí),kernel buffer的存在可以使得寫(xiě)請(qǐng)求完全異步。
悲劇的是,當(dāng)請(qǐng)求的data size遠(yuǎn)大于kernel buffer size的時(shí)候,這個(gè)方法本身變成了性能的瓶頸。因?yàn)閐ata需要在disk,kernel buffer,user buffer之間拷貝很多次(每次寫(xiě)滿(mǎn)整個(gè)buffer)。
而Zero copy正是通過(guò)消除這些多余的data copy來(lái)提升性能。
Data Transfer:The Zero Copy Approach
如果重新檢查一遍traditional approach,你會(huì)注意到實(shí)際上第二次和第三次copy是毫無(wú)意義的。應(yīng)用程序僅僅緩存了一下data就原封不動(dòng)的把它發(fā)回給socket buffer。實(shí)際上,data應(yīng)該直接在read buffer和socket buffer之間傳輸。transferTo()方法正是做了這樣的操作。Listing 2是transferTo()的函數(shù)原型:
public void transferTo(long position, long count, WritableByteChannel target);
transferTo()方法把data從file channel傳輸?shù)街付ǖ膚ritable byte channel。它需要底層的操作系統(tǒng)支持zero copy。在UNIX和各種Linux中,會(huì)執(zhí)行List 3中的系統(tǒng)調(diào)用sendfile(),該命令把data從一個(gè)文件描述符傳輸?shù)搅硪粋€(gè)文件描述符(Linux中萬(wàn)物皆文件):
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
在List 1中的file.read()和socket.send()可以用一句transferTo()替代,如List 4:
transferTo(position, count, writableChannel);
Figure 3 展示了在使用transferTo()之后的數(shù)據(jù)流向
Figure 4 展示了在使用transferTo()之后的上下文切換
在像Listing 4那樣使用transferTo()之后,整個(gè)過(guò)程如下:
transferTo()方法使得文件內(nèi)容被DMA engine直接copy到一個(gè)read buffer中。然后數(shù)據(jù)被kernel再次拷貝到和output socket相關(guān)聯(lián)的那個(gè)kernel buffer中去。
第三次拷貝由DMA engine完成,它把kernel buffer中的data拷貝到protocol engine中。
這是一個(gè)很明顯的進(jìn)步:我們把context switch的次數(shù)從4次減少到了2次,同時(shí)也把data copy的次數(shù)從4次降低到了3次(而且其中只有一次占用了CPU,另外兩次由DMA完成)。但是,要做到zero copy,這還差得遠(yuǎn)。如果網(wǎng)卡支持 gather operation,我們可以通過(guò)kernel進(jìn)一步減少數(shù)據(jù)的拷貝操作。在2.4及以上版本的linux內(nèi)核中,開(kāi)發(fā)者修改了socket buffer descriptor來(lái)適應(yīng)這一需求。這個(gè)方法不僅減少了context switch,還消除了和CPU有關(guān)的數(shù)據(jù)拷貝。user層面的使用方法沒(méi)有變,但是內(nèi)部原理卻發(fā)生了變化:
transferTo()方法使得文件內(nèi)容被copy到了kernel buffer,這一動(dòng)作由DMA engine完成。
沒(méi)有data被copy到socket buffer。取而代之的是socket buffer被追加了一些descriptor的信息,包括data的位置和長(zhǎng)度。然后DMA engine直接把data從kernel buffer傳輸?shù)絧rotocol engine,這樣就消除了唯一的一次需要占用CPU的拷貝操作。
Figure 5描述了新的transferTo()方法中的data copy:
總結(jié)
以上是生活随笔為你收集整理的Zero Copy 简介的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: OpenCV中霍夫圆检测
- 下一篇: opencv 车道线检测(一)