通过零拷贝实现高效的数据传输(操作系统)
許多Web應(yīng)用程序提供大量靜態(tài)內(nèi)容,這相當(dāng)于從磁盤讀取數(shù)據(jù)并將完全相同的數(shù)據(jù)寫回到響應(yīng)套接字。此活動似乎只需要相對較少的CPU活動,但效率有點低下:內(nèi)核從磁盤讀取數(shù)據(jù)并將其跨越內(nèi)核用戶邊界推送到應(yīng)用程序,然后應(yīng)用程序?qū)⑵渫苹氐絻?nèi)核用戶邊界寫出到插座。實際上,應(yīng)用程序作為一個低效的媒介,將數(shù)據(jù)從磁盤文件獲取到套接字。
每次數(shù)據(jù)遍歷用戶內(nèi)核邊界時,都必須復(fù)制它,這會消耗CPU周期和內(nèi)存帶寬。幸運的是,您可以通過一種名為 - 足夠恰當(dāng) -?零拷貝的技術(shù)來消除這些副本。使用零拷貝請求的應(yīng)用程序,內(nèi)核將數(shù)據(jù)直接從磁盤文件復(fù)制到套接字,而不通過應(yīng)用程序。零拷貝大大提高了應(yīng)用程序的性能,并減少了內(nèi)核和用戶模式之間的上下文切換次數(shù)。
Java類庫通過transferTo()in方法在?Linux和UNIX系統(tǒng)上支持零拷貝java.nio.channels.FileChannel。您可以使用該transferTo()方法將字節(jié)從其調(diào)用的通道直接傳輸?shù)搅硪粋€可寫字節(jié)通道,而不需要數(shù)據(jù)流經(jīng)應(yīng)用程序。本文首先演示通過傳統(tǒng)的復(fù)制語義完成簡單文件傳輸所帶來的開銷,然后展示如何使用零復(fù)制技術(shù)?transferTo()實現(xiàn)更好的性能。
日期轉(zhuǎn)移:傳統(tǒng)方法
考慮從文件讀取并通過網(wǎng)絡(luò)將數(shù)據(jù)傳輸?shù)搅硪粋€程序的場景。(本場景描述了許多服務(wù)器應(yīng)用程序的行為,包括提供靜態(tài)內(nèi)容的Web應(yīng)用程序,FTP服務(wù)器,郵件服務(wù)器等等)。操作的核心在清單1中的兩個調(diào)用中(請參見下載以獲取指向完整的示例代碼):
清單1.將文件中的字節(jié)復(fù)制到套接字
| 12 | File.read(fileDesc, buf, len);Socket.send(socket, buf, len); |
雖然清單1在概念上很簡單,但在內(nèi)部,復(fù)制操作需要在用戶模式和內(nèi)核模式之間切換四次上下文,并且在操作完成之前將數(shù)據(jù)復(fù)制四次。圖1顯示了數(shù)據(jù)如何從文件內(nèi)部移動到套接字:
圖1.傳統(tǒng)數(shù)據(jù)復(fù)制方法
圖2顯示了上下文切換:
圖2.傳統(tǒng)的上下文切換
涉及的步驟是:
使用中間內(nèi)核緩沖區(qū)(而不是直接將數(shù)據(jù)傳輸?shù)接脩艟彌_區(qū)中)可能看起來效率低下。但是,中間內(nèi)核緩沖區(qū)被引入到進(jìn)程中以提高性能。在應(yīng)用程序沒有要求與內(nèi)核緩沖區(qū)一樣多的數(shù)據(jù)時,在讀取端使用中間緩沖區(qū)允許內(nèi)核緩沖區(qū)充當(dāng)“預(yù)讀緩存”。當(dāng)請求的數(shù)據(jù)量小于內(nèi)核緩沖區(qū)大小時,這會顯著提高性能。寫入側(cè)的中間緩沖區(qū)允許寫入異步完成。
不幸的是,如果所請求數(shù)據(jù)的大小遠(yuǎn)遠(yuǎn)大于內(nèi)核緩沖區(qū)大小,這種方法本身可能會成為性能瓶頸。在磁盤,內(nèi)核緩沖區(qū)和用戶緩沖區(qū)最終傳送到應(yīng)用程序之前,數(shù)據(jù)被復(fù)制多次。
零拷貝通過消除這些冗余數(shù)據(jù)副本來提高性能。
數(shù)據(jù)傳輸:零拷貝方法
如果您重新檢查傳統(tǒng)方案,您會注意到第二個和第三個數(shù)據(jù)副本實際上不是必需的。應(yīng)用程序除了緩存數(shù)據(jù)并將其傳回到套接字緩沖區(qū)之外別無其他。相反,數(shù)據(jù)可以直接從讀緩沖區(qū)傳輸?shù)教捉幼志彌_區(qū)。該transferTo()?方法可以讓你做到這一點。清單2顯示了以下方法的簽名?transferTo():
清單2.?transferTo()?方法
| 1 | public void transferTo(long position, long count, WritableByteChannel target); |
該transferTo()方法將數(shù)據(jù)從文件通道傳輸?shù)浇o定的可寫字節(jié)通道。在內(nèi)部,它取決于底層操作系統(tǒng)對零拷貝的支持;?在UNIX和各種Linux中,這個調(diào)用被路由到sendfile()?系統(tǒng)調(diào)用,如清單3所示,它將數(shù)據(jù)從一個文件描述符傳輸?shù)搅硪粋€文件描述符:
清單3.?sendfile()系統(tǒng)調(diào)用
| 12 | #include <sys/socket.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); |
清單1中的file.read()和socket.send()?調(diào)用的動作可以被一個調(diào)用所取代?,如清單4所示:transferTo()
清單4.使用?transferTo()將數(shù)據(jù)從磁盤文件復(fù)制到套接字
| 1 | transferTo(position, count, writableChannel); |
圖3顯示了transferTo()使用該方法時的數(shù)據(jù)路徑:
圖3.使用數(shù)據(jù)拷貝?transferTo()
圖4顯示了transferTo()?使用該方法時的上下文切換:
圖4.用上下文切換?transferTo()
transferTo()在清單4中使用時采取的步驟如下:
這是一個改進(jìn):我們已將上下文切換次數(shù)從四次減少到兩次,并將數(shù)據(jù)副本數(shù)量從四個減少到三個(其中只有一個涉及CPU)。但是,這還沒有讓我們達(dá)到零拷貝的目標(biāo)。如果底層網(wǎng)絡(luò)接口卡支持收集操作,我們可以進(jìn)一步減少內(nèi)核所做的數(shù)據(jù)復(fù)制。在Linux內(nèi)核2.4和更高版本中,套接字緩沖區(qū)描述符已被修改以適應(yīng)此要求。這種方法不僅減少了多個上下文切換,還消除了需要CPU參與的重復(fù)數(shù)據(jù)副本。用戶端使用率仍然保持不變,但內(nèi)在因素已發(fā)生變化:
圖5顯示了使用transferTo()收集操作的數(shù)據(jù)副本:
圖5.?transferTo()使用收集操作時的數(shù)據(jù)拷貝
建立一個文件服務(wù)器
現(xiàn)在讓我們將零拷貝付諸實踐,使用在客戶端和服務(wù)器之間傳輸文件的相同示例(請參閱下載以獲取示例代碼)。TraditionalClient.java并?TraditionalServer.java基于傳統(tǒng)的復(fù)制語義,使用File.read()和Socket.send()。TraditionalServer.java是一個服務(wù)器程序,它偵聽特定端口以供客戶端連接,然后從套接字一次讀取4K字節(jié)的數(shù)據(jù)。TraditionalClient.java連接到服務(wù)器,File.read()從文件讀取(使用)4K字節(jié)的數(shù)據(jù),并socket.send()通過套接字將內(nèi)容發(fā)送(使用)到服務(wù)器。
同樣,TransferToServer.java并?TransferToClient.java執(zhí)行相同的功能,而是使用transferTo()方法(并進(jìn)而sendfile()系統(tǒng)調(diào)用)將文件從服務(wù)器傳輸?shù)娇蛻舳恕?/span>
性能比較
我們在運行2.6內(nèi)核的Linux系統(tǒng)上執(zhí)行了示例程序,并測量了傳統(tǒng)方法和transferTo()不同尺寸方法的運行時間(以毫秒為單位)。表1顯示了結(jié)果:
表1.性能比較:傳統(tǒng)方法與零拷貝
| 7MB | 156 | 45 |
| 21MB | 337 | 128 |
| 63MB | 843 | 387 |
| 98MB | 1320 | 617 |
| 200MB | 2124 | 1150 |
| 350MB | 3631 | 1762 |
| 700MB | 13498 | 4422 |
| 1GB | 18399 | 8537 |
正如您所看到的,transferTo()與傳統(tǒng)方法相比,API將時間縮短了約65%。這對于大量將數(shù)據(jù)從一個I / O通道復(fù)制到另一個I / O通道的應(yīng)用程序(如Web服務(wù)器)具有顯著提高性能的潛力。
概要
transferTo()與從一個通道讀取數(shù)據(jù)并將相同的數(shù)據(jù)寫入另一個通道相比,我們已經(jīng)證明了使用它的性能優(yōu)勢?。中間緩沖區(qū)副本 - 即使是隱藏在內(nèi)核中的副本 - 可能會產(chǎn)生可衡量的成本。在通道間大量復(fù)制數(shù)據(jù)的應(yīng)用程序中,零復(fù)制技術(shù)可以顯著提高性能。
可下載的資源
- 此內(nèi)容的PDF
- 本文的示例程序(j-zerocopy.zip | 3KB)
相關(guān)主題
- 零拷貝I:用戶模式透視
總結(jié)
以上是生活随笔為你收集整理的通过零拷贝实现高效的数据传输(操作系统)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓手机如何成功登陆谷歌市场?
- 下一篇: 产生线程安全的原因(1)(操作系统)