天天看點

磁盤IO的總結

轉自:http://simpleframework.net/blog/v/8486.html

1. 完全随機寫還是跳躍,5倍的性能差距!

全随機寫無疑是最慢的寫入方式,在logic dump測試中很驚訝的發現,将200M的記憶體資料随 機的寫入到100G的磁盤資料裡面,竟然要2個小時之多。原因就是雖然隻有200M的資料,但實際上卻是200萬次随機寫,根據測試,在2850機器上, 這樣完全的随機寫,r/s 大約在150~350之間,在180機器上,r/s難以達到250,這樣計算,難怪需要2~3個小時之久。

如何改進這種單線程随機寫慢的問題呢。一種方法就是盡量将完全随機寫變成有序的跳躍随機寫。實作方式,可以是簡單的在記憶體中緩存一段時間,然後排序,使得在 寫盤的時候,不是完全随機的,而是使得磁盤磁頭的移動隻向一個方向。根據測試,再一次讓我震驚,簡單的先在記憶體中排序,竟然直接使得寫盤時間縮短到1645秒,磁盤的r/s也是以提升到1000以上。寫盤的速度,一下子提高了5倍。

一個需要注意的地方,這種跳躍寫對性能的提升,來至與磁頭的單方向移動,它非常容易受其他因素的影響。測試中,上面提到的測試是隻寫block檔案, 但如果在每個tid的進行中再增加一個寫index的小檔案。雖然如果隻寫index小檔案,所用時間幾乎可以忽略,但如果夾雜在寫block檔案中間的 話,對整體的寫性能可能影響巨大,因為他可能使得磁盤的磁頭需要這兩個地方來回跑。根據測試,如果隻寫index檔案,隻需要300s就可以寫完所有 200萬個tid,單如果将寫索引和寫block放在一起,總時間就遠大于分别寫這兩部分的時間的和。針對這種情況,一種解決方案就是就不要将小資料量的資料實時的刷盤,使用應用層的cache來緩存小資料量的index,這樣就可以消除對寫block檔案的影響。

從原理上解釋上面的表象,一般來說,硬碟讀取資料的過程是這樣的,首先是将磁頭移動到磁盤上資料所在的區域,然後才能進行讀取工作。磁頭移動的過程又可以 分解為兩個步驟,其一是移動磁頭到指定的磁道,也就是尋道,這是一個在磁盤盤片徑向上移動的步驟,花費的時間被稱為“尋道時間”;其二就是旋轉盤片到相應 扇區,花費的時間被稱為“潛伏時間”(也被稱為延遲)。那麼也就是說在硬碟上讀取資料之前,做準備工作上需要花的時間主要就是“尋道時間”和“潛伏時間” 的總和。真正的資料讀取時間,是由讀取資料大小和磁盤密度、磁盤轉速決定的固定值,在應用層沒有辦法改變,但應用層缺可以通過改變對磁盤的通路模式來減少“尋道時間”和“潛伏時間”, 我們上面提到的在應用層使用cache然後排序的方式,無疑就是縮短了磁盤的尋址時間。由于磁頭是實體裝置,也很容易了解,為什麼中間插入對其他小檔案的讀寫會導緻速度變慢很多。

建議:盡量避免完全的随機寫,在 不能使用多線處理的時候,盡量使用應用層cache,確定寫盤時盡量有順序性。對于小資料量的其他檔案,可以一直儲存在應用層cache裡面,避免對其他大資料量的資料寫入産生影響。

2. 多線程随機讀、處理速度、響應時間

多線程随機讀的處理速度可以達到單線程随機讀的10倍以上,但同上也帶來了響應時間的增大。測試結論如下:(每個線程盡量讀)

磁盤IO的總結

結論标明增加線程數,可以有效的提升程式整體的io處理速度。但同時,也使得每個io請求的響應時間上升很多。

從底層的實作上解釋這個現象:應用層的io請求在核心态會加入到io請求隊列裡面。核心在處理io請求的時候,并不是簡單的先到先處理,而是根據磁盤的特 性,使用某種電梯算法,在處理完一個io請求後,會優先處理最臨近的io請求。這樣可以有效的減少磁盤的尋道時間,進而提升了系統整體的io處理速度。但對于每一個io請求來看,由于可能需要在隊列裡面等待,是以響應時間會有所提升。

