联系支持

我们通过电子邮件回复,通常两天内。

为防止滥用,Google reCAPTCHA 会检查这次提交,其间会向 Google 传输数据。脚本只在你打开此表单时才加载。

← 全部文章

每个请求都要加载整个 lib/

这个服务唯一入口文件的第 46 行:

foreach (glob(__DIR__ . '/lib/*.php') as $f) { require_once $f; }

那是 65 个文件、1.5 兆字节,每个请求都要加载一遍。计数器图片、统计页面、404 — 一视同仁。昨天这件事发生了 20,734 次。

26 个样式家族 — 715 KB,用得上一个 旗帜 189 KB · 图表 158 KB · 其他一切 470 KB 冷启动,无字节码缓存:132 毫秒 字节码来自磁盘:18.2 毫秒 字节码在共享内存:0.33 毫秒 — glob 加 65 次 stat 65 个文件,1.5 MB,56 个类,每个请求

要紧的是那个小数字

把这一切从头加载一遍,关掉字节码缓存,大约要 132 毫秒。这是会让此事成为丑闻的数字,而它不是服务器实际付出的数字。

当字节码已经编译好、放在共享内存里时,剩下的就是列一次目录,加上每个文件一次 stat(),因为时间戳校验是开着的:0.33 毫秒。按昨天的流量算,一整天不到七秒的工作。

中间还有第三个测量值,18.2 毫秒,那是字节码缓存从磁盘读而不是从内存读的情形。它是上界而不是真实值 — 是缓存存在、但比真正的那个慢时会发生的事。之所以写出来,是因为它是命令行能测到的唯一一个热态数值,而假装不是这样就是那个省事的错误。

所以:这几乎不花什么,而它几乎不花什么完全是靠着别的东西。把字节码缓存关掉,同一行就要贵四百倍。这是一条真实的依赖,知道它在那儿,好过在一次升级中发现它。

这 1.5 兆字节里到底是什么

其中百分之四十七 — 26 个文件、715 千字节 — 是计数器样式家族。画一张计数器图片恰好需要其中一个。另外 25 个被加载、被声明,然后从不被碰。

接下来:189 千字节的旗帜数据,国家页面上用得到;158 千字节的绘图库,有东西要画图时用得到。这两样都不参与生成计数器图片,而那正是这个服务回答得最多的一种请求。

这件事可见的代价是内存而不是时间。每请求的内存峰值多出大约四兆字节,而且不同于字节码 — 所有进程共享 — 这一部分是按请求付的,并行地,由每一个工作进程同时付。

有人已经注意到了

这个 glob 是 lib/*.php。它不往下递归,而正下方有两个目录:lib/live/ 229 千字节,lib/recht/ 182 千字节。四百千字节,曾经在加载路径上,现在不在了。

它们被挪走,是因为又大又少用:统计区块和法律文本。低一层目录,就是全部机制。没有配置,没有惰性加载器,没有自动加载映射 — 一个文件要么在 glob 里,要么不在。

这是一次真正的修复,值得说出来,因为显而易见的下一步会是自动加载器,而在这里,自动加载器是为一个 mkdir 已经解决过两次的问题做一次大改动。

为什么它留着

一个 glob 是能奏效的最简单的做法,而按实测它确实管用:三分之一毫秒,在一台树莓派上,在必须快的那条路径上。换成逐个引入,意味着每个新文件都得在某处加一行,而漏掉的那一行会在生产环境上引发致命错误,而不是让加载稍微大一点。

缺的是把当初让它可承受的那件事写下来:lib/ 里的某个文件变大时,它搬进子目录,并在用到它的地方引入。那正是那两个目录遵循的规则,而在今天之前它没有写在任何地方。

一般化的说法比实测更无趣。「什么都加载什么」通常是错误的形状,而在重建之前,值得先弄清楚它到底有没有让你付出什么。在这里它花掉 0.33 毫秒和四兆字节,而四兆字节才是需要盯着的那一部分,因为它随并发增长,毫秒不会。

广告