最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

從數(shù)據(jù)拷貝的角度:如何讓你的程序性能趕超 P8 大牛!

2021-09-14 15:40 作者:C語言編程__Plus  | 我要投稿

文章來源于:微信公眾號丨碼農(nóng)的荒島求生

計(jì)算機(jī)處理的任務(wù)大體可以分為兩類:CPU密集型與IO密集型。

當(dāng)前流行的互聯(lián)網(wǎng)應(yīng)用更多的屬于IO密集型,傳統(tǒng)的IO標(biāo)準(zhǔn)接口都是基于數(shù)據(jù)拷貝的,這篇文章我們主要關(guān)注該怎樣從數(shù)據(jù)拷貝的角度來優(yōu)化IO性能,讓你的程序在IO性能方面趕超P8。


為什么IO接口要基于數(shù)據(jù)拷貝?

為了讓廣大碼農(nóng)們更好的沉迷于自己的一畝三分地,防止ta們分心去關(guān)心計(jì)算機(jī)中的硬件資源分配問題,操作系統(tǒng)誕生了。

操作系統(tǒng)本質(zhì)上就是一個(gè)管家,?目的就是更加公平合理的給各個(gè)進(jìn)程分配硬件資源?,在操作系統(tǒng)出現(xiàn)之前,程序員需要直面各類硬件,就像這樣:


在這一時(shí)期程序員真可謂掌控全局,掌控全局帶來的后果就是你需要掌控所有細(xì)節(jié),這顯然不利于生產(chǎn)力的釋放。

操作系統(tǒng)應(yīng)用而生。

計(jì)算機(jī)系統(tǒng)就變成這樣了:


現(xiàn)在應(yīng)用程序不需要和硬件直接交互了,僅從IO的角度上看,操作系統(tǒng)變成了一個(gè)類似路由器的角色,把應(yīng)用程序遞交過來的數(shù)據(jù)分發(fā)到具體的硬件上去,或者從硬件接收數(shù)據(jù)并分發(fā)給相應(yīng)的進(jìn)程。

數(shù)據(jù)傳遞是通過什么呢?就是我們常說的buffer,所謂buffer就是一塊可用的內(nèi)存空間,用來暫存數(shù)據(jù)。


操作系統(tǒng)這一中間商導(dǎo)致的問題就是:?你需要首先把東西交給操作系統(tǒng),操作系統(tǒng)再轉(zhuǎn)手交給硬件?,這就必然涉及到數(shù)據(jù)拷貝。

這就是為什么傳統(tǒng)的IO操作必然需要進(jìn)行數(shù)據(jù)拷貝的原因所在。關(guān)于操作系統(tǒng)系統(tǒng)完整的闡述請參見博主的《深入理解操作系統(tǒng)》。

然而數(shù)據(jù)拷貝是有性能損耗的,接下來我們用一個(gè)實(shí)例來讓大家對該問題有一個(gè)更直觀的認(rèn)知。

網(wǎng)絡(luò)服務(wù)器

瀏覽器打開一個(gè)網(wǎng)頁需要很多數(shù)據(jù),包括看到的圖片、html文件、css文件、js文件等等,當(dāng)瀏覽器請求這類文件時(shí)服務(wù)器端的工作其實(shí)是非常簡單的:服務(wù)器只需要從磁盤中抓出該文件然后丟給網(wǎng)絡(luò)發(fā)送出去。


代碼基本上類似這樣:

read(fileDesc, buf, len);write(socket, buf, len);

這兩段代碼非常簡單,第一行代碼從文件中讀取數(shù)據(jù)存放在buf中,然后將buf中的數(shù)據(jù)通過網(wǎng)絡(luò)發(fā)送出去。

注意觀察buf,服務(wù)器全程沒有對buf中的數(shù)據(jù)進(jìn)行任何修改,buf里的數(shù)據(jù)在用戶態(tài)逛了一圈后揮一揮衣袖沒有帶走半點(diǎn)云彩就回到了內(nèi)核態(tài)。

這兩行看似簡單的代碼實(shí)際上在底層發(fā)生了什么呢?

答案是這樣的:


在程序看來簡單的兩行代碼在底層是比較復(fù)雜的,看到這張圖你應(yīng)該真心感激操作系統(tǒng),操作系統(tǒng)就像一個(gè)無比稱職的管家,替你把所有臟活累活都承擔(dān)下來,好讓你悠閑的在用戶態(tài)指點(diǎn)江山。

