浪費內存?多大個事?

遙想蓋子當年,MS 紅火了,談笑間,640k 內存足矣。 - 程序君

現在已經不是從指縫中扣內存的時代了。bit 在主流的解釋型語言中要麼失了蹤跡,要麼被作為高(sha)級(bi)功能被雪藏起來,就像 .net 的 managed code 一樣,被壓抑得像個曠婦。 HTTP 這樣的 string-based protocol 進一步助長了這種氣焰,互聯網世界原本精心構建的那一個個端莊優雅的透著書香的數據結構,讓渡給了粗陋的 JSON。

昨天文章中的 slides,在如何 improve memory 那裡,我放了一張 RFC793 TCP header 的截圖。有個細心的讀者發現了,於是我們有了如下的對話(吐槽):

第 33 張幻燈片標題是 memory,圖怎麼放得是 TCP 的協議頭:)

眼尖心細!這張 slides 我懶得寫文字了,調侃了一下現在日下的世風,web dev 動輒 json,對數據的把控退化到 integer / string / array / map,不再有 enum 的概念,不再有 bit,該用 atom/symbol 統統 string..

我覺得很多人會不以為然。現在不是論 M,更不是論 K 的時代了。每 Gb 內存,也就是 711 一份好燉的價格,咱不差錢,32G 不夠,上 128G,還不夠,只要系統支持,咱可以照著 Tb 往上擼,多大個事!為了摳點內存,浪費我司寶貴的程序員的時間,那可是一秒鐘幾十萬上下的!

好吧,那我們聊聊這個事。

先說好理解的。一個 256 byte 就可以搞定的數據結構,如果任其膨脹到 2k,會發生什麼?

  • CPU 的順序讀取時間膨脹了 8x

  • 網路的發送時間增長了 8x

好吧,這些都是小時間,ns / us 量級的東西,不足為慮!

我們接著看:

公允期間,咱們都不做 gzip(你要非跟我較真這個,那麼咱就把前提變成 1k 和 8k),由於 2k > 標準的 MTU(1514),這個數據一個報文發送不了,於是乎,發送端需要分片,接收端需要重組。分片是萬惡之源,我們昨天的文章中提到,美國到歐洲一個 roundtrip 就是 150ms,一個包變成成兩個,則意味著可能受到 TCP slow start 的影響而延遲發送,同時,多一個包,丟包的概率就大大升高了。而丟包,眾所周知,是網路性能的大敵。

更大的數據帶來更多的讀寫時間,更多的網路傳輸時間,可能會引發分片,進而引發重組,還有更高的丟包率,以上種種,形成了乘數效應,耿直的程序員漢子管這叫:cluster f**k。

所以不要小看了多用了些本不該用的內存 —— 它就跟「多收了三五斗米」一樣,暗藏的蝴蝶效應也許會攪得你雞犬不寧。

好吧,其實這也不是多大點事,原本 150ms 完成的事情,現在即便最壞的情況,500ms 完成,也沒啥大不了哈。

好,咱么再換個角度,談談 capacity。

假設你一台伺服器配 16G 內存,其中有 12G 可以完全歸你的 app 所有。在 256 byte 的數據結構下,暫且不考慮內存的其他損耗(mm frag,control block 的消耗等等),你可以支撐 48M 的 capacity。2k 大小的數據結構,則縮減到 6M。還是 8 倍的差距。假設你的系統要能支持到 40M 的這種結構的熱數據,那麼,對於前者,一主兩備的 cluster,三台組個 full mesh 網路足矣;對於後者,你需要至少 3 x 7 台,可能稍稍得花些心思:full mesh 時同步的消耗會不會太大,要不要轉而使用 gossip 組 cluster 啊。

如果你對數字沒概念,那咱們換成錢 —— 16G 內存的 M4.xlarge,一小時兩毛,一個月按 750 小時算,150刀,那麼 3 台的成本是 450 刀,而 21 台是 3150 刀,換算成軟妹幣,duang 的一下,差出來一個 Jr. + 一個程序員鼓勵師的費用啊。

而且,管理更多的機器,花的時間雖然不是線性,但總歸多些,你這邊士氣 -1,人家那裡軍隊 +1,還添了個大天使,全員士氣 +1。一來一回,效率差出了不少。

當然你可以說咱初始資金多,不差錢。好,那繼續擼。

我們再看程序設計的嚴謹性。為方便閱讀,我們把上篇文章的 TCP header 再粘回來:

我們可以看到,不帶 option 的 TCP header,是 20 個位元組,5 個 word。

假設我們用 JSON 表述 TCP header:

這個 syn-ack 的 TCP header 238 個位元組,60 個 word。

沒有對比就沒有傷害,你是不是心裡默默為我前文所說「端莊優雅,透著書香」和「粗鄙」點了個贊?

從軟體設計的角度,TCP header 體現了深思熟慮 —— word 對齊,cache 友好,各個域的取值範圍定義清晰,雖然留下了一些坑(比如 window),但考慮了未來如何擴展(自己挖的坑自己填)。這裡面最關鍵是設計得很有節制,每個 bit 物盡其用,盡量減少自己的負載(overhead);對比之下,上面這個 JSON 的結構,雖然完全秉承了 tcp header 的定義,卻處處透著即興發揮的淺薄。

為了確保它正確,我們也許還需要寫一個冗長的 json schema 來規定數據結構。然而,誰又來保證 json schema 的正確呢?

以 JSON 為中心的 API 設計方案正處在這樣一種窘迫。浪費空間不說,隨處透露出的「我還沒想好需要些什麼,先放點東西上來,以後慢慢補」讓程序員無所顧忌 —— 前後端叼著雪糕抽著煙,一個「數據介面」就被「碰撞」出來。然後來點需求,加點東西,有用沒用的,反正 JSON 夠靈活,往裡塞唄。隨著時間的推移,這個結構會越來越龐雜,像是托勒密的地心說模型,最終瀕臨崩潰。

相反,如果數據結構少些靈活,多些限制,表面上看被束了手腳,騰挪餘地小,殊不知,約束並非壞事,它讓你更清晰地考慮系統的設計,從而在各種不可為中殺出一條血路,做出更好的選擇。

浪費內存,還是挺大一個事兒的。現在你知道貴司為啥沒鼓勵師了吧 :0


推薦閱讀:

Linux 的內存顯示只有 95% 被使用了,其他的呢?
動態內存申請對齊有什麼意義?
在c++中,一個對象的內存分配問題?
電腦的主存儲器和主存、內存的關係是什麼?

TAG:内存管理 | 数据结构 |