為了快速構建項目,使用高性能框架是我的職責,但若不去深究底層的細節會讓我失去對技術的熱愛。
探究的過程是痛苦並激動的,痛苦在於完全理解甚至要十天半月甚至沒有機會去應用,激動在於技術的相同性,新的框架不再是我焦慮。
每一個底層細節的攻克,就越發覺得自己對計算機一無所知,這可能就是對知識的敬畏。
新IO和傳統IO-intsmaze
新IO和傳統IO都是用於進行輸入/輸出。
新IO采用了內存映射的方式來處理輸入/輸出,新IO將文件或文件的一段區域映射到內存中,這樣就可以像訪問內存一樣訪問文件了,通過這種方式比傳統的輸入/輸出要快的多。通過內存映射機制操作文件比使用常規方法和使用FileChannel讀寫高效的多。
傳統IO操作-intsmaze
傳統的文件IO操作中,調用操作系統提供的底層標准IO系統調用函數 read()、write() ,調用此函數的進程(在JAVA中即java進程)由當前的用戶態切換到內核態,然后OS的內核代碼負責將相應的文件數據讀取到內核的IO緩沖區,然后再把數據從內核IO緩沖區拷貝到進程的私有地址空間中去,這樣便完成了一次IO操作。
傳統IO的優化-intsmaze
為了減少磁盤的IO操作,同時程序訪問一般都帶有局部性,局部性原理,OS根據局部性原理會在一次 read()系統調用過程中預讀更多的文件數據緩存在內核IO緩沖區中,當繼續訪問的文件數據在緩沖區中時便直接拷貝數據到進程私有空間,避免了再次的低效率磁盤IO操作。
其過程如下:
為什么要搞一個內核IO緩沖區把原本只需一次拷貝數據的事情搞成需要2次數據拷貝呢?
這么做是為了減少磁盤的IO操作,為了提高性能而考慮的,程序訪問一般都帶有局部性,局部性原理,在這里主要是指的空間局部性,即我們訪問了文件的某一段數據,那么接下去很可能還會訪問接下去的一段數據,由於磁盤IO操作的速度比直接訪問內存慢了好幾個數量級,所以OS根據局部性原理會在一次 read()系統調用過程中預讀更多的文件數據緩存在內核IO緩沖區中,當繼續訪問的文件數據在緩沖區中時便直接拷貝數據到進程私有空間,避免了再次的低效率磁盤IO操作。
新IO-intsmaze
講新IO前先講講背景知識,虛擬空間。
虛擬空間-intsmaze
很久很久以前的存儲管理技術必須將作業全部裝入內存才能執行且作業常駐內存直到運行結束,難以滿足較大作業或較多作業進入內存執行。 為了能讓作業的一部分裝入就可以運行的存儲管理技術叫做虛擬內存管理技術。
現代操作系統中的進程在使用內存的時候,都不是直接訪問內存物理地址的,進程訪問的都是虛擬內存地址,然后虛擬內存地址再轉化為內存物理地址。 虛擬內存就是硬盤中的一塊區域,它用來存放內存里使用頻率不高的頁面文件,讓使用頻率高的頁面文件活動在內存區域中,提高CPU對數據操作的速度。
進程看到的所有地址組成的空間,就是虛擬空間。虛擬空間是某個進程對分配給它的所有物理地址(已經分配的和將會分配的)的重新映射。 在Linux中,這個區域叫做swap,一般大小應設置為物理內存的2倍。詳情見 https://blog.csdn.net/fengxinlinux/article/details/52071766
局部性原理-intsmaze
大多數程序執行時,在一個較短的時間內僅能使用程序代碼的一部分,相應的,程序所訪問的存儲空間也局限於某個區域,這就是程序執行的局部性原理。
基於局部性原理,在程序裝入時可以將程序的一部分放入內存,而將其余部分放在外存,然后啟動程序(部分裝入)。在程序執行期間,當所訪問的信息不在內存中,再由操作系統將所需的部分調入內存(請求調入)。另外,系統將內存中暫時不用的內容置換到外存上,騰出空間存放將要調入內存的信息(置換功能)。
頁式虛擬地址與內存頁面物理地址轉-intsmaze
虛擬地址轉化為真實地址的時候,不一定會對應內存地址,還可能對應硬盤地址。 內存的一個地址一般對應1byte,硬盤的一個地址一般對應512byte(一個磁盤扇區).
內存和硬盤里的數據做交換時,也就是把一個內存地址對應的數據拷貝到硬盤里或者反過來把硬盤數據拷貝到內存里,想要方便處理操作系統會統一單位(傳說中的頁對齊)。 頁就是一個統一的單位,頁的大小總是磁盤扇區大小的倍數,通常是2次冪,比如1024字節。
有了頁這個統一單位,接下來我們說的虛擬地址、內存地址、磁盤地址都是對應的一個頁。頁式虛擬地址與內存物理地址建立一一對應的頁表(硬件地址變換機構來執行轉換)。將邏輯地址上連續的頁號映射到物理內存中稱為離散的多個物理塊(頁面),將頁面和物理塊一一對應,體現在頁表。(頁表由頁號和塊號組成)
虛擬地址空間可以大於實際的內存空間,比如實際內存大小是1G,但是虛擬地址空間可以是4G。這樣在操作系統中的普通應用程序看來,就好像是有4G的可用內存。
虛擬地址空間可以大於實際內存空間,這是怎么實現的呢?
比如我實際內存1G,虛擬內存設成了4G,現在往4G的虛擬內存里放了4G的數據,那么當前只有1G的數據在真實內存中,另外的3G因為裝不下就只能以文件形式放到硬盤里,這個存放內存內容的硬盤文件就叫頁面文件。
虛擬內存的空間=物理內存+頁面文件。
頁式管理-intsmaze
各進程的虛擬空間被划分為等的頁若干個長度相,頁長1K—4K。進程虛擬地址變為由頁號P與頁內地址W組成。 同時也把內存分成與頁面大小相等的區域,稱為頁面。用戶進程在內存空間除了在每個頁面內地址連續之外,每個頁面之間不再連續。
操作系統層面優化提升程序執行效率-intsmaze
1,設置虛擬內存大小-intsmaze
swap空間就是虛擬內存,在物理內存不足時,有較大的用處。
查看內存空間大小:free -m // m表示顯示的字節單位是m(megabytes)
用命令free查看系統內 Swap 分區大小。
free -m total used free shared buffers cached Mem: 1002 964 38 0 21 410 -/+ buffers/cache: 532 470 Swap: 951 32 929 可以看到 Swap 只有951M
如何修改百度即可。
2,設置實際內存和虛擬內存進行數據交換的傾向性-intsmaze
vm.swappiness是Linux內核的一個參數,范圍是0~100。它表示實際內存和虛擬內存區域進行數據交換的傾向性大小,數值越大表示傾向性越大,即交換的頁面文件越多,反之亦然。一般默認值為60。可用'cat /proc/sys/vm/swappiness’查看。
這個值應該設置成多大才能提高Linux的性能呢?
以下摘自 https://blog.csdn.net/liu870915/article/details/51860932
這個當然要由具體的環境來定了。在一台CentOS機器上,分別把值設為0,60,100,下面是運行'vmstat -S M 5’的三次數據報告。(vmstat命令是用來查看虛擬內存狀況的,參數-S M表示以M為單位,5表示每5秒鍾產生一次報告。)這里主要關注bi,bo和wa這三個值,bi代表每秒鍾從硬盤讀入數據的塊數(因為硬盤是塊設備),bo表示每秒鍾寫入硬盤數據的塊數,wa表示CPU等待IO設備就緒的時間。 當值為100時,wa基本為50左右的值,這表示50%的CPU時間都在等待IO設備就緒(大好的CPU資源就這樣被浪費了!)現在你明白瓶頸在哪里了吧?對,就是硬盤。 說明我實驗的這台機器硬盤IO的處理能力是最影響性能的了。那么該怎么解決呢?當然了,換個轉速更快的硬盤當然可以,還有呢?增加內存有可能也可以。增加了內存以后,再把swappiness的值設小點,以減少硬盤IO的操作。內存夠大時,無論頁面文件的使用頻率是高還是低都放在內存里,無須使用虛擬內存。 但是在這個例子中,swpd的值始終為0,這表示沒有虛擬文件被使用。這說明內存容量是足夠的,即使再增加內存,作用也不大。最好的辦法就是更換硬盤了。 如何改變swapiness的值?你可以運行'echo 數值 > /proc/sys/vm/swapiness’ 或者 'sysctl –w vm.swappiness = 數值' 來修改內核中的實時參數。如果想機器在重啟之后仍然保持這個數值的話,就需要在'/etc/sysctl.conf’文件中加上'vm.swappiness = 數值' 這一行。
新IO-內存映射文件-intsmaze
傳統IO中當對文件進行操作的時候,一般總是先打開文件,然后申請一塊內存用做緩沖區,再將文件數據循環讀入並處理,當文件長度大於緩沖區長度的時候需要多次讀入。
內存映射文件是將一個文件直接映射到進程的進程空間中(“映射”就是建立一種對應關系,這里指硬盤上文件的位置與進程邏輯地址空間中一塊相同區域之間一 一對應,這種關系純屬是邏輯上的概念,物理上是不存在的),這樣可以通過內存指針用讀寫內存的辦法直接存取文件內容。
在內存映射過程中,並沒有實際的數據拷貝,文件沒有被載入內存,只是邏輯上放入了內存,具體到代碼,就是建立並初始化了相關的數據結構,這個過程由系統調用mmap()實現,所以映射的效率很高.
經驗表明,內存映射IO允許加載不能直接訪問的潛在巨大文件,在大文件處理方面性能更加優異。它的不足是增加了頁面錯誤的數目(由於操作系統只將一部分文件加載到內存,如果一個請求頁面沒有在內存中,它將導致頁面錯誤)。
映射文件區域的能力取決於於內存尋址的大小。在32位機器中,你不能一次訪問超過4GB或2 ^ 32(以上的文件),只能分批映射。
內存映射文件優化本質-intsmaze
mmap()是系統調用,沒有進行數據拷貝,數據拷貝是在缺頁中斷處理時進行的,由於mmap()將文件直接映射到用戶空間,所以中斷處理函數根據這個映射關系,直接將文件從硬盤拷貝到用戶空間(沒有拷貝到內核空間),只進行了一次數據拷貝 。
從硬盤上將文件讀入內存,都是要經過數據拷貝,並且數據拷貝操作是由文件系統和硬件驅動實現的,理論上來說,拷貝數據的效率是一樣的。
內存映射文件的效率比標准IO高的重要原因就是因為少了把數據拷貝到OS內核緩沖區這一步,內存映射只拷貝一次效率要比read/write 拷貝兩次高。
虛擬內存與內存映射文件的聯系-intsmaze
虛擬內存是內存映射文件的基礎,內存映射文件的底層還是依賴虛擬內存。虛擬內存和內存映射文件都是將一部分內容加載到內存,另一部分放在磁盤上,二者都是應用程序動態性的基礎,由於二者的虛擬性,對於用戶都是透明的.
虛擬內存是硬盤的一部分,是計算機RAM(隨機存取存儲器)與硬盤的數據交換區,因為實際的物理內存可能遠小於進程的地址空間,這就需要把內存中暫時不用到的數據放到硬盤上一個特殊的地方,當請求的數據不在內存中時,系統產生缺頁中斷,內存管理器便將對應的內存頁重新從硬盤調入物理內存。
內存映射文件是由一個文件到一塊內存的映射,使應用程序可以通過內存指針對磁盤上的文件進行訪問,其過程就如同對加載了文件的內存的訪問,因此內存文件映射非常適合於用來管理大文件。
虛擬內存與內存映射文件的區別-intsmaze
虛擬內存實現的基礎是分頁機制和局部性原理,架構在物理內存之上,其引入是因為實際的物理內存運行程序所需的空間,即使現在計算機中的物理內存越來越大,將所有運行着的程序全部加載到內存中非常不現實。
內存映射文件虛擬性並不是由於局部性,而是使進程虛擬地址空間的某個區域建立映射磁盤文件的全部或部分內容,通過該區域可以直接對被映射的磁盤文件進行訪問,而不必執行文件I/O操作也無需對文件內容進行緩沖處理。
用圖來表示mmap,即為如下所示。mmap函數會在內存中找一段空白內存,然后將這部分內存與文件的內容對應起來。我們對內存的所有操作都會直接反應到文件中去。mmap的主要功能就是建立內存與文件這種對應關系。所以才被命名為memory map。
此圖為 Linux 中進程的虛擬存儲器,即進程的虛擬地址空間, 32 位操作系統,就有2^32 = 4G的虛擬地址空間,
圖中有一塊區域: “共享庫的內存映射區域” ,這段區域就是在內存映射文件的時候將某一段的虛擬地址和文件對象的某一部分建立起映射關系,此時並沒有拷貝數據到內存中去,而是當進程代碼第一次引用這段代碼內的虛擬地址時,觸發了缺頁異常,這時候OS根據映射關系直接將文件的相關部分數據拷貝到進程的用戶私有空間中去。
請收看下節內容-intsmaze
自此文件IO的演化理論依據介紹完了,下一篇將會基於java的源碼去看各種實現。
文章內容參考:
《深入理解計算機系統(原書第三版3)》,《清華大學計算機系列教材:計算機操作系統教程(第4版)》,示例圖片來源於其他博客。