這簡單的兩行代碼涉及:?四次數(shù)據(jù)拷貝?以及?四次上下文切換:


read函數(shù)會涉及一次用戶態(tài)到內(nèi)核態(tài)的切換,操作系統(tǒng)會向磁盤發(fā)起一次IO請求,當(dāng)數(shù)據(jù)準(zhǔn)備好后通過DMA技術(shù)把數(shù)據(jù)拷貝到內(nèi)核的buffer中,注意本次數(shù)據(jù)拷貝無需CPU參與。

此后操作系統(tǒng)開始把這塊數(shù)據(jù)從內(nèi)核拷貝到用戶態(tài)的buffer中,此時(shí)read()函數(shù)返回,并從內(nèi)核態(tài)切換回用戶態(tài),到這時(shí)read(fileDesc, buf, len);這行代碼就返回了,buf中裝好了新鮮出爐的數(shù)據(jù)。

接下來send函數(shù)再次導(dǎo)致用戶態(tài)與內(nèi)核態(tài)的切換,此時(shí)數(shù)據(jù)需要從用戶態(tài)buf拷貝到網(wǎng)絡(luò)協(xié)議子系統(tǒng)的buf中,具體點(diǎn)該buf屬于在代碼中使用的這個(gè)socket。

此后send函數(shù)返回,再次由內(nèi)核態(tài)返回到用戶態(tài);此時(shí)在程序員看來數(shù)據(jù)已經(jīng)成功發(fā)出去了,但實(shí)際上數(shù)據(jù)可能依然停留在內(nèi)核中,此后第四次數(shù)據(jù)copy開始,利用DMA技術(shù)把數(shù)據(jù)從socket buf拷貝給網(wǎng)卡,然后真正的發(fā)送出去。

這就是看似簡單的這兩行代碼在底層的完整過程。

你覺得這個(gè)過程有什么問題嗎?

發(fā)現(xiàn)問題

有的同學(xué)肯定已經(jīng)注意到了,既然在用戶態(tài)沒有對數(shù)據(jù)進(jìn)行任何修改,那為什么要這么麻煩的讓數(shù)據(jù)在用戶態(tài)來個(gè)一日游呢?直接在內(nèi)核態(tài)從磁盤給到網(wǎng)卡不就可以了嗎?

恭喜你,答對了!

這種優(yōu)化思路就是所謂的零拷貝技術(shù),Zero Copy。

總體上來看,優(yōu)化數(shù)據(jù)拷貝會有以下三個(gè)方向:

用戶態(tài)不需要真正的去訪問數(shù)據(jù),就像上面這個(gè)示例,用戶態(tài)根本不需要知道buf里面裝的是什么。在這種情況下無需把數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)然后再把數(shù)據(jù)從用戶態(tài)拷貝回內(nèi)核態(tài)。

數(shù)據(jù)無需用戶態(tài)感知,數(shù)據(jù)拷貝完全發(fā)生在內(nèi)核態(tài)。

內(nèi)核態(tài)不要真正的去訪問數(shù)據(jù),用戶態(tài)程序可以繞過內(nèi)核直接和硬件交互,這樣就避免了內(nèi)核的參與,從而減少數(shù)據(jù)拷貝的可能。

內(nèi)核無需感知數(shù)據(jù)。

如果內(nèi)核態(tài)和用戶態(tài)不得不進(jìn)行數(shù)據(jù)交互,則優(yōu)化用戶態(tài)與內(nèi)核態(tài)數(shù)據(jù)的交互方式。

知道了解決問題的思路,我們來看下為了實(shí)現(xiàn)零拷貝,計(jì)算機(jī)系統(tǒng)中都有哪些巧妙的設(shè)計(jì)。

mmap

是的,就是mmap,在《?mmap可以讓程序員實(shí)現(xiàn)哪些騷操作?》一文中我們對其進(jìn)行了詳細(xì)講解,你能想到mmap還可以實(shí)現(xiàn)零拷貝嗎?

對于本文提到的網(wǎng)絡(luò)服務(wù)器我們可以這樣修改代碼:

buf = mmap(file, len);write(socket, buf, len);

你可能會想僅僅將read替換為mmap會有什么優(yōu)化嗎?

