编辑器是那种你永远不会退出的程序。它整天待在浏览器、邮件客户端和视频通话后面,替你保管没写完的句子。它花掉多少,就整天都在花,而且花在你同时还想拿来编译点东西的那台机器上。
于是我们不再猜测,直接测了 MeatPad ,在一台其他程序照常运行的普通工作 Mac 上。
数字
三个场景,一台机器,一个下午:
MeatPad 0.10.2 · macOS 26.5.2 · Apple 芯片
仅开笔记窗口,已运行 19 小时 63 MB 峰值 83 MB
2,721 个文件的项目,6 个标签页 108 MB 峰值 131 MB
单个 6.3 MB 文本文件 1500 MB 见下文诚实的一节
VS Code 1.130.0,同一文件夹,同样 6 个文件
710 MB 分布在 8 个进程
真正重要的是中间那一行,因为那才是干活的状态:一个真实的仓库,文件树、六个打开的标签页、语法高亮、全项目搜索和补全全部在跑。它稳定在 108 MB,然后就一直待在那里。131 MB 的峰值来自第一次遍历项目、为补全建立标识符索引的过程,这部分内存随后立刻归还。
VS Code 那一行是用来对照的,不是用来下判决的:全新的用户数据目录,一个扩展都没装,同一个文件夹、同样六个文件,启动一分钟后测量。它能做的事远比 MeatPad 多,而这个差距里很大一部分正是很多人理应想要的插件生态。这里比较的是底线,不是胜负。
自己动手验证,但小心那个错误的数字
在这件事上,你 Mac 上的两个工具彼此矛盾,而只有一个是对的。就在 MeatPad 报告 63 MB 的同一时刻, ps 对同一个进程给出的是 147 MB:
$ ps -o rss= -p $(pgrep -x MeatPad)
150304
$ footprint -p $(pgrep -x MeatPad)
phys_footprint: 63 MB
phys_footprint_peak: 83 MB
常驻集大小会把映射进这个进程的每一页都算上,包括 AppKit、SwiftUI、CoreText 以及系统其余部分那些共享的、只读的写时复制页面。这些页面在整台 Mac 上只常驻一份,不记在任何人头上。物理内存占用只统计这个进程真正需要负责的部分,这也正是活动监视器在内存一列里显示的数字。下次有人给出某个 Mac 原生应用的内存数字时,问问他测的是哪一个。
原因一:进程里根本没有浏览器
MeatPad 是原生应用:Swift、AppKit 和 SwiftUI,连同自带的框架包一共 92 个源文件、约一万五千五百行。没有 Chromium,没有 JavaScript 运行时,没有 Node 进程,没有插件宿主,也没有在应用包里塞一份私有的界面工具库。
头一百兆字节的秘密基本就在这里。建立在 Web 技术栈上的编辑器必须带着一个浏览器去上班,而浏览器是一个很有主见的操作系统:自己的排版引擎、自己的垃圾回收器、每个窗口一个进程、样样东西都有自己的一份。在画出你代码的第一个字符之前,这笔钱已经付完了。原生应用则从机器上借用排版引擎、文本引擎和界面工具库,它们本来就为了访达而加载好了。
原因二:编辑器只排你看得见的那些行
编辑器悄悄出错的地方就是文本排版。为了让滚动条高度正确,就按精确的字体和宽度去测量一份长文档的每一行,这在还没给人看到任何东西之前,是过于沉重的工作量。
MeatPad 通过 TextKit 2 上的 STTextView 绘制,这是 Apple 当前的文本栈,按可视区域工作。它只排屏幕上以及稍微往外一点的片段,这些片段一离开视野就重新被遗忘。打开一个文件,就是把它读进来并画满一屏。文档其余部分的开销被推迟到你滚动过去的那一刻,滚开之后又被释放。
原因三:你的笔记是磁盘上的文件,不是内存里的数据库
每条笔记都是一个普通文本文件,旁边配一个很小的 JSON。应用不会把一个工作区加载进内存然后住在里面。只有你真正打开的文档才持有自己的文本,关掉窗口就还回去。
同样的直觉贯穿在项目功能里:
- 文件树先只浅扫一层,让窗口立刻画出来,等完整的树准备好了再换上去。
- 全项目搜索在搜索的那一刻并发地从磁盘读取文件。两次搜索之间没有常驻内存的搜索索引,因为磁盘很快,而你的项目也没那么大。
- 补全索引保存的是每个文件的标识符计数,而不是文件内容,超过四兆字节的文件直接跳过。
- 语法高亮用的是 tree-sitter:用 C 写的小巧解析器,每个打开的文档一棵语法树,随着你输入增量更新,而不是重新构建。
一个把内存留给你编译器的编辑器。 MeatPad 是 macOS 上原生的笔记本兼代码编辑器:普通文件,无需账号,没有同步引擎,没有遥测。
了解 MeatPad →原因四:昂贵的智能住在另一个进程里
编辑器的智能确实很贵。语言服务器会持有整个项目的语义模型,对于一个大型 Rust 或 Swift 代码库来说,这个模型远远超过编辑器对文本所做的一切。
MeatPad 并不承担这份工作。它用 Language Server Protocol 与你机器上已经装好的服务器对话,SourceKit-LSP、rust-analyzer、Pyright,各自在自己的进程里,打开项目时启动,关闭项目时结束。语义模型花的钱记在服务器头上,在活动监视器里以它自己的名字出现,你一关项目就被收回。编辑器持有的是消息,不是模型。如果一个服务器都没装,补全会退回到当前项目里的标识符,其余一切照常工作。
这也是上面那些数字诚实而不是取巧的原因。它们低,不是因为功能被砍掉、被塞进另一个进程藏起来,而是因为真正需要大量内存的那部分,被允许拥有属于它自己的内存。
这个设计会咬人的地方
表格第三行不是笔误。打开一个 6.3 MB 的纯文本文件,大约十二万九千行,MeatPad 会冲过一个吉字节,并占满一个 CPU 核心。
原因不是文本引擎,它的表现完全符合宣传。原因是状态栏。状态栏显示文档的行数、词数和字符数,而它每次界面重新布局时都会遍历整篇文本把这三项重算一遍。在一条笔记或一个源文件上,那不过是几千字节的扫描,没人会察觉。在十二万九千行上,每一次布局都变成对六兆字节文本的完整遍历,而界面重新布局的次数非常多。
修起来是件小事:这些计数应该在文本变化时算一次,而不是每次绘制都算一次,它已经排进队列。我们把它写在这里而不是略过,是因为一篇只列出漂亮测量值的内存文章其实是广告。编辑器真正会被指向的日常文件,都落在前两行的范围里。特别大的单个文件是还没走到的边界,而修好它是我们的事。
这件事为什么值一个下午
卖出去的 Mac 大多是 16 GB 内存,而很少有人还剩一个吉字节可用:浏览器开着三十个标签页,设计工具开着,还有东西在编译。编辑器攥住的内存,就是编译器用不上的内存;一旦不够,macOS 就开始压缩和交换,你感受到的就是所有东西同时变得差了一点。
一个借用系统框架、只排可见内容、把文档留作文件、并让语言服务器跑在各自进程里的原生应用,在没人做过什么优化的情况下,最后落在 63 MB 左右。这不是什么英雄式的工程故事,这只是没有先去造一个浏览器的普通结果。
从一条笔记开始。 因编辑器而留下。 笔记、Markdown 和真正的代码项目,全都本地存在你的 Mac 上,装在一个可以整天开着的应用里。
下载MeatPad →