欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Golang中零拷貝的原理以及實(shí)踐

 更新時(shí)間:2023年07月02日 14:15:06   作者:藍(lán)胖子的編程夢(mèng)  
零拷貝技術(shù)相信大家都有所耳聞,但是本文不僅會(huì)講述零拷貝技術(shù)的原理,并將從實(shí)際代碼出發(fā),看看零拷貝技術(shù)在golang中的應(yīng)用,現(xiàn)在讓我們開始吧

零拷貝原理

零拷貝技術(shù)的原理本質(zhì)上就是減少數(shù)據(jù)的拷貝次數(shù),因?yàn)楫?dāng)調(diào)用傳統(tǒng)read write方法讀取文件內(nèi)容并返回給客戶端的時(shí)候,會(huì)經(jīng)過四次拷貝。我用golang代碼舉例如下

func?main()?{??
???http.HandleFunc("/tradition",?func(writer?http.ResponseWriter,?request?*http.Request)?{??
??????f,?_?:=?os.Open("./testmmap.txt")??
??????buf?:=?make([]byte,?1024)??
??????//?內(nèi)核拷貝到buf
??????n,?_?:=?f.Read(buf)??
??????//?buf拷貝到內(nèi)核
??????writer.Write(buf[:n])??
???})??
???http.ListenAndServe(":8080",?http.DefaultServeMux)??
}

如上面代碼所示,如果我們需要將本地testmmap.txt文件的內(nèi)容讀出來返回給客戶端。

testmmap.txt里只有一個(gè)hello的單詞,當(dāng)服務(wù)啟動(dòng)以后訪問接口便會(huì)返回hello。

(base) ?  codelearning git:(master) ? cat testmmap.txt
hello
(base) ?  codelearning git:(master) ? curl localhost:8080/tradition
hello

整個(gè)過程需要經(jīng)過read和write兩次系統(tǒng)調(diào)用,而每次read和write的調(diào)用將面臨用戶態(tài)和內(nèi)核態(tài)緩沖區(qū)之間數(shù)據(jù)的拷貝。

整個(gè)拷貝過程如上圖所示,磁盤和內(nèi)核間的數(shù)據(jù)傳遞可以通過DMA技術(shù)讓cpu不參與其中,但是內(nèi)核態(tài)和用戶態(tài)間的數(shù)據(jù)拷貝則需要經(jīng)過cpu參與,涉及到了兩次系統(tǒng)調(diào)用,和4次數(shù)據(jù)拷貝。

mmap+write

基于上述傳統(tǒng)文件的訪問方式,我們可以用mmap技術(shù)進(jìn)行優(yōu)化,mmap可以讓用戶緩沖區(qū)buf的地址和文件磁盤地址建立映射,這樣訪問用戶緩沖區(qū)buf的數(shù)據(jù)就等效于訪問磁盤文件上的數(shù)據(jù)。

用mmap優(yōu)化后的文件訪問代碼如下:

??
http.HandleFunc("/mmap",?func(writer?http.ResponseWriter,?request?*http.Request)?{??
???f,?_?:=?os.Open("./testmmap.txt")??
???data,?err?:=?syscall.Mmap(int(f.Fd()),?0,?5,?syscall.PROT_READ,?syscall.MAP_SHARED)??
???if?err?!=?nil?{??
??????panic(err)??
???}??
???writer.Write(data)??
})

可以看到mmap返回了一個(gè)data的字節(jié)數(shù)組,這個(gè)字節(jié)數(shù)組的內(nèi)容就是映射了文件內(nèi)容,之后將字節(jié)數(shù)組寫入到響應(yīng)體里。

syscall.Mmap(int(f.Fd()), 0, 5, syscall.PROT_READ, syscall.MAP_SHARED)

這里再解釋下mmap涉及的參數(shù)含義:

其中第一個(gè)參數(shù)代表要映射的文件描述符。

接著是映射的范圍是從0個(gè)字節(jié)到第5個(gè)字節(jié)。

第四個(gè)參數(shù) 代表映射的后的內(nèi)存區(qū)域是只讀的,類似的參數(shù)還有 syscall.PROT_WRITE表示內(nèi)存區(qū)域可以被寫入,syscall.PROT_NONE表示內(nèi)存區(qū)域不可訪問。

第五個(gè)參數(shù)表示 映射的內(nèi)存區(qū)域可以被多個(gè)進(jìn)程共享,這樣一個(gè)進(jìn)程修改了這個(gè)內(nèi)存區(qū)域的數(shù)據(jù),對(duì)其他進(jìn)程是可見的,并且修改后的內(nèi)容會(huì)自動(dòng)被操作系統(tǒng)同步到磁盤文件里。