如果你真的理解了mmap就會知道,mmap僅僅將文件內(nèi)容映射到了進(jìn)程地址空間中,并沒有真正的拷貝到進(jìn)程地址空間,這節(jié)省了一次從內(nèi)核態(tài)到用戶態(tài)的數(shù)據(jù)拷貝。

同樣的,當(dāng)調(diào)用write時(shí)數(shù)據(jù)直接從內(nèi)核buf拷貝給了socket buf,而不是像read/write方法中把用戶態(tài)數(shù)據(jù)拷貝給socket buf。


我們可以看到,利用mmap我們節(jié)省了一次數(shù)據(jù)拷貝,上下文切換依然是四次。


盡管mmap可以節(jié)省數(shù)據(jù)拷貝,但維護(hù)文件與地址空間的映射關(guān)系也是有代價(jià)的,除非CPU拷貝數(shù)據(jù)的時(shí)間超過維系映射關(guān)系的代價(jià),否則基于mmap的程序性能可能不及傳統(tǒng)的read/write。

此外,如果映射的文件被其它進(jìn)程截?cái)?,在Linux系統(tǒng)下你的進(jìn)程將立即接收到SIGBUS信號,因此這種異常情況也需要正確處理。

除了mmap之外,還有其它辦法也可以實(shí)現(xiàn)零拷貝。

sendfile

你沒有看錯(cuò),在Linux系統(tǒng)下為了解決數(shù)據(jù)拷貝問題專門設(shè)計(jì)了這一系統(tǒng)調(diào)用:


Windows下也有一個(gè)作用類似的API:TransmitFile。

這一系統(tǒng)調(diào)用的目的是在兩個(gè)文件描述之間拷貝數(shù)據(jù),但值得注意的是,數(shù)據(jù)拷貝的過程完全是在內(nèi)核態(tài)完成,因此在網(wǎng)絡(luò)服務(wù)器的這個(gè)例子中我們將把那兩行代碼簡化為一行,也就是調(diào)用這里的sendfile。

使用sendfile將節(jié)省兩次數(shù)據(jù)拷貝,因?yàn)閿?shù)據(jù)無需傳輸?shù)接脩魬B(tài):


調(diào)用sendfile后,首先DMA機(jī)制會把數(shù)據(jù)從磁盤拷貝到內(nèi)核buf中,接下來把數(shù)據(jù)從內(nèi)核buf拷貝到相應(yīng)的socket buf中,最后利用DMA機(jī)制將數(shù)據(jù)從socket buf拷貝到網(wǎng)卡中。

我們可以看到,同使用傳統(tǒng)的read/write相比少了一次數(shù)據(jù)拷貝,而且內(nèi)核態(tài)和用戶態(tài)的切換只有兩次。

有的同學(xué)可能已經(jīng)看出了,這好像不是??拷貝吧,在內(nèi)核中這不是還有一次從內(nèi)核態(tài)buf到socket buf的數(shù)據(jù)拷貝嗎?這次拷貝看上去也是沒有必要的。

的確如此,為解決這一問題,單純的軟件機(jī)制已經(jīng)不夠用了,我們需要硬件來幫一點(diǎn)忙,這就是DMA Gather Copy。

sendfile 與DMA Gather Copy

傳統(tǒng)的DMA機(jī)制必須從一段連續(xù)的空間中傳輸數(shù)據(jù),就像這樣:


很顯然,你需要在源頭上把所有需要的數(shù)據(jù)都拷貝到一段連續(xù)的空間中:


現(xiàn)在肯定有同學(xué)會問,為什么不直接讓DMA可以從多個(gè)源頭收集數(shù)據(jù)呢?


這就是所謂的DMA Gather Copy。

有了這一特性,無需再將內(nèi)核文件buf中的數(shù)據(jù)拷貝到socket buf,而是網(wǎng)卡利用DMA Gather Copy機(jī)制將消息頭以及需要傳輸?shù)臄?shù)據(jù)等直接組裝在一起發(fā)送出去。

在這一機(jī)制的加持下,CPU甚至完全不需要接觸到需要傳輸?shù)臄?shù)據(jù),而且程序利用sendfile編寫的代碼也無需任何改動,這進(jìn)一步提升了程序性能。


當(dāng)前流行的消息中間件kafka就基于sendfile來高效傳輸文件。

其實(shí)你應(yīng)該已經(jīng)看出來了,高效IO的秘訣其實(shí)很簡單:?盡量少讓CPU參與進(jìn)來?。

