14

I am forced to use Windows 10 for a project. Yes, I would rather use GNU/Linux. To keep my sanity, I've tried to regard Windows as a bootloader for Emacs :)

Unfortunately, Magit (one of my favorite parts of Emacs, which also makes up for the lack of a good command line on Windows) is unbearably slow. I have an SSD, 16 GB of RAM and a quad-core i7 but it takes eight seconds to execute magit-status on a small repository. Then, when I want to stage another change, it takes about 5 seconds per file.

Here's what I've tried:

  • $ git config --global core.preloadindex true
  • $ git config --global core.fscache true
  • $ git config --global gc.auto 256
  • Adding the entire project to the Windows Defender (my only AV) exclusion list
  • Setting the magit-git-executable to the regular msysgit one I downloaded (https://git-for-windows.github.io/). I checked and git status here takes < 1 second. I know that magit-status does way more, but this is too much.

Can anyone suggest ways to make this faster? I can't imagine anyone using Magit on Windows like this.

It was suggested that this question is a duplicate, but they asked:

I'm struggling to understand why Emacs have noticeably shorter startup time on Ubuntu than Windows. Anyone knows the answer?

I know at least some reasons why Emacs, Git, and Magit are slower on Windows. I am asking how do I optimize Magit to do fewer things, or cache results, or something, even if it's at the expense of functionality.

  • git-status takes <1 second? It should be essentially instantaneous. Is there any perceptible delay at all? – PythonNut Jan 11 '16 at 16:32
  • Do you have the same issues running the equivalent git commands from the command line? – elethan Jan 11 '16 at 16:50
  • I think magit's default choice for magit-git-executable will probably be a bit faster (the ones in cmd and bin are actually wrappers, if executable-find returns one of them magit will attempt to set magit-git-executable to the "real" git). 8 seconds for a small repository sounds like something else is wrong though, takes ~0.8s for magit's repo here (Windows 8). – npostavs Jan 11 '16 at 16:52
  • Possible duplicate of Why does Emacs take longer to start on Windows than on Linux? – nanny Jan 11 '16 at 17:24
  • 1
    Also, for a more accurate time, you could set magit-refresh-verbose to t. – nanny Jan 11 '16 at 17:32
10

I have actually done rather a lot of research on this and fundamentally the problem is that git for windows sucks

This is the upstream bug: https://github.com/git-for-windows/git/issues/596 and it requires somebody to rewrite shell scripts in C so that there is no more command forking. For me, its the interactive rebase that is the real killer (I can kick off an interactive rebase, go make tea, come back, read some news, drink the tea, and then maybe it's finished. It's much worse than a lot of people think it is), but general status-like calls are also enough to interrupt the work stride.

An alternative might be to update jGit to support the commands that magit uses, and then run it in nailgun to reduce the JVM startup time, I started a thread to discuss this: http://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03064.html

You might want to read https://stackoverflow.com/questions/4485059 for some potential speedups, but honestly you'll barely notice them.

Something you can do in magit is to follow the author's advice in setting up a minimal magit-status just for staging

;; WORKAROUND https://github.com/magit/magit/issues/2395
(define-derived-mode magit-staging-mode magit-status-mode "Magit staging"
  "Mode for showing staged and unstaged changes."
  :group 'magit-status)
(defun magit-staging-refresh-buffer ()
  (magit-insert-section (status)
    (magit-insert-untracked-files)
    (magit-insert-unstaged-changes)
    (magit-insert-staged-changes)))
(defun magit-staging ()
  (interactive)
  (magit-mode-setup #'magit-staging-mode))

so, do you know any experienced C or Java developers who'd be able to help with either of the solutions to fix git or jGit?

  • I'm not so sure the problem is shell scripts vs. C, because magit-status takes a really long time as well, and I don't think status uses many of the shell scripts. – nanny Jan 14 '16 at 16:39
  • 1
    And thank you for that "minimal" magit-status code, it seem to help me a bit (it cuts my magit-refresh time down to 2-3 seconds). – nanny Jan 14 '16 at 17:05
  • This is exactly what I want. Thanks! – Hut8 Jan 14 '16 at 17:16
  • @Hut8 how fast is magit staging for you with the minimal code? – nanny Jan 14 '16 at 20:31
  • 1
    the reason why magit-status is slow is because it calls out to git maybe 10 or 20 times. Starting new processes on Windows is extremely slow compared to GNU platforms. The shell scripts are an extreme case of this (because pretty much every statement is a new process) – fommil Jan 14 '16 at 22:45
2

Having recently looked at the a list of call-process calls from magit-status for another reason, it occured to me that some of them can be cached. With the following advice magit-status on Magit's repo goes from 1.9 to 1.3 seconds (my previous measurement of 0.8s mentioned in the comments was for a different (faster) computer). If you are already using magit-staging from the other answer it's probably not going to help much: I saw a reduction from 0.16 to 0.12 seconds (but that's barely larger than measurement noise).

WARNING: This doesn't take care of updating the cache, so things may go wrong (especially if you are fiddling with your git configuration).

(defvar-local magit-git--git-dir-cache nil)
(defvar-local magit-git--toplevel-cache nil)
(defvar-local magit-git--cdup-cache nil)

(defun memoize-rev-parse (fun &rest args)
  (pcase (car args)
    ("--git-dir"
     (unless magit-git--git-dir-cache
       (setq magit-git--git-dir-cache (apply fun args)))
     magit-git--git-dir-cache)
    ("--show-toplevel"
     (unless magit-git--toplevel-cache
       (setq magit-git--toplevel-cache (apply fun args)))
     magit-git--toplevel-cache)
    ("--show-cdup"
     (let ((cdup (assoc default-directory magit-git--cdup-cache)))
       (unless cdup
         (setq cdup (cons default-directory (apply fun args)))
         (push cdup magit-git--cdup-cache))
       (cdr cdup)))
    (_ (apply fun args))))

(advice-add 'magit-rev-parse-safe :around #'memoize-rev-parse)

(defvar-local magit-git--config-cache (make-hash-table :test 'equal))

(defun memoize-git-config (fun &rest keys)
  (let ((val (gethash keys magit-git--config-cache :nil)))
    (when (eq val :nil)
      (setq val (puthash keys (apply fun keys) magit-git--config-cache)))
    val))

(advice-add 'magit-get :around #'memoize-git-config)
(advice-add 'magit-get-boolean :around #'memoize-git-config)
  • why don't you just use the melpa.org/#/memoize package? – fommil Jan 15 '16 at 10:18
  • 1
    @fommil: I needed to memoize rev-parse per buffer for some args. I couldn't see a way to do so after a cursory look at the memoize package. Also it says it can't handle memoizing nil. – npostavs Jan 15 '16 at 15:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.