響應時間上升,應該主要是由于我們測試的時候采用每個線程都盡量讀的方式。在實際的應用中,我們的程式都沒有達到這種壓力。是以,在io成為瓶頸的程式裡面,應該盡量使用多線程并行處理不同的請求。對于線程數的選擇,還需要通過性能測試來衡量。

3. 是否使用direct io

首先看測試結論:

磁盤IO的總結

可見在小資料量下非dio方式更快,但随着資料量增大,dio方式更快,分界線在50G左右。(注,測試基于: 線程數:50,每次讀操作讀出:4K, 機器:del 180, 記憶體:機器總記憶體8G,使用其他程式占用3G,剩餘5G左右, 其他情況可能有不同的分界線。)

4. 系統緩存

4.1. 系統緩存相關的幾個核心參數:

1. /proc/sys/vm/dirty_background_ratio

該檔案表示髒資料到達系統整體記憶體的百分比,此時觸發pdflush程序把髒資料寫回磁盤。

預設設定:10

2. /proc/sys/vm/dirty_expire_centisecs

該檔案表示如果髒資料在記憶體中駐留時間超過該值,pdflush程序在下一次将把這些資料寫回磁盤。

預設設定:3000(1/100秒)

3. /proc/sys/vm/dirty_ratio

該檔案表示如果程序産生的髒資料到達系統整體記憶體的百分比,此時程序自行把髒資料寫回磁盤。

預設設定:40

4. /proc/sys/vm/dirty_writeback_centisecs

該檔案表示pdflush程序周期性間隔多久把髒資料寫回磁盤。

預設設定:500(1/100秒)

4.2. 系統一般在下面三種情況下回寫dirty頁:

1. 定時方式: 定時回寫是基于這樣的原則:/proc/sys/vm/dirty_writeback_centisecs的值表示多長時間會啟動回寫線程,由這個定時 器啟動的回寫線程隻回寫在記憶體中為dirty時間超過(/proc/sys/vm/didirty_expire_centisecs / 100)秒的頁(這個值預設是3000,也就是30秒),一般情況下dirty_writeback_centisecs的值是500,也就是5秒,是以 預設情況下系統會5秒鐘啟動一次回寫線程,把dirty時間超過30秒的頁回寫,要注意的是,這種方式啟動的回寫線程隻回寫逾時的dirty頁,不會回寫 沒逾時的dirty頁,可以通過修改/proc中的這兩個值,細節檢視核心函數wb_kupdate。

2. 記憶體不足的時候: 這時并不将所有的dirty頁寫到磁盤,而是每次寫大概1024個頁面,直到空閑頁面滿足需求為止

3. 寫操作時發現髒頁超過一定比例: 當髒頁占系統記憶體的比例超過/proc/sys/vm/dirty_background_ratio 的時候,write系統調用會喚醒pdflush回寫dirty page,直到髒頁比例低于/proc/sys/vm/dirty_background_ratio,但write系統調用不會被阻塞,立即傳回.當髒 頁占系統記憶體的比例超/proc/sys/vm/dirty_ratio的時候, write系統調用會被被阻塞,主動回寫dirty page,直到髒頁比例低于/proc/sys/vm/dirty_ratio

4.3. pb項目中的感觸:

1,如果寫入量巨大,不能期待系統緩存的自動回刷機制,最好采用應用層調用fsync或者sync。如果寫入量大,甚至超過了系統緩存自動刷回的速 度,就有可能導緻系統的髒頁率超過/proc/sys/vm/dirty_ratio, 這個時候,系統就會阻塞後續的寫操作,這個阻塞有可能有5分鐘之久,是我們應用無法承受的。是以,一種建議的方式是在應用層,在合适的時機調用 fsync。

2,對于關鍵性能,最好不要依賴于系統cache的作用,如果對性能的要求比較高,最好在應用層自己實作cache,因為系統cache受外界影響太大,說不定什麼時候,系統cache就被沖走了。

3,在logic設計中,發現一種需求使用系統cache實作非常合适,對于logic中的高樓貼,在應用層cache實作非常複雜,而其數量又非常 少,這部分請求,可以依賴于系統cache發揮作用,但需要和應用層cache相配合,應用層cache可以cache住絕大部分的非高樓貼的請求,做到 這一點後,整個程式對系統的io就主要在高樓貼這部分了。這種情況下,系統cache可以做到很好的效果。

