第7期:硬盤的性能特征
我們都知道內存比硬盤要快得多,大概能快出一兩個數(shù)量級(價格也要貴這么多)。不過,硬盤的問題并不只是訪問速度慢。
硬盤不適合做頻繁小量訪問
所謂頻繁小量訪問,是指運算過程中每次獲取的數(shù)據(jù)量很小,而讀取的次數(shù)很多。對于內存,每次取10字節(jié)、取1000萬次、總共訪問1億字節(jié);和每次取1000萬字段、取10次、也訪問1億字節(jié);這兩者耗時是差不多的。內存訪問數(shù)據(jù)的最小單位就是字節(jié),無論怎么做,***涉及到的字節(jié)數(shù)是一樣的。
但硬盤的機制完全不同,在硬盤中數(shù)據(jù)是按扇區(qū)存放的,數(shù)據(jù)訪問的最小單位是扇區(qū)(一般是512字節(jié)),也就是說從硬盤上讀1個字節(jié)和讀512字節(jié)的時間幾乎一樣的(內存復制時間相對于硬盤時間忽略不計了)。這樣,每次取10字節(jié)取1000萬次,和每次取1000萬字節(jié)取10次,訪問量一樣,但耗時就可能會有巨大的差異,前者可能會多取很多無用的數(shù)據(jù)。
實際情況還要更惡劣一些,我們常常直接使用操作系統(tǒng)中的文件存儲數(shù)據(jù),而大部分文件系統(tǒng)的讀取單位是簇,缺省大小一般是4K,比512字節(jié)要大8倍。而且,讀取硬盤是一個復雜的動作,不象內存那樣執(zhí)行一條CPU指令就可以讀取,訪問硬盤需要一系列的動作來啟動硬盤控制器并確定讀取位置,讀取1000萬次就要執(zhí)行1000萬次這樣的動作,當然一次讀1000萬字節(jié)時也會涉及較多次的磁盤動作(1000萬字節(jié)會在很多扇區(qū)中),但總次數(shù)仍然會少得多,批量不頻繁讀取的性能就會遠遠好于頻繁小量讀取。
不連續(xù)的隨機訪問是罪魁
不過,我們在上述結論中用了“可能”二字,而沒有用肯定的語氣,這是因為還要考慮數(shù)據(jù)存儲是否連續(xù)。如果要訪問的數(shù)據(jù)在硬盤上是連續(xù)存儲的,那取1000萬次10字節(jié)也不會很慢,因為后面的10字節(jié)已經在前面讀出的扇區(qū)里面而不必再讀,硬盤控制器以及操作系統(tǒng)都有緩存功能,實際硬盤讀取次數(shù)并沒有那么多,結果的性能相差也不會非常大。所以我們還要在"頻繁小量"前面加上“隨機”這個定語,也就是讀取的內容不連續(xù),這時候,前面讀出的扇區(qū)取出需要的部分外,其它內容和后面要訪問的數(shù)據(jù)沒有關系,只能浪費掉,而且后面再訪問又要再次讀,”可能“就會變成”肯定“了。
對于目前仍在大量采用的機械硬盤,隨機訪問還存在磁頭跳動問題,也就是硬盤指標中的平均尋道時間。尋道是個非常慢的機械動作,比讀一個扇區(qū)要慢得多得多。這樣,即使每次讀出扇區(qū)(簇)都沒有任何浪費,在隨機訪問時的尋道成本卻可能超過讀取本身。使用機械硬盤時要特別注意避免隨機頻繁小時訪問。
固態(tài)硬盤的情況要好一些,它沒有尋道的機械動作了,這樣在隨機讀取時也沒有尋道時間問題。不過,盡管固態(tài)硬盤并不是按扇區(qū)形式存儲數(shù)據(jù),操作系統(tǒng)仍將它模擬得和機械硬盤類似,必須按某個基本單位(簇)讀寫,這樣,隨機的頻繁小量(小于簇)訪問g還是會有前述的低效問題。對于許多需要隨機訪問小量數(shù)據(jù)的運算,簇(扇區(qū))這個單位太大了。
并行和并發(fā)導致隨機訪問
那么,對于只需要連續(xù)批量訪問的運算(比如遍歷匯總或查找),使用硬盤時的性能是否就只是其本身訪問速度決定的呢?
對于單線程或單任務的運算基本上是這樣。但現(xiàn)在研究高性能計算時顯然不可能不考慮并行運算,而且許多運算服務也需要支持多并發(fā)。并行和并發(fā)運算會使原本可以連續(xù)訪問的硬盤數(shù)據(jù)一定程度變成隨機訪問,原因很簡單:多線程實際上在共享同一套硬盤,不同線程的訪問請求顯然不會連續(xù),硬盤要同時響應這些請求就會發(fā)生跳動,也就是隨機訪問了。
對于機械硬盤這個后果常常很嚴重,如果線程之間切換頻繁,就會導致頻繁的尋道動作,結果就會發(fā)生多線程反而比單線程慢的現(xiàn)象。而有些單任務時性能尚可的場景,一旦并發(fā)了性能就會急劇下降。一個補救的辦法是加大讀數(shù)據(jù)的緩沖區(qū),每個線程每次讀出的數(shù)據(jù)足夠多且連續(xù),這樣能使尋道時間占比變小而感覺不明顯。但同時也會加劇內存占用,線程越多對內存需求越大。固態(tài)硬盤就好得多,緩沖區(qū)達到簇(扇區(qū))的規(guī)模就可了。
有點類似的場景是列式存儲,數(shù)據(jù)是按列連續(xù)存放的,需要多列計算時,即使單線程也會發(fā)生硬盤隨機訪問現(xiàn)象,涉及列較多時就未必會比行存有優(yōu)勢,多線程還會進一步加劇這個問題。在機械硬盤上用列存時要特殊注意這個問題,不一定總能提高性能。
外存運算需要采用不同的算法
由于硬盤的這些特征,有些運算從內存換到外存時會采用完全不同的方法,性能下降的程度也不是簡單按比例做乘法。
比較典型的是JOIN運算,對于外鍵式1:N的JOIN,如果數(shù)據(jù)全部在內存中,可以使用針對外鍵維表主鍵做HASH索引,遍歷事實表時用索引直接可以找到維表的相應記錄,事實表有多個外鍵指向不同維表時也只要遍歷一次就可以全解析。但如果維表過大而不能裝入內存時就不能采用同樣的算法了,因為硬盤無法支持隨機頻繁小量的訪問,而訪問維表記錄恰恰是這種運算,強行使用這種方案的性能會惡劣到還不如把數(shù)據(jù)先排序再來歸并的地步。外存大表JOIN時要按鍵值(的HASH值)分段,每段都足夠小可以裝入內存做JOIN,這種方法一次只能解析掉一個關聯(lián),事實表指向多個外鍵大維表時就要做多輪解析,運算量比內存JOIN大得多。
單機內存不足時還可以利用集群來避免復雜的外存運算。網絡本身的延遲和硬盤差不多,通過網絡獲取集群機的內存數(shù)據(jù)時,單位數(shù)據(jù)量的訪問性能不會比硬盤好多少,但卻可以利用內存的隨機訪問能力和并發(fā)能力。不過網絡和硬盤類似,每次啟動一個網絡連接的消耗較大,也不合適頻繁小量的訪問,要把多次獲取數(shù)據(jù)的請求和返回結果積累起來一起傳輸。用這種方法做大維表JOIN,比內存計算麻煩,但比外存運算還是簡單很多,仍然可以一次性解析掉多個關聯(lián)。