類似的參數(shù)還有syscall.MAP_PRIVATE表示內(nèi)存區(qū)域是私有的,不可被其他進(jìn)程訪問,聲明為私有后,每個(gè)進(jìn)程擁有單獨(dú)的一份內(nèi)存映射拷貝,并且對(duì)此內(nèi)存區(qū)域進(jìn)行修改不會(huì)被同步到磁盤文件。

注意整個(gè)過程,我們是沒有將文件內(nèi)容讀取到用戶空間的任何緩沖區(qū)的。我們僅僅是在write系統(tǒng)調(diào)用時(shí),告訴了內(nèi)核一個(gè)地址(即字節(jié)數(shù)組的地址),而這個(gè)地址被mmap映射成了文件的地址。示意圖如下:

整個(gè)過程是用戶進(jìn)程告訴內(nèi)核需要拷貝的數(shù)據(jù)數(shù)據(jù)的地址,然后內(nèi)核拷貝數(shù)據(jù)。

sendfile

基于上述mmap+write方式進(jìn)行優(yōu)化后的文件內(nèi)容訪問減少了一次拷貝過程,不過系統(tǒng)調(diào)用還是兩次。如果用sendfile的話可以將系統(tǒng)調(diào)用減少到一次。

func?Sendfile(outfd?int,?infd?int,?offset?*int64,?count?int)?(written?int,?err?error)?

Sendfile的系統(tǒng)調(diào)用可以將目的文件描述符和源文件描述符傳遞進(jìn)去,剩下的拷貝過程就交給內(nèi)核了。示意圖如下:

但是sendfile對(duì)源文件描述符有要求,普通的文件可以,如果源文件描述符是socket則不能用sendfile了。

splice

splice系統(tǒng)調(diào)用則是為了解決源文件描述符和目的文件描述符都是socket的情況而產(chǎn)生的。splice系統(tǒng)調(diào)用的原理是通過管道讓數(shù)據(jù)在源socket和目的socket之間進(jìn)行傳輸。示意圖如下:

splice的系統(tǒng)調(diào)用方法如下:

func?Splice(rfd?int,?roff?*int64,?wfd?int,?woff?*int64,?len?int,?flags?int)?(n?int64,?err?error)?

注意splice系統(tǒng)調(diào)用需要保證傳入的文件描述符,rfd或者wfd至少一個(gè)是管道的文件描述符。創(chuàng)建管道也是一個(gè)系統(tǒng)調(diào)用,如下:

func?Pipe2(p?[]int,?flags?int)?error?

再回到通過splice系統(tǒng)調(diào)用的情況,可以看到要調(diào)用兩次splice系統(tǒng)調(diào)用,才能完成socket間的數(shù)據(jù)傳遞,因?yàn)閟plice系統(tǒng)調(diào)用會(huì)根據(jù)源文件描述符或目的文件描述符是管道的情況做不同的動(dòng)作。

第一次系統(tǒng)調(diào)用,目的文件描述符是管道,那么內(nèi)核則會(huì)將管道和源文件描述符綁定在一起,注意此時(shí)是不會(huì)進(jìn)行數(shù)據(jù)拷貝的。

第二次splice系統(tǒng)調(diào)用,源文件描述符是管道,那么內(nèi)核才會(huì)將管道內(nèi)的數(shù)據(jù)拷貝到目的文件描述符,由于在前一次,管道已經(jīng)和源文件描述符進(jìn)行了綁定,所以這次的splice系統(tǒng)調(diào)用,實(shí)際上會(huì)將源文件描述符的數(shù)據(jù)拷貝到目的文件描述符。

整個(gè)過程,拋開DMA技術(shù)拷貝的次數(shù),一共只有一次數(shù)據(jù)拷貝的過程。

零拷貝在golang中的實(shí)踐

講完了零拷貝涉及的技術(shù),我們來看看golang是如何運(yùn)用這些技術(shù)的。拿一個(gè)比較常用的方法舉例,io.Copy, 其底層調(diào)用了copyBuffer方法,copyBuffer會(huì)判斷copy的目的接口Writer是否實(shí)現(xiàn)了ReaderFrom 接口,如果實(shí)現(xiàn)了則直接調(diào)用ReaderFrom  從src讀取數(shù)據(jù)。

func?Copy(dst?Writer,?src?Reader)?(written?int64,?err?error)?{??
???return?copyBuffer(dst,?src,?nil)??
}
func?copyBuffer(dst?Writer,?src?Reader,?buf?[]byte)?(written?int64,?err?error)?{??
???//?If?the?reader?has?a?WriteTo?method,?use?it?to?do?the?copy.??
???//?Avoids?an?allocation?and?a?copy.???if?wt,?ok?:=?src.(WriterTo);?ok?{??
??????return?wt.WriteTo(dst)??
???}??
???//?Similarly,?if?the?writer?has?a?ReadFrom?method,?use?it?to?do?the?copy.??
???if?rt,?ok?:=?dst.(ReaderFrom);?ok?{??
??????return?rt.ReadFrom(src)??
???}??
???//?進(jìn)行傳統(tǒng)的文件讀取,代碼較長(zhǎng),暫時(shí)省略了。
???.......
???return?written,?err??
}

net.TcpConn實(shí)現(xiàn)了ReadFrom 接口,拿net.TcpConn舉例,看看它的實(shí)現(xiàn)。

func?(c?*TCPConn)?readFrom(r?io.Reader)?(int64,?error)?{??
???if?n,?err,?handled?:=?splice(c.fd,?r);?handled?{??
??????return?n,?err??
???}??
???if?n,?err,?handled?:=?sendFile(c.fd,?r);?handled?{??
??????return?n,?err??
???}??
???return?genericReadFrom(c,?r)??
}

最終net.TcpConn 會(huì)調(diào)用readFrom方法從來源io.Reader讀取數(shù)據(jù),而readFrom讀取數(shù)據(jù)用到的技術(shù)則是剛剛所講的零拷貝技術(shù),這里用到了splice和sendFile系統(tǒng)調(diào)用,如果來源io.Reader是一個(gè)tcp連接或者時(shí)unix 連接則會(huì)調(diào)用splice進(jìn)行數(shù)據(jù)拷貝,否則就會(huì)調(diào)用sendFile進(jìn)行數(shù)據(jù)拷貝,具體細(xì)節(jié)我就不在這里展開了。

總之,你可以看到,其實(shí)我們平時(shí)用到的方法就用到了零拷貝技術(shù),這些經(jīng)常說的底層原理離我們并不遙遠(yuǎn),學(xué)習(xí),永遠(yuǎn)懷著一顆謙卑的心。

到此這篇關(guān)于詳解Golang中零拷貝的原理以及實(shí)踐的文章就介紹到這了,更多相關(guān)Golang零拷貝內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang基于websocket實(shí)現(xiàn)的簡(jiǎn)易聊天室程序

    golang基于websocket實(shí)現(xiàn)的簡(jiǎn)易聊天室程序

    這篇文章主要介紹了golang基于websocket實(shí)現(xiàn)的簡(jiǎn)易聊天室,分析了websocket的下載、安裝及使用實(shí)現(xiàn)聊天室功能的相關(guān)技巧,需要的朋友可以參考下
    2016-07-07
  • Go語言學(xué)習(xí)之條件語句使用詳解

    Go語言學(xué)習(xí)之條件語句使用詳解

    這篇文章主要介紹了Go語言中條件語句的使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04
  • Go語言編譯原理之源碼調(diào)試

    Go語言編譯原理之源碼調(diào)試

    這篇文章主要為大家介紹了Go語言編譯原理之源碼調(diào)試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go 阻塞的實(shí)現(xiàn)示例

    Go 阻塞的實(shí)現(xiàn)示例

    Go語言提供了多種同步和通信機(jī)制,可以用于實(shí)現(xiàn)阻塞的效果,本文主要介紹了Go 阻塞的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-05-05
  • golang結(jié)構(gòu)化日志slog的用法簡(jiǎn)介

    golang結(jié)構(gòu)化日志slog的用法簡(jiǎn)介

    日志是任何軟件的重要組成部分,Go?提供了一個(gè)內(nèi)置日志包(slog),在本文中,小編將簡(jiǎn)單介紹一下slog包的功能以及如何在?Go?應(yīng)用程序中使用它,感興趣的可以了解下
    2023-09-09
  • Golang 的defer執(zhí)行規(guī)則說明

    Golang 的defer執(zhí)行規(guī)則說明

    這篇文章主要介紹了Golang 的defer執(zhí)行規(guī)則說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • golang構(gòu)建工具M(jìn)akefile使用詳解

    golang構(gòu)建工具M(jìn)akefile使用詳解

    這篇文章主要為大家介紹了golang構(gòu)建工具M(jìn)akefile的使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • Go之interface的具體使用

    Go之interface的具體使用

    這篇文章主要介紹了Go之interface的具體使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03
  • Go語言中slice作為參數(shù)傳遞時(shí)遇到的一些“坑”

    Go語言中slice作為參數(shù)傳遞時(shí)遇到的一些“坑”

    這篇文章主要給大家介紹了關(guān)于Go語言中slice作為參數(shù)傳遞時(shí)遇到的一些“坑”,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-03-03
  • 使用Go語言實(shí)現(xiàn)找出兩個(gè)大文件中相同的記錄

    使用Go語言實(shí)現(xiàn)找出兩個(gè)大文件中相同的記錄

    這篇文章主要為大家詳細(xì)介紹了使用Go語言實(shí)現(xiàn)找出兩個(gè)大文件中相同的記錄的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-10-10

最新評(píng)論