5. 磁盤預讀

關于預讀,從網上摘錄如下兩段:

預讀算法概要

1. 順序性檢測

為了保證預讀命中率,Linux隻對順序讀(sequential read)進行預讀。核心通過驗證如下兩個條件來判定一個read()是否順序讀:

  • 這是檔案被打開後的第一次讀,并且讀的是檔案首部;
  • 目前的讀請求與前一(記錄的)讀請求在檔案内的位置是連續的。

如果不滿足上述順序性條件,就判定為随機讀。任何一個随機讀都将終止目前的順序序列,進而終止預讀行為(而不是縮減預讀大小)。注意這裡的空間順序性說的 是檔案内的偏移量,而不是指實體磁盤扇區的連續性。在這裡Linux作了一種簡化,它行之有效的基本前提是檔案在磁盤上是基本連續存儲的,沒有嚴重的碎片 化。

2. 流水線預讀

當程式在處理一批資料時,我們希望核心能在背景把下一批資料事先準備好,以便CPU和硬碟能流水線作業。Linux用兩個預讀視窗來跟蹤目前順序流的預讀 狀态:current視窗和ahead視窗。其中的ahead視窗便是為流水線準備的:當應用程式工作在current視窗時,核心可能正在ahead窗 口進行異步預讀;一旦程式進入目前的ahead視窗,核心就會立即往前推進兩個視窗,并在新的ahead視窗中啟動預讀I/O。

3. 預讀的大小

當确定了要進行順序預讀(sequential readahead)時,就需要決定合适的預讀大小。預讀粒度太小的話,達不到應有的性能提升效果;預讀太多,又有可能載入太多程式不需要的頁面,造成資源浪費。為此,Linux采用了一個快速的視窗擴張過程:

首次預讀: readahead_size = read_size * 2; // or *4

預讀視窗的初始值是讀大小的二到四倍。這意味着在您的程式中使用較大的讀粒度(比如32KB)可以稍稍提升I/O效率。

後續預讀: readahead_size *= 2;

後續的預讀視窗将逐次倍增,直到達到系統設定的最大預讀大小,其預設值是128KB。這個預設值已經沿用至少五年了,在目前更快的硬碟和大容量記憶體面前,顯得太過保守。

# blockdev –setra 2048 /dev/sda

當然預讀大小不是越大越好,在很多情況下,也需要同時考慮I/O延遲問題。

6. 其他細節:

6.1. pread 和pwrite

在多線程io操作中,對io的操作盡量使用pread和pwrite,否則,如果使用seek+write/read的方式的話,就需要在操作時加鎖。這種加鎖會直接造成多線程對同一個檔案的操作在應用層就串行了。進而,多線程帶來的好處就被消除了。

使用pread方式,多線程也比單線程要快很多,可見pread系統調用并沒有因為同一個檔案描述符而互相阻塞。pread和pwrite系統調用在底層 實作中是如何做到相同的檔案描述符而彼此之間不影響的?多線程比單線程的IOPS增高的主要因素在于排程算法。多線程做pread時互相未嚴重競争是次要 因素。

核心在執行pread的系統調用時并沒有使用inode的信号量,避免了一個線程讀檔案時阻塞了其他線程;但是pwrite的系統調用會使用inode的 信号量,多個線程會在inode信号量處産生競争。pwrite僅将資料寫入cache就傳回,時間非常短,是以競争不會很強烈。

6.2. 檔案描述符需要多套嗎?

在使用pread/pwrite的前提下,如果各個讀寫線程使用各自的一套檔案描述符,是否還能進一步提升io性能?

每個檔案描述符對應核心中一個叫file的對象,而每個檔案對應一個叫inode的對象。假設某個程序兩次打開同一個檔案,得到了兩個檔案描述符,那麼在 核心中對應的是兩個file對象,但隻有一個inode對象。檔案的讀寫操作最終由inode對象完成。是以,如果讀寫線程打開同一個檔案的話,即使采用 各自獨占的檔案描述符,但最終都會作用到同一個inode對象上。是以不會提升IO性能。

轉載于:https://www.cnblogs.com/bugchecker/archive/2011/08/29/3041625.html

繼續閱讀