emacs慢一大部分是gc,gc慢主要是分析大量对象是否可回收慢,其实释放不用对象空间不耗费性能。
单线程花太多时间分析gc,就会卡emacs。
有没有一种可能,在emacs外部分析emacs的内存特征和重建gc对象扫描,这样没扫完就持续扫,一旦扫完同时emacs空闲,就让emacs直接释放内存。
这样做会增加外部进程内存占用,但绝对不卡emacs,性能提升会让所有elisp插件受益。
emacs慢一大部分是gc,gc慢主要是分析大量对象是否可回收慢,其实释放不用对象空间不耗费性能。
单线程花太多时间分析gc,就会卡emacs。
有没有一种可能,在emacs外部分析emacs的内存特征和重建gc对象扫描,这样没扫完就持续扫,一旦扫完同时emacs空闲,就让emacs直接释放内存。
这样做会增加外部进程内存占用,但绝对不卡emacs,性能提升会让所有elisp插件受益。
现在流行的操作系统安全策略 (ASLR) 都不让这么搞了吧
现在scratch/igc分支使用感受快了很多,不过没有了解实现方法
感觉上在 emacs 内部开些线程做并发 GC 就行? 外部进程做 GC 暂时没想到什么特别的优势.
瞎猜现在 igc 分支上用的 GC 应该有这种设计, 一般 mark stack/reg 之类的地方需要停一下, 大部分 mark 和 sweep 就可以放在其他线程了, 然后 compaction 阶段也会停一次. 整个 GC 还是会有一些 pause, 不过比在主线程 GC 要好挺多.
出于安全考虑外部扫描基本不可行,内部专门开一个线程做这事儿?
还好把,
1 pe文件自己的重定位可以直接改pe结构的字段来关闭
2 堆内存也可以指定分配地址,如果是外部进程,也不会冲突
刚去看了下igc分支的实现 emacs/src/alloc.c at 0756b1f2f5452d715396f66d887c137776e360ca · emacs-mirror/emacs · GitHub
和原来的几个区别
1 所有的标记操作不需要在这里做了
#ifndef HAVE_MPS
struct gc_root_visitor visitor = { .visit = mark_object_root_visitor };
visit_static_gc_roots (visitor);
mark_pinned_objects ();
mark_pinned_symbols ();
mark_lread ();
mark_terminals ();
mark_kboards ();
mark_threads ();
mark_charset ();
mark_composite ();
mark_profiler ();
#ifdef HAVE_PGTK
mark_pgtkterm ();
#endif
#ifdef USE_GTK
xg_mark_data ();
#endif
#ifdef HAVE_HAIKU
mark_haiku_display ();
#endif
#ifdef HAVE_WINDOW_SYSTEM
mark_fringe_data ();
#endif
#ifdef HAVE_X_WINDOWS
mark_xterm ();
mark_xselect ();
#endif
#ifdef HAVE_ANDROID
mark_androidterm ();
#ifndef ANDROID_STUBIFY
mark_sfntfont ();
#endif
#endif
#ifdef HAVE_NS
mark_nsterm ();
#endif
mark_fns ();
#endif // not HAVE_MPS
2 实际的清理工作也不需要这里做了
#ifndef HAVE_MPS
queue_doomed_finalizers (&doomed_finalizers, &finalizers);
mark_finalizer_list (&doomed_finalizers);
/* Must happen after all other marking and before gc_sweep. */
mark_and_sweep_weak_table_contents ();
eassert (weak_hash_tables == NULL);
eassert (mark_stack_empty_p ());
gc_sweep ();
unmark_main_thread ();
#endif
不过他用mps也是用的协作式的方式,这种貌似不是在独立的线程,对于用户体验而言没有根本性的提升
确实, 现在的改法 emacs 里面不会直接调用 gc 了, 应该是在 alloc 之类的地方偷一点 cpu 时间做 concurrent gc. 看 mps 的设计应该是能在另一个线程做一些 mark 之类的事情, 但是恐怕得 benchmark 下看看才知道有没有实际收益.
目前对用户体验可能也有点提升, mps 可以做 incremental gc, 就算是在主线程, 理想情况可以把 gc 时间分散成多个小阶段, 用户的感受到的长时间停顿会少点.
感觉上比起依赖外部 gc 可能还是针对自己的需求写一个比较好. 但是 emacs 历史包袱有点重, 看 igc 分支的改动, gc 和 lisp 还有编辑器一些功能都耦合在一起了, 改起来似乎也挺头疼的.
有没有可能用rust重写C的那部分,然后消除gc呢?
gc是elisp的设计。用rust重写引擎没用
Concurrent/incremental GC 本身需要有write barrier,需要改elisp解释器。所以改动那边的代码听起来也正常。
MPS的GC算法比较先进,可能有点overkill。所以感觉这次集成MPS步子有点大。
我也感觉针对自己的需求从新写一个比较好。一个单纯的non-generational incremental GC可能代码量在1,2千行。
看到楼主写的博客里分析过现在gc的瓶颈: https://manateelazycat.github.io/2023/04/17/emacs-gc-research/
- 访问静态 GC 根(visit_static_gc_roots)的时间大约在 29 至 31 毫秒之间。
- 标记缓冲区(mark_buffers)的时间在 0.3 至 0.6 毫秒之间波动。
- GC sweep 的时间在 6.7 至 8.7 毫秒之间波动。
- GC 总时间(GC total)在大约 37 至 39 毫秒之间波动。
里面emacs GC大部分时间是耗在了objects mark里。我感觉incremental GC可以解决这个问题。
因为incremental GC在最基础的mark&sweep GC上把mark阶段做的事情分成小块做。比如原来mark一次堆需要处理100个objects。它就把100个拆成20个小块去做,每次处理5个objects,处理完之后就把cpu让给用户代码执行。这样理论上,29 ms的停顿就变成了1.45ms (29ms /20)。不过如果用户分配内存太快,GC跟不上的时候还是会退回到原来的模式,需要做一次完整的mark&sweep。
另外如果进一步分析堆上的object的生命周期,如果里面的object大多是临时object。那也可以进一步通过generational GC去进一步优化。但是实现复杂度要翻倍。
另外就是用多线程去做GC。加速里面的mark和sweep。
所以 Rust 和消除 GC 有啥关系??
可能rust本身没有gc吧
那跟要实现的动态语言有没有 GC 有毛线关系? 应该用汇编, 照样没有 GC
用错表情了应该是这样的
可能rust本身没有gc吧
如果 Emacs 一开始就是用 Go/JavaScript/Java 这种自带 GC 的语言写, 不知道是不是会方便很多. 用 js 的话扩展起来也很方便
那么用emacs的意义是啥,前有eclipse后有vscode
重点是前一句
建议先查下Emacs诞生当年的情况就明白了