之前看到 关于load-path增多对emacs运行速度的重大影响 这个帖子里使用了系统监测工具procmon对Emacs性能进行测试,而且得到了有力的结论(我一开始还不信呢)。今天学着用这个思路来分析一下Linux上Emacs的启动性能。配置是我的koishimacs。
运行strace -C -o strace-summary.log emacs -nw,启动emacs后退出,得到syscall summary如下:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
87.67 0.455477 2 159813 155809 openat
4.49 0.023314 5828 4 wait4
2.08 0.010793 6 1752 mmap
1.15 0.005955 1 4394 3306 faccessat2
1.11 0.005746 2 2346 read
0.92 0.004758 1 4018 close
0.59 0.003055 2 1234 1233 readlink
0.53 0.002743 1 1494 fstat
0.48 0.002487 4 517 brk
0.30 0.001561 1 1171 1070 ioctl
0.28 0.001463 3 379 mprotect
0.12 0.000604 604 1 execve
0.10 0.000504 1 320 fcntl
0.06 0.000324 1 316 lseek
0.04 0.000205 8 25 21 rt_sigreturn
0.03 0.000164 6 27 munmap
0.03 0.000155 4 32 getdents64
0.01 0.000042 10 4 clone3
0.01 0.000035 0 88 rt_sigprocmask
0.01 0.000028 0 43 write
0.00 0.000023 0 28 prlimit64
0.00 0.000019 3 6 unlink
0.00 0.000019 0 21 4 newfstatat
0.00 0.000017 4 4 3 access
0.00 0.000016 2 7 pipe2
0.00 0.000013 1 9 pread64
0.00 0.000013 0 20 14 readlinkat
0.00 0.000005 1 4 setpgid
0.00 0.000004 0 16 getpid
0.00 0.000002 0 13 uname
0.00 0.000001 1 1 arch_prctl
0.00 0.000001 1 1 set_tid_address
0.00 0.000001 1 1 set_robust_list
0.00 0.000001 1 1 rseq
0.00 0.000000 0 40 rt_sigaction
0.00 0.000000 0 3 dup2
0.00 0.000000 0 41 alarm
0.00 0.000000 0 3 3 mkdir
0.00 0.000000 0 4 symlink
0.00 0.000000 0 2 umask
0.00 0.000000 0 4 getuid
0.00 0.000000 0 1 getgid
0.00 0.000000 0 3 geteuid
0.00 0.000000 0 1 getegid
0.00 0.000000 0 2 getpgrp
0.00 0.000000 0 2 setfsuid
0.00 0.000000 0 2 setfsgid
0.00 0.000000 0 1 sigaltstack
0.00 0.000000 0 6 4 prctl
0.00 0.000000 0 1 gettid
0.00 0.000000 0 8 futex
0.00 0.000000 0 1 timer_create
0.00 0.000000 0 21 timer_settime
0.00 0.000000 0 2 fchmodat
0.00 0.000000 0 8 1 pselect6
0.00 0.000000 0 11 ppoll
0.00 0.000000 0 1 timerfd_create
0.00 0.000000 0 21 timerfd_settime
0.00 0.000000 0 1 eventfd2
0.00 0.000000 0 3 getrandom
0.00 0.000000 0 1 pidfd_open
------ ----------- ----------- --------- --------- ----------------
100.00 0.519548 2 178304 161468 total
高得离谱的openat调用,失败率达到了惊人的97%。Emacs的load函数使用openat而不是stat判断文件是否可以访问,在注释中有解释:stat可能产生整数宽度错误(它可能返回32位或64位对齐的数据,没有有效的宽度判断方法)。不过对于Linux的实现,它的开销应该不超过stat。问题是,为什么会有这么多的失败?我想这和之前 @junmoxiao 那个帖子里提到的load-path产生的性能问题是一样的。
于是我们对openat调用进行进一步的分析,运行strace -e trace=openat -o strace-openat.log emacs -nw,同样在启动后退出。得到所有的openat调用,分析这份数据:
-
Emacs 会依次尝试列表
load-suffix中的后缀。 这会让load的开销乘上一个固定倍数(对我来说是 6 倍)。 -
Emacs 会在
load-path的每个路径中,以该列表顺序尝试所有可能的子路径来寻找需要加载的文件,来查找由require加载的文件。这会带来非常大的额外开销(倍数྾你的load-path对应的文件树森林的结点总个数,对我来说,这个倍数达到了10²数量级)。Emacs主线已经有人针对此问题提了补丁。此外,在更早的版本上想要压缩这部分成本,还有两种“软”方式:- 在使用
require时显式指定路径名(chenbin的做法)。 - 尽可能对
load-path进行剪枝,因为搜索会按顺序遍历整个load-path(lazycat的做法)。
但是在之前的讨论里里,我们分析了,这种优化影响不到内置包,造成卡顿的其实是这些内置包在启动后的一股脑的加载,比如cl-lib。第三方包的惰性加载反而加剧了内置包的require开销。
- 在使用
-
Emacs 会在每个已安装的软件包中搜索
info.*文件,这些文件可能包含软件包文档。该变量由Info-directory-list控制,与前两项相比,这部分的影响不大。
对于之前讨论的问题,看起来include-yy已经在他的blog里整理了一份纪要。总之,看起来我只要坐等emacs31发布就行了,windows用户要考虑的就很多了 ![]()