关于cmdproxy的优化和magit的优化

这块的内容比较多,单独开一个帖子。
在这个帖子中 优化windows版本emacs的总结(持续更新 2024-2-6) 提到cmdproxy的存在。
它编译出来后是在类似这样的位置
C:\opt\emacs-2024-2-2\libexec\emacs\30.0.50\x86_64-w64-mingw32\cmdproxy.exe
源码的位置在nt\cmdproxy.c
这个文件到底干啥了呢,打开源码可以看到干的事情还比较多,包括用各种后缀去定位程序,处理编码。
但是单纯的命令执行如果是我自己来做,直接cmd /c 拼接下不就行了,感觉只是自己用的话没必要这么复杂。
于是用procmon监控下emacs到底用cmdproxy干了啥,
在emacs里面执行M-x shell-command whoami
如下可以看到,它仅仅是执行了cmdproxy -c whoami


再测试下async-shell-command,会发现和shell-command执行的命令相同
再测试下shell,会发现它执行的是cmdproxy -i
那就比较简单了,我自己写个程序,替换掉cmdproxy,执行速度应该会更快把。
写如下的newcmdproxy.cpp

#include <string.h>
#include <string>

int main(int argc, char** argv)
{
    if (argc < 2) {
        return -1;
    }
   
    if (strncmp(argv[1], "-c", 2) == 0) {
        std::string cmd = "cmd /c ";
        cmd += argv[2];
        ::system(cmd.c_str());
    } else {
        ::system("cmd");
    }
}

编译之后替换掉之前的cmdproxy.exe。
测试如下的elisp函数都能正常执行

shell-command
async-shell-command
shell

注意因为是直接用的cmd /c执行命令,没有处理编码,默认是gbk,需要如下设置下

(modify-coding-system-alist 'process "[cC][mM][dD][pP][rR][oO][xX][yY]" '(chinese-gbk-dos . chinese-gbk-dos))

接下来试试magit。本以为能提升速度,结果和之前没有变化。
再用procmon监控看看,发现magit并不是调用上面的函数来执行命令,貌似是直接走的call-process
这就还得再研究下了。


对magit也做了下分析,抓取进程名是git的,发现有点震惊的事情,只是打开一个git仓库,简单操作 了一下,magit就执行了好几十条命令,这想快也难呐。
就算换成libgit,估计也提升不到哪里去。

另外可以看到,magit还自己尝试用各种步骤去处理路径问题,其实这些完全没必要magit来做,判断是windows之后直接从PATH里面读就行了,为了解决兼容性问题,多启动了这么多次进程,肯定就慢。

"c:\Program Files\Git\cmd\git.exe" -c "alias.X=!x() { which \"$1\" | cygpath -mf -; }; x" X git
C:/Program Files/Git/mingw64/libexec/git-core/git.exe

git.exe -c "alias.X=!x() { which \"$1\" | cygpath -mf -; }; x" X git
C:/Program Files/Git/mingw64/libexec/git-core/git.exe

sh -c "x() { which \"$1\" | cygpath -mf -; }; x \"$@\"" "x() { which \"$1\" | cygpath -mf -; }; x" git
C:/Program Files/Git/cmd/git.exe

sh -c "x() { which \"$1\" | cygpath -mf -; }; x \"$@\"" "x() { which \"$1\" | cygpath -mf -; }; x" git
C:/Program Files/Git/cmd/git.exe

"C:\Program Files\Git\usr\bin\which.exe" git
/cmd/git

"C:\Program Files\Git\usr\bin\cygpath.exe" -mf -
从标准输入读路径进行转换

"c:\Program Files\Git\cmd\git.exe" -c "alias.P=!cygpath -wp \"$PATH\"" P
把PATH中的内容打做路径转换

git.exe -c "alias.P=!cygpath -wp \"$PATH\"" P

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --exec-path
c:/Program Files/Git/mingw64/libexec/git-core

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" -c alias.echo=!echo echo x{0}

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" config --get-all credential.helper

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" version

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 rev-parse --show-toplevel

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 config --list -z

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 update-index --refresh

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 rev-parse --verify HEAD

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 rev-parse --git-dir

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 status -z --porcelain --

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 --no-pager -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 diff --ita-visible-in-index --no-ext-diff --no-prefix --

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 rev-parse --is-bare-repository

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 --no-pager -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 diff --ita-visible-in-index --cached --no-ext-diff --no-prefix --
打出了diff的输出

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 rev-parse --verify refs/stash

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 symbolic-ref --short HEAD

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 rev-parse --verify --abbrev-ref master@{upstream}

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager --literal-pathspecs -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 rev-parse --verify HEAD~10

"c:\Program Files\Git\mingw64\libexec\git-core\git.exe" --no-pager -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 --no-pager -c core.preloadindex=true -c log.showSignature=false -c color.ui=false -c color.diff=false -c i18n.logOutputEncoding=UTF-8 log --format=%h%x0c%D%x0c%x0c%aN%x0c%at%x0c%s --decorate=full -n10 --use-mailmap --no-prefix --

这里不禁在想,如果我手动指定了git的路径,是不是就能减少前面执行的命令,试了下还真可以,用如下的命令设置之后就跳过了前面获取git程序路径,解析PATH的过程。

(setq magit-git-executable "C:\\Program Files\\Git\\mingw64\\bin\\git.exe")

不过这里有个坑,我开始是把路径设置为 C:\\Program Files\\Git\\cmd\\git.exe,就会同时执行C:\\Program Files\\Git\\mingw64\\bin\\git.exe这两个git.exe。
只有按上面那样设置才能生效,最大化减少执行的命令


总结:
1 用上面代码编译出来的程序直接替换cmdproxy.exe。对于使用上面提到的函数执行命令的插件,应该有执行速度的提升。但是对magit没用。
2 手动设置 (setq magit-git-executable "C:\\Program Files\\Git\\mingw64\\bin\\git.exe")之后对magit的启动速度有提升

4 个赞

问题分析挺详细透彻.

OS的文件会缓存, 针对一个仓库执行多个操作效率还是挺高的, 时间主要浪费在进程的多次创建上. 如果libgit2不需要创建那么多进程, 理论上效率应该会很高.

短时间内magit是没指望了, 准备尝试下emacs自带的vc功能, 据说效率比较高.

想办法提高进程启动速度也可以尝试,我的目标是让win上emacs的操作速度接近linux。目前做了一些测试,有可能可以

之前在 SE 上问了个问题, 是有关在 win 上执行 M-x term 的, 也出现了这个 cmdproxy:

到现在还没人解决…

已分析解决