一、一个 14 年的老仓库,今天早上还在删「等待」
2026 年 10 月 1 日上午,GitHub 上一个叫 SumatraPDF 的仓库半小时内来了 14 次提交,提交标题是这样的:Remove contents editor delays、Remove trim layout delay、Remove redundant annotation waits、Poll for installer paint completion。作者 Krzysztof Kowalczyk 干的事,是把界面里残留的人为延迟一个个拔掉。
一个仓库建于 2012 年 10 月、主语言是 C 的老项目,第 14 个年头还在为几十毫秒较劲,本身就挺不合时宜的。更不合时宜的是它的体积。
同一天从官方渠道各拖一份回来实测,统一口径为 1MB=1048576 字节:
文件
实测体积
SumatraPDF 3.6.1 64 位安装包
10.56 MB(11,075,960 字节)
SumatraPDF 3.6.1 32 位安装包
9.75 MB
SumatraPDF 3.6.1 64 位绿色压缩包
9.57 MB
同上,解压后得到的唯一一个文件
19.35 MB
Adobe Acrobat Reader DC 26.002.21931 英文版安装包
748.86 MB(785,234,296 字节)
Adobe 同版本多语言安装包
822.30 MB
Adobe 那两个数字来自官方 CDN 直链的 HTTP 响应头,下载地址取自微软 winget-pkgs 里维护的官方安装清单。翻译过来就是:下载一份 Adobe 的工夫,够下载 71 份 SumatraPDF,多语言版是 78 份。
顺带泼盆冷水——中文互联网上关于它体积的说法,什么 3.9MB、7.2MB、8.5MB、10MB,几乎全不对:有的是三四年前的旧版本,有的是把「安装包」和「解压后的主程序」混着讲。要引用一个体积数字,先说清是哪个文件、哪天量的。
二、11MB 里面到底装了什么
### 一颗和自己同岁的渲染内核
SumatraPDF 在 GPL-3.0 协议下开源发布,部分代码走 BSD,完全免费,仓库地址是 github.com/sumatrapdfreader/sumatrapdf。GitHub 上 17,681 颗星、2,045 个 fork,最近一次提交就是今天。
它省体积的第一招,是把 PDF 渲染整颗外包给 MuPDF。仓库里 ext/versions.txt 这份依赖清单写得很清楚:当前版本内置 MuPDF 1.28.2,日期 2026-08-03。清单上其余 28 个依赖也全是同一路数——harfbuzz、freetype、libjpeg-turbo、lcms2、unrar、libwebp……没有 Chromium,没有 Electron,没有 WebView 里跑起来的半个浏览器。
第二招是形态:绿色版压缩包解开之后只有一个文件,SumatraPDF-3.6.1-64.exe,19.35MB,双击就用,不写注册表,能塞进 U 盘带走。官方页面上标明支持 Windows 7 / 8 / 10 / 11,32 位、64 位、ARM64 三种构建都有——2016 年那批跑着 Win7 的老机器,拿 32 位版本就能上,解压后只有 17.40MB。
### 代价是:它只肯待在 Windows 上
官方专门写了一页回答「为什么只有 Windows」,原话很实在:现任维护者不会写其他平台的程序;而且 SumatraPDF 与 Windows 深度耦合、针对它做过优化,移植等于把 UI 代码推倒重写。开源允许任何人拿源码去做一个别的系统的版本,但官方自己不打算动手。
三、1000 页是怎么「秒开」的
答案有点反直觉:它压根没把后面 999 页读进来。
PDF 文件尾部有一张交叉引用表,记录了每个对象在文件里的偏移位置。MuPDF 打开文档时只解析这张表和当前要显示的那几页,剩下的一直等到你真翻过去才解码。所以「打开」这件事的成本,与文档有多少页基本无关。
这话能不能验?能。下面这段脚本装的就是与 3.6.1 内置版本一致的 MuPDF,它会生成 10 页、100 页、1000 页三份中文 PDF,然后分别计时:
"""拿 MuPDF 实测:PDF 页数从 10 涨到 1000,打开代价到底涨多少依赖:pip install pymupdf==1.28.2(内置 MuPDF 版本与 SumatraPDF 3.6.1 一致)import osimport statisticsimport timeimport pymupdfOUT = os.path.dirname(os.path.abspath(__file__))DPI = 110 # 约等于阅读器默认版面下的实际渲染密度BODY = ("第 {n} 组样本:汇聚节点是自组网中最常被误解的部件,它既不等价于路由,""也不等价于防火墙,而是介于两者之间的一种状态同步实体。工程上最容易犯的错,""是把它的收敛时间当成常数;实测表明,在 32 节点以上规模时,收敛时间与该节点""所在簇的度分布呈明显的幂律关系,而不是教科书里假定的指数分布。"def make_pdf(path, pages, title="压力测试报告"):doc = pymupdf.open()for i in range(pages):page = doc.new_page(width=595, height=842) # A4,单位 ptpage.insert_text((56, 80), f"{title} · 第 {i + 1} 页 / 共 {pages} 页",fontsize=15, fontname="china-s")text = "\n".join(BODY.format(n=i * 6 + k + 1) for k in range(6))page.insert_textbox(pymupdf.Rect(56, 150, 539, 720), text,fontsize=10, fontname="china-s")doc.save(path, garbage=3, deflate=True)doc.close()return os.path.getsize(path)def ms(sec):return sec * 1000def measure(path, runs=7):"""先跑 1 轮热身,再取 7 轮中位数"""def once():t = time.perf_counter()doc = pymupdf.open(path)t_open = time.perf_counter()n = doc.page_countt_count = time.perf_counter()doc[0].get_pixmap(dpi=DPI)t_first = time.perf_counter()doc[n - 1].get_pixmap(dpi=DPI)t_last = time.perf_counter()doc.close()return (ms(t_open - t), ms(t_count - t_open),ms(t_first - t_count), ms(t_last - t_first), n)once() # 热身,避免第一轮的冷噪声rows = [once() for _ in range(runs)]med = tuple(statistics.median(c) for c in zip(*rows))return med[:4], rows[-1][4]def eager_pass(path):"""对照口径:打开就把整篇预处理一遍"""t = time.perf_counter()doc = pymupdf.open(path)chars = sum(len(doc[i].get_text()) for i in range(doc.page_count))cost = ms(time.perf_counter() - t)doc.close()return cost, charsif __name__ == "__main__":for pages in (10, 100, 1000):p = os.path.join(OUT, f"sample-{pages}.pdf")if not os.path.exists(p):size = make_pdf(p, pages)print(f"[生成] {pages} 页 -> {size / 1048576:.2f} MB")r, n = measure(p)print(f"[{pages:>4} 页] 打开 {r[0]:.2f} ms | 取页数 {r[1]:.2f} ms | "f"首页渲染 {r[2]:.2f} ms | 第{n}页渲染 {r[3]:.2f} ms")big = os.path.join(OUT, "sample-1000.pdf")cost, chars = eager_pass(big)print(f"[对照] 1000 页全量预处理一遍:{cost:.0f} ms,共 {chars} 字符")在一台 Apple M2 Pro(10 核、16GB 内存、macOS 26.6.2、SSD)跑出来的中位数是这样的:
文档
文件大小
打开
拿到页数
渲染首页
直接跳到最后一页
10 页
0.01 MB
0.25 ms
0.01 ms
1.14 ms
0.63 ms
100 页
0.10 MB
0.26 ms
0.01 ms
1.40 ms
0.67 ms
1000 页
0.96 MB
0.35 ms
0.09 ms
3.50 ms
0.66 ms
页数翻了 100 倍,打开的代价从 0.25 毫秒涨到 0.35 毫秒;直接跳到第 1000 页渲染,跟跳到第 10 页几乎一样快,都在 0.7 毫秒上下。(不同机器、重复跑会有几十微秒到一毫秒的浮动。)
再看对照口径:换成「一打开就把整篇过一遍」的处理方式,也就是脚本里 eager_pass 干的事,同样 1000 页要花 1077 毫秒。0.35 毫秒与 1.08 秒,中间隔着三个数量级。
这就是「1000 页秒开」这四个字的技术底座。
不过口径得说死:上面测的是渲染内核,跑在 macOS 上;真实 Windows 实机还得叠加上进程启动、窗口绘制、磁盘冷读的开销,那是另一个数量级的事。国内有开发者做过横评,用一份 189 页、126MB 的 PDF 实测冷启动:SumatraPDF 约 0.3 秒,Edge 内置阅读器 1.2 秒,Foxit Editor 约 1.8 秒,Acrobat 约 3.5 秒,某国产办公套件首次打开 2.6 秒。两个口径不在一条线上,指向却是一致的。
四、它也在变胖,而且不打算去别的平台
先看趋势线。上一个稳定版 3.5.2 的主程序单文件是 15.32MB,3.6.1 变成 19.35MB,一年半涨了 26%。把两个版本的依赖清单并排一比就知道钱花在哪儿了:新增 heicdec(HEIC/AVIF)、jxldec(JPEG XL)、cmark-gfm(Markdown)、darkmodelib(暗色主题),harfbuzz 从 2.6.4 升到 13.0.1,unrar 从 6.21 升到 7.1.6,依赖总数从 24 个涨到 29 个。
更有味道的是还在开发期的 3.7:官方版本记录写得明明白白,选中一段文字右键「翻译」,子菜单里出现了 Grok Build、Claude Code、OpenAI Codex、Antigravity 这些名字;连内置手册的搜索框都支持把问题丢给 ChatGPT 或 Grok。
一个靠「小」出名的软件,正在把边界往 AI 时代推。这是它的进化,也是它的风险:每加一个「顺手就有」的功能,那个 10.56MB 的安装包就得再长一点。维护者今天早删的那些 delay,某种程度上就是在给这些新东西找补时间。
另一处必须摊开讲的是适用边界。它没有 OCR,没有云同步,重度编辑——比如批量表单流转、页面重排、扫描件校对——远比不上 Acrobat 那一派;Mac 和 Linux 版本更是连影子都没有,官方已经把话说死了。把它当成「读」的工具,它几乎无敌;当成「处理」的工具,它会让你失望。
五、什么人该把它装进那台旧电脑
答案其实挺窄,但落在四类人身上都很实在。
第一类是论文和标准的重度读者。每天要在几十份几十 MB 的 PDF 之间来回跳的人,等业务软件启动的时间足够翻三页了。第二类是手上有旧机器要处置的人:一台 2016 年的笔记本,4GB 内存、机械硬盘,装 32 位版本,17.40MB,不怕慢。第三类是要带资料出门的人:绿色版一个 exe,U 盘里放着,图书馆的公用电脑上直接双击,走的时候不留下东西。第四类是反感被采集数据的人:没有账户、没有云、没有后台服务。
反过来,如果日常工作是批注校对、表单流程、数字签名、扫描件 OCR 那一套,那这个轻量级阅读器帮不上忙,还是得回到 Acrobat 那一类重型工具上——代价是它的安装包比你操作系统补丁还大。
下载渠道建议走官网或 GitHub 仓库 github.com/sumatrapdfreader/sumatrapdf,绿色版和安装包随取;不建议从各类「高速下载站」拿,那些地方往往帮你多装一份别的东西。
软件做大是商业公司的本能,一个 14 年的开源项目却把每一次提交都花在「删掉等待」上,这种偏执在今天确实少见。只是好奇:要是哪天它的安装包也涨到三位数 MB,你还会像现在这样无条件信任它吗?
热门跟贴