性能优化求大神指导

paredit 很容易造成不平衡的情况,比如复制内容里面不平衡。

但这不是重点,我想知道我写的这个代码怎么能有更好的性能。而不是 paredit 和 parinfer 的关系、区别、优劣和必要性之类的。

setq 1+不写成incf吗?

代码看起来很low-level了,不知道还有什么优化空间。

buffer有必要从头parse到尾?

函数调用的开销?改写成inline?

谢谢,我不知道有incf这个函数。

从头 parse 到尾是为了解决没有关闭的字符串之类的问题。就算取一段来处理,我也觉得要保证在 1000 行这个数量上性能比较好才行。

说一个比较琐碎的,pcasecl-case 比较数字用的是eql,这个函数没有自己的byte op,性能不是最快的。可以用 =(确定lhs和rhs都是数字)或者eq (确定要比较一个long int)来做数字比较

(defmacro my/number-case (form &rest clauses)
  (declare (indent 1) (debug cl-case))
  (macroexp-let2 macroexp-copyable-p form form
    `(cond ,@(mapcar (lambda (c)
                       (pcase-exhaustive c
                         (`(,head . ,handlers)
                           (if (memq head '(t otherwise))
                               `(t ,@handlers)
                             `((= ,head ,form)
                               ,@handlers)))))
                     clauses))))

(my/number-case 1
  (1 (+ 1 2))
  (3 4))
1 个赞

谢谢,但是不知道为什么换了这个方式之后反而更慢了。差30%左右的样子。

性能和 cond 里面都用 = 是一样,不知道为什么 cl-case 反而还快。

但我明白了确实一些细微的地方就可以有比较大的影响。

那可以试试用eq,eq是比=快的。是我疏忽了,因为=要分别判断 浮点,整数,大整这几个不同的数字类型。eq就是比较指针。另外一方面,你有没有把macro编译之后再做bench?macro展开也是需要时间的。

(defmacro my/number-case (form &rest clauses)
  (declare (indent 1) (debug cl-case))
  (macroexp-let2 macroexp-copyable-p form form
    `(cond ,@(mapcar (lambda (c)
                       (pcase-exhaustive c
                         (`(,head . ,handlers)
                           (if (memq head '(t otherwise))
                               `(t ,@handlers)
                             `((eq ,head ,form)
                               ,@handlers)))))
                     clauses))))

(my/number-case 1
  (1 (+ 1 2))
  (3 4))

这样的话似乎和 cl-case 的性能是一样的,可能这个地方的影响不是很大。 虽然代码里面有个很长的 case。

你是什么环境?我在Archlinux Emacs 27.0.90里,用原编译后的代码(无更改)在 flycheck.el 直接开parinfer-bench,耗时3.288438ms

flycheck 共11521行代码

ArchLinux Emacs 27.0.90 是不是你的电脑性能太好了?

你试的 flycheck.el?

有可能是中间遇到了对于 parinfer 来说的错误,停止 parse 了。 我试下


是的,有 parse 中的错误。在我这大约 10ms

原来一大片下划线是parinfer标错啊,我还以为flycheck坏了……不过就算标错我也只用了2ms,这样我觉得可能是你GC阈值太低影响性能了。

(setq gc-cons-threshold (* 20 1024 1024))

是的,遇到错误就会停下来了。我决定先放一放,等有想法了再继续。

也许能够获得一个范围是最好的办法,不过这个范围很难算。

这个函数如果用的太多是很慢的,因为他的原理是跳到point-min一行行数……不过既然你profile了没有发现,应该不是大头。


GC很多会不会是你overlay太多了?太多的话可以不用以后立即删掉overlay,而不是等着GC。

是的,初始化的时候一共只用一次。

我感觉是push和pop太频繁了,初步的一个想法就是不要一个字符一个字符的处理,看在哪些情况下可以跳过一些内容。我改了 whitespace 的逻辑,在缩进那段区域直接用 back-to-indentation 跳过去,但是好像效果不是很好。

在考虑遇到 symbol 的时候直接跳过去。

代码上好像没啥问题,再优化估计要靠算法了。

感觉 elisp 在性能上还是和 js 有比较大的差距。如果按照 js 的库一比一的 port 的话,性能惨不忍睹。js 在字符串处理上性能相当好。

elisp 里面尽量利用 buffer 里面的内容也还是差一大截。

elisp性能和Python(CPython implementation)大概同一级

按理说字符串上大家都是用utf-8,应该差不多。主要还是运行速度的差距。

Emacs 26.1 以及之前的 pcase 用的是 eq,不知道后来怎么就改成 eql 了,好像 NEWS 也没有解释

~ $ emacs-26.1 -Q --batch --eval "(print (macroexpand '(pcase 1 (1 t))))"

(if (eq 1 1) (progn t) nil)
~ $ emacs -Q --batch --eval "(print (macroexpand '(pcase 1 (1 t))))"

(if (eql 1 1) (progn t) nil)

parinfer 原本的逻辑是一个大字符串来处理的,在 emacs 里面如果把 buffer 里的字符串拿出来,好像做什么性能都不是太好。也可能我写的不好,但是那样比我现在这样的方式要差至少几十倍。

在学Elisp, 想请大神帮忙解释一下 (declare (indent 1) (debug cl-case)) 里面(debug cl-case)怎么解释, 有什么用处? 看文档在第18章完全不懂 :crazy_face: