track image
浪费内存?多大个事?

浪费内存?多大个事?

陈天陈天

遥想盖子当年,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

「真诚赞赏,手留余香」
5 人赞赏
凯凯是个好孩子
陶利
刘伟鹏
张徳力
chuan yin
文章被以下专栏收录
111 条评论
推荐阅读