實(shí)際上sendfile的使用場景是比較受限的,大前提是用戶態(tài)無需看到操作的數(shù)據(jù),并且只能從文件描述符往socket中傳輸數(shù)據(jù),而且DMA Gather Copy也需要硬件支持,那么有沒有一種不依賴硬件特性同時(shí)又能在任意兩個(gè)文件描述符之間以零拷貝方式高效傳遞數(shù)據(jù)的方法呢?

答案是肯定的!這就要說到Linux下的另一個(gè)系統(tǒng)調(diào)用了:splice。

Splice

這里還要再次強(qiáng)調(diào)一下不管是sendfile還是這里的splice系統(tǒng)調(diào)用,使用的大前提都是無需在用戶態(tài)看到要傳遞的數(shù)據(jù)。

讓我們再來看一下傳統(tǒng)的read/write方法。

在這一方法下必須將數(shù)據(jù)從內(nèi)核態(tài)拷貝的用戶態(tài),然后在從用戶態(tài)拷貝回內(nèi)核態(tài),?既然用戶態(tài)無需對該數(shù)據(jù)有任何操作,那么為什么不讓數(shù)據(jù)傳輸直接在內(nèi)核態(tài)中進(jìn)行呢??

現(xiàn)在目標(biāo)有了,實(shí)現(xiàn)方法呢?

答案是借助Linux世界中用于進(jìn)程間通信的管道,pipe。

還是以網(wǎng)絡(luò)服務(wù)器為例,DMA把數(shù)據(jù)從磁盤拷貝到文件buf,然后將數(shù)據(jù)寫入管道,當(dāng)在再次調(diào)用splice后將數(shù)據(jù)從管道讀入socket buf中,然后通過DMA發(fā)送出去,值得注意的是向管道寫數(shù)據(jù)以及從管道讀數(shù)據(jù)并沒有真正的拷貝數(shù)據(jù),而僅僅傳遞的是該數(shù)據(jù)相關(guān)的必要信息。


你會看到,splice和sendfile是很像的,實(shí)際上后來sendfile系統(tǒng)調(diào)用經(jīng)過改造后就是基于splice實(shí)現(xiàn)的,既然有splice那么為什么還要保留sendfile呢?答案很簡單,如果直接去掉sendfile,那么之前依賴該系統(tǒng)調(diào)用的所有程序?qū)o法正常運(yùn)行。

總結(jié)

本文介紹了很多零拷貝的優(yōu)化技巧,但是注意,一定要注意,?如果你的程序?qū)π阅芤獩]有到那種極度苛刻哪怕慢1ns都不行的時(shí)候,忘掉本文講解的這些所謂優(yōu)化技巧?,老老實(shí)實(shí)用read/write,相比這些所謂的技巧,內(nèi)存拷貝沒有那么糟糕。

本文僅僅告訴你為了追求高性能系統(tǒng)中都有哪些亂七八糟的設(shè)計(jì)。

寫在最后:對于準(zhǔn)備成為一名優(yōu)秀程序員的朋友,如果你想更好的提升你的編程核心能力(內(nèi)功),讓自己成為一個(gè)具有真材實(shí)料的厲害的程序員,不妨從現(xiàn)在開始!C/C++,永不過時(shí)的編程語言~

微信公眾號:C語言編程學(xué)習(xí)基地

整理分享(多年學(xué)習(xí)的源碼、項(xiàng)目實(shí)戰(zhàn)視頻、項(xiàng)目筆記,基礎(chǔ)入門教程)

歡迎轉(zhuǎn)行和學(xué)習(xí)編程的伙伴,利用更多的資料學(xué)習(xí)成長比自己琢磨更快哦!




從數(shù)據(jù)拷貝的角度:如何讓你的程序性能趕超 P8 大牛!的評論 (共 條)

分享到微博請遵守國家法律
夏邑县| 军事| 宁化县| 麻阳| 宁远县| 长汀县| 漳平市| 巴南区| 开平市| 思茅市| 舞阳县| 茌平县| 叙永县| 莎车县| 靖州| 西安市| 驻马店市| 和平县| 上饶县| 攀枝花市| 孟村| 鄯善县| 库尔勒市| 稷山县| 灌南县| 灵璧县| 梧州市| 巴塘县| 灌阳县| 石渠县| 青州市| 左权县| 安陆市| 临沧市| 高邑县| 二手房| 通州市| 都匀市| 蒙山县| 台东市| 仲巴县|