Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance #2982

Open
tarsius opened this issue Feb 1, 2017 · 55 comments
Open

Improve performance #2982

tarsius opened this issue Feb 1, 2017 · 55 comments
Labels
area: abstraction enhancement New feature or request

Comments

@tarsius
Copy link
Member

tarsius commented Feb 1, 2017

Magit's performance isn't so good to say the least. I would go as far as to say that mediocre and in certain situations outright bad performance currently is Magit's biggest problem.

We have made some improvements in the past, like e.g. caching calls to git during a buffer refresh, but these improvements are to a large extend outweighed by the addition of new features. Already there exist useful features that are disabled by default because they can be to costly in some cases.

One major difference between Git and Magit is that the latter shows all kinds of useful information up-front. The major disadvantage of Magit's approach is that this information has to be always up-to-date (else it couldn't be relied on), and that doing so costs time.

To achieve good performance the cost of keeping (primarily, but not only) the status buffer up-to-date has to be reduced. And in order to do so, some fundamental changes have to be made. Doing that will take a while because it makes it necessary to replace some core abstractions. The good news is that improving or even replacing those abstractions will have other benefits beside improved performance.


A These are the primary causes of bad performance:

  1. After the user has performed some action, the complete status buffer is refreshed. And that is done by recreating its content from scratch. Every time. All of it.

  2. The required information is collected by calling git many times. Until recently, there was no alternative, but now Emacs has a foreign function interface and so we can start using libgit2 now.

  3. Expensive parsing (primarily diffs and logs) is done synchronously, even when we don't need the complete result immediately (because most of it is not visible until the user scrolls).

  4. The results of expensive parsing are not being cached. (The result of calls to git are cached, but that mitigates the effect of (2), not (1).)

  5. Many sections are created by inserting the output directly into the user-visible buffer, where it is then manipulated to look and feel the way we want to. Without changing that, it would be very hard to address (3) and (4).


B There are three primary paths for improving performance:

  1. Implement an Elisp binding for libgit2 #2959 Start using libgit2 instead of calling git over and over again.

  2. Rethink parsing, caching, and refreshing #2985 Completely change how sections are created (using separate parsing-buffers, which likely will be preserved as a caching mechanism) and make it possible to update sections asynchronously and independently of the containing buffer.

  3. Closely related to (2), only update those parts of a buffer that actually need updating.

  4. Delay updating buffers that are not visible.

  5. Insert diffs in two steps. Get a list of the modified files. For files that are expanded get the actual diff and insert it.

Luckily these two paths can be tackled in parallel, because in most cases a given section is either created by inserting and then "washing" git output (A5) or by processing a list of items (or a single value) using Elisp and then inserting that as the sections content (A2).

So aside from this overview, these changes will also be discussed independently, in dedicated issues.

@tarsius tarsius added the enhancement New feature or request label Feb 1, 2017
@jkdufair
Copy link

jkdufair commented Feb 1, 2017

I'd love to help contribute on number 2. I've developed some decent familiarity with the section management functions. I'll keep an eye out for the new issue.

@xmo-odoo
Copy link

xmo-odoo commented Jun 23, 2017

Just want to comment that this would be really cool, especially number 2: I regularly do source-rewriting on a fairly large project with rewriters which are a bit hit-or-miss (they'll false-positive and rewrite stuff they should not and the serialisation will rewrite slightly more than it should) so there needs to be a post-op filtering to create the actual commits (remove stuff or split changes across multiple commits)

add -p/checkout -p kinda work but the lack of context and the keyboard answers means it's easy to "go automatic" and end up e.g. reverting a change which should not have or staging one which shouldn't be.

Magit's diff-committed and diff-uncommitted are absolute game-changers there, they're absolutely stellar in UI and UX. Their issue however is that staging changes are slow, and the slowness becomes huge as the diff size grows, with a ~15kloc diff (wasn't kidding about the bigness) it takes about 1mn30 between me hitting stage and Emacs being interactive again (though this is on 24.5, I should probably update).

@braham-snyder
Copy link

braham-snyder commented Jun 23, 2017

@xmo-odoo Have you tried magit-ediff-stage? Although it's not quite the same as [un-]staging directly from magit's status buffer, it avoids the regeneration of the status buffer per-stage (so will likely be far faster per-stage), while retaining better interactivity than stock git.

@xmo-odoo
Copy link

@braham-snyder I'm really not fond of ediff (though that may be because I've almost never used it so I find it odd), and the complete overview of the working copy is probably the thing I love most about magit's diff views.

@Alexander-Shukaev
Copy link

One more observation. magit-refresh can turn out to be very time consuming, especially for *-refine-*-related stuff. Oftentimes, I have magit-status (or any other magit buffer) buffer not displayed (no window displaying any of them), but when I do changes to the code in a corresponding repository, magit-refresh is still invoked. How about teaching magit to detect whether buffer(s) which it wants to refresh are not displayed, and thus queue actual refreshing on each of them (via e.g. buffer-local facilities) until they are actually displayed?

@tarsius
Copy link
Member Author

tarsius commented Mar 10, 2019

I am planning to do that too. Added it to the description above.

@jypma
Copy link

jypma commented Apr 2, 2019

There may be some good pointers over at gitstatus that can help also make magit faster. Although someone probably ought to add the multi-threading and caching improvements of gitstatusd to git itself.

@pooyataher
Copy link

pooyataher commented Apr 17, 2019

It seems that everybody here is aware of these issues, but just for the record I must mention that:

  • magit-status takes 30 seconds to run on my fast machine.

I installed magit yesterday and I was excited about it, but for now I switch back to git at command line because git status responds promptly for my small projects.

@jdtsmith
Copy link

There are three primary paths for improving performance:
1. ...
2.
3.
4.
5.

Luckily these two paths can be tackled in parallel...

Looks like this one is on an expanding front :).

@plusoptix

This comment has been minimized.

@tarsius

This comment has been minimized.

@pooyataher

This comment has been minimized.

@plusoptix
Copy link

OK, thank you for the feedback: I digged deeper: This happens, when you have gettext- / *.po files in your tree. If you dismiss all changes on these kind of files (we have about 20 in our repo), magit responses quite fast.

I tried also to start the emacs without init.el, so no special hooks are activated to open gettext files.

Is this a new bug or should I only add it here as hint for other users, which have the same issue.

@tarsius
Copy link
Member Author

tarsius commented Sep 11, 2019

Actually lets look at this a bit. 20 untracked (or ignored?) files isn't much at all.

So if you have those ~20 files, then it takes 20-40 seconds to do a simple refresh (C-g)? Please time it. And without them it takes less than a second?

Are these files being ignore by Git (i.e. is there such a rule in .gitignore)? If there wasn't such a rule, does adding one make the issue go away? Even if it does, if you just added such a rule now, then remove it again for the subsequent tests.

Does the Magit status buffer show any information about these files? (Is there a Ignored files or Untracked files section?) Does removing all magit-insert-*-files sections from magit-status-section-hook fix the issue? If so, then figure out which of these functions is the cause.

@plusoptix
Copy link

More input to this issue:
There are 20 git controller po-files in my repo
(check with $> git status | grep \.po)
changed: cs.po
changed: da.po
changed: de.po
changed: el.po
changed: en.po
changed: es.po
changed: fr.po
changed: hu.po
changed: it.po
changed: ja.po
changed: lv.po
changed: pl.po
changed: poqt_gui.pot
changed: pt.po
changed: ro.po
changed: ru.po
changed: tr.po
changed: uk.po
changed: zh_CN.po
changed: zh_TW.po
-> entering "g" at magit buffer
-> utimer -s
Elapsed Time: 0 days 00:00:39.198 (39.198 seconds)

$> git checkout cs.po da.po de.po el.po en.po es.po fr.po hu.po it.po ja.po
-> revert the first half of the files
changed: lv.po
changed: pl.po
changed: poqt_gui.pot
changed: pt.po
changed: ro.po
changed: ru.po
changed: tr.po
changed: uk.po
changed: zh_CN.po
changed: zh_TW.po
-> entering "g" at magit buffer
-> utimer -s
Elapsed Time: 0 days 00:00:23.259 (23.259 seconds)

$> git checkout da.po...
-> checkout the first half, so the second half of the files stay changed:
changed: lv.po
changed: pl.po
changed: poqt_gui.pot
changed: pt.po
changed: ro.po
changed: ru.po
changed: tr.po
changed: uk.po
changed: zh_CN.po
changed: zh_TW.po

$> git checkout *.po

  • no changed po-files left over
    -> enter "g" at magit buffer
    -> utimer -s
    Elapsed Time: 0 days 00:00:00.158 (0.158 seconds)

How large are our files:
$> wc *.po
5643 24040 189701 cs.po
4661 17088 144576 da.po
5428 23581 186507 de.po
4691 19496 182921 el.po
5623 24424 183887 en.po
5831 26770 198447 es.po
5872 26756 200695 fr.po
4341 15857 135002 hu.po
5867 26355 199595 it.po
5318 18889 188949 ja.po
5559 23807 190925 lv.po
4662 18840 155765 pl.po
5866 26783 199455 pt.po
5744 25187 192231 ro.po
5864 25840 232415 ru.po
5758 24131 192257 tr.po
4810 17666 168804 uk.po
5280 18832 177455 zh_CN.po
5273 18809 177316 zh_TW.po

About 300 translations per language. A lot of translations are sentences.
There are 7 further .po files, which are not git tracked and added into the .gitignore. These files do not interfere.

@plusoptix
Copy link

At the moment I have in my magit-status.el
(defcustom magit-status-sections-hook
'(magit-insert-status-headers
magit-insert-merge-log
magit-insert-rebase-sequence
magit-insert-am-sequence
magit-insert-sequencer-sequence
magit-insert-bisect-output
magit-insert-bisect-rest
magit-insert-bisect-log
magit-insert-untracked-files
magit-insert-unstaged-changes
magit-insert-staged-changes
magit-insert-stashes
magit-insert-unpushed-to-pushremote
magit-insert-unpushed-to-upstream-or-recent
magit-insert-unpulled-from-pushremote
magit-insert-unpulled-from-upstream)

When I remove
magit-insert-stashes
magit-insert-unpushed-to-pushremote
magit-insert-unpushed-to-upstream-or-recent
magit-insert-unpulled-from-pushremote
magit-insert-unpulled-from-upstream

Then I can reduce the time from 38 to 10 seconds

@tarsius
Copy link
Member Author

tarsius commented Sep 11, 2019

I just remembered we recently added something to make this easier: set magit-debug-refresh to t and then post the output from *Messages*.

Here's what those numbers normally look like:

Refreshing magit...
Running magit-pre-refresh-hook...done (0.002s)
Refreshing buffer `magit: magit'...
  magit-insert-error-header                          5.28e-06
  magit-insert-diff-filter-header                    3.5779e-05
  magit-insert-head-branch-header                    0.005917256
  magit-insert-upstream-branch-header                0.009664677
  magit-insert-push-branch-header                    0.00295999
  magit-insert-tags-header                           0.029366055
  magit-insert-status-headers                        0.051382107
  magit-insert-merge-log                             0.002367351
  magit-insert-rebase-sequence                       0.000558741
  magit-insert-am-sequence                           0.000267262
  magit-insert-sequencer-sequence                    0.000472803
  magit-insert-bisect-output                         0.000263354
  magit-insert-bisect-rest                           5.3672e-05
  magit-insert-bisect-log                            5.2426e-05
  magit-insert-untracked-files                       0.002597223
  magit-insert-unstaged-changes                      0.003391459
  magit-insert-staged-changes                        0.005237672
  magit-insert-stashes                               0.020132359
  magit-insert-modules                               0.002975827
  magit-insert-worktrees                             0.002223353
  magit-insert-unpushed-to-pushremote                0.041945618
  magit-insert-unpushed-to-upstream-or-recent        0.032749889
  magit-insert-unpulled-from-pushremote              0.000270675
  magit-insert-unpulled-from-upstream                0.021647526
  forge-insert-pullreqs                              0.021850482
  forge-insert-issues                                0.015691664
Refreshing buffer `magit: magit'...done (0.236s)
Running magit-post-refresh-hook...done (0.008s)
Refreshing magit...done (0.253s, cached 84/116)

@aaronjensen
Copy link

aaronjensen commented Sep 14, 2019

FYI I don't see magit-debug-refresh in the MELPA version.

FWIW, refresh takes about 400ms in my repo and I don't have forge enabled. Though I just disabled magit-todos and it brought it down to 200ms... yuck.

Edit: Is there a way to limit what gets refreshed when staging and unstaging? Ideally it'd only refresh the staged and unstaged sections and it'd be great to disable things like magit-todos during that refresh.

@kyleam
Copy link
Member

kyleam commented Sep 14, 2019

FYI I don't see magit-debug-refresh in the MELPA version.

I think that was meant to be magit-refresh-verbose.

@aaronjensen
Copy link

Ah, thanks. Looks like forge still takes a little time even if the repo isn't forge enabled yet, and magit-todos (specifically magit-todos-branch-list) is very expensive.

Refreshing buffer ‘magit: iheartjane’...
  magit-insert-error-header                          2e-06
  magit-insert-diff-filter-header                    6e-06
  magit-insert-head-branch-header                    0.015213
  magit-insert-upstream-branch-header                0.020369
  magit-insert-push-branch-header                    0.004845
  magit-insert-tags-header                           0.011144
  magit-insert-status-headers                        0.057213
  magit-insert-merge-log                             0.004333
  magit-insert-rebase-sequence                       0.000206
  magit-insert-am-sequence                           9.9e-05
  magit-insert-sequencer-sequence                    0.000195
  magit-insert-bisect-output                         0.000101
  magit-insert-bisect-rest                           2.6e-05
  magit-insert-bisect-log                            2.6e-05
  magit-insert-untracked-files                       0.02154
  magit-insert-unstaged-changes                      0.018993
  magit-insert-staged-changes                        0.010953
  magit-todos--insert-todos                          0.158363
  magit-insert-stashes                               0.006123
  magit-insert-unpushed-to-pushremote                0.026288
  magit-insert-unpushed-to-upstream                  0.011508000000000001
  magit-insert-unpulled-from-pushremote              8.9e-05
  magit-insert-unpulled-from-upstream                0.006936
  forge-insert-pullreqs                              0.042196
  forge-insert-issues                                5e-06
Refreshing buffer ‘magit: iheartjane’...done (0.387s)

@Alexander-Shukaev
Copy link

Alexander-Shukaev commented Oct 8, 2019

Repositories with large amount of both files and changes per commit, e.g. produced by generated code (e.g. when the source code is regenerated after an update to the code generator), make magit-status totally unusable. I know me and others already complained about it many times and suggested a number of improvements. However, I have a feeling that implementing the comprehensive list of improvements from the top might take a lot of time. It's also not clear what's the current status of this sub-project.

Long story short, not sure if the following idea is implied by the comprehensive list of improvements at the top but I'd still mention it. I believe a drastic performance improvement for magit-status could already be achieved if all the sections that potentially insert diffs are eliminated and only lists of the corresponding files affected per section are displayed instead. This solves three main performance problems:

  1. Don't have to wait for diffs to be calculated and inserted when they might not even be needed at this point at all.
  2. Don't grow the buffer extra large as Emacs is (to say softly) notorious of not working well with extra large buffers.
  3. Don't have to rebuild/refresh that extra large buffer over and over again upon any update (even a ridiculously small one).

However, doing only that would spoil usability too indeed. The idea is to remap the key that is currently expanding diffs (which are already inserted in the buffer) to a function that would instead create a separate buffer where sections of diff hunks are inserted for that particular file only and immediately focus it; upon quit, focus back the parent magit-status buffer. So magit-status buffer acts as a "master" buffer towards any hunk buffers opened, e.g. when magit-status buffer is killed, all hunk buffers belonging to it are killed as well (at least by default). Whatever actions/features/bindings (e.g. (un)staging, reverting, etc.) are available in the magit-status buffer for the sections of diff hunks can be replicated in such separate file diff dedicated buffer. This essentially sounds like a feature request for an additional (buffer-wise) mode of operation for magit-section, which is planned to be released separately anyway as per #4003.

This does not sound like a lot of work and can be introduced as an optional mode to use for performance reasons. What do you guys think?

NOTE:

#2985 looks interesting indeed but it's far too complex and ambitious to implement in the nearest future. One has to be reasonable with the ratio of outcome to delivery time/effort. Solving the outstanding performance problem can be done in stages, where the above approach seems appealing as the very first stage since it offers high ratio.

@Profpatsch
Copy link

it’s not a big rebase either, I’m rebasing maybe 10 commits.

@Profpatsch
Copy link

My initial profiling has pointed to

magit/lisp/magit-sequence.el

Lines 978 to 1038 in 2145477

(defun magit-sequence-insert-sequence (stop onto &optional orig)
(let ((head (magit-rev-parse "HEAD")) done)
(setq onto (if onto (magit-rev-parse onto) head))
(setq done (magit-git-lines "log" "--format=%H" (concat onto "..HEAD")))
(when (and stop (not (member (magit-rev-parse stop) done)))
(let ((id (magit-patch-id stop)))
(--if-let (--first (equal (magit-patch-id it) id) done)
(setq stop it)
(cond
((--first (magit-rev-equal it stop) done)
;; The commit's testament has been executed.
(magit-sequence-insert-commit "void" stop 'magit-sequence-drop))
;; The faith of the commit is still undecided...
((magit-anything-unmerged-p)
;; ...and time travel isn't for the faint of heart.
(magit-sequence-insert-commit "join" stop 'magit-sequence-part))
((magit-anything-modified-p t)
;; ...and the dust hasn't settled yet...
(magit-sequence-insert-commit
(let* ((magit--refresh-cache nil)
(staged (magit-commit-tree "oO" nil "HEAD"))
(unstaged (magit-commit-worktree "oO" "--reset")))
(cond
;; ...but we could end up at the same tree just by committing.
((or (magit-rev-equal staged stop)
(magit-rev-equal unstaged stop)) "goal")
;; ...but the changes are still there, untainted.
((or (equal (magit-patch-id staged) id)
(equal (magit-patch-id unstaged) id)) "same")
;; ...and some changes are gone and/or others were added.
(t "work")))
stop 'magit-sequence-part))
;; The commit is definitely gone...
((--first (magit-rev-equal it stop) done)
;; ...but all of its changes are still in effect.
(magit-sequence-insert-commit "poof" stop 'magit-sequence-drop))
(t
;; ...and some changes are gone and/or other changes were added.
(magit-sequence-insert-commit "gone" stop 'magit-sequence-drop)))
(setq stop nil))))
(dolist (rev done)
(apply 'magit-sequence-insert-commit
(cond ((equal rev stop)
;; ...but its reincarnation lives on.
;; Or it didn't die in the first place.
(list (if (and (equal rev head)
(equal (magit-patch-id rev)
(magit-patch-id orig)))
"stop" ; We haven't done anything yet.
"like") ; There are new commits.
rev (if (equal rev head)
'magit-sequence-head
'magit-sequence-stop)))
((equal rev head)
(list "done" rev 'magit-sequence-head))
(t
(list "done" rev 'magit-sequence-done)))))
(magit-sequence-insert-commit "onto" onto
(if (equal onto head)
'magit-sequence-head
'magit-sequence-onto))))
as the function taking most of the time.

@aspiers
Copy link
Contributor

aspiers commented Jan 5, 2021

If you want to do more profiling, you may find https://github.com/aspiers/etrace useful.

@black7375
Copy link

I also think it's good to use gitstatus.
https://github.com/romkatv/gitstatus

@rudolf-adamkovic
Copy link

rudolf-adamkovic commented Jun 19, 2021

[...] bad performance currently is Magit's biggest problem.

+1 Every time I rename a directory, I have to use some other Git client to commit the change, as Magit creates a huge, 500MB or even 1GB, status buffer with diffs nobody asked for, blocking the entire Emacs. It would help if we could at least hit C-g to stop Magit from diffing for the status buffer, like we can do when committing large changes.

@tarsius tarsius removed this from the 3.0.0 milestone Aug 9, 2021
@SalTor
Copy link

SalTor commented Oct 7, 2021

Regarding refreshing .. Is this an example of something that is known for magit's performance issues?

Here I show staging and unstaging a file. First I do it with s then wait for the screen to refresh, then I go to the file and unstage with u and wait for the screen refresh. There's an odd delay. Weirder still is that I can remove the delay by pressing a button after s or u such as j or k for navigation (I'm using evil mode)

Oct-07-2021 11-52-12

I have noticed similar delay and delay workarounds for other commands like committing

@tarsius
Copy link
Member Author

tarsius commented Oct 7, 2021

I have noticed similar delay and delay workarounds for other commands like committing

Lately I have noticed similar failures to redisplay for many things unrelated to Magit. I suspect there is a regression in Emacs' emacs-28 branch concerning redisplay. Were you able to reproduce this issue using Emacs v27.1?

@SalTor
Copy link

SalTor commented Oct 7, 2021

Lately I have noticed similar failures to redisplay for many things unrelated to Magit. I suspect there is a regression in Emacs' emacs-28 branch concerning redisplay. Were you able to reproduce this issue using Emacs v27.1?

I'm actually running emacs-27 version 27.2 on OSX as I come across this. I haven't tested whether I also exerience this on emacs 28

@tarsius
Copy link
Member Author

tarsius commented Oct 7, 2021

The described symptoms are very similar, but it appears this is a different issue. I have no idea what could be wrong here.

ldeck added a commit to ldeck/nix-home that referenced this issue Feb 11, 2022
See magit/magit#2982

Essentially, symlinks to git somehow perform slower.
@geza-herman
Copy link
Contributor

Let me share a change in magit which made magit-status a little bit faster for me:

  (defun magit-rev-format (format &optional rev args)
    (let ((str (magit-git-string "log" "-1" "--no-patch"
                                 (concat "--format=" format) args
                                 (if rev (concat rev "^{commit}") "HEAD") "--")))
      (unless (string-equal str "")
        str)))

In other words, use git log -1 instead of git show. I'm not sure whether this always faster or not, but on the repository I'm working on, log -1 is almost 10 times faster (the speedup can be git version dependent). Of course, magit-rev-format doesn't take too much time relative to the whole magit-status, but for me, every ms counts: I optimized the repository by removing unnecessary refs (because --decorate can take a lot of time, if there are a lot of refs), so magit-status takes only 0.2 sec. In this case, this little log -1 optimization has a noticeable effect.

@hrehfeld
Copy link

Not sure if useful, but here is a case where magit-git-wash is slow while staging -- input is sluggish. the magit status buffer is 4300 lines long if expanded:

       12617  98% - command-execute
       12307  96%  - funcall-interactively
       12304  96%   - magit-stage
       12304  96%    - magit-apply-hunk
       12304  96%     - magit-apply-patch
       12262  95%      - magit-refresh
       12214  95%       - magit-refresh-buffer
       12204  95%        - magit-status-refresh-buffer
       12114  94%         - magit-run-section-hook
        9048  70%          - magit-insert-unstaged-changes
        9022  70%           - magit--insert-diff
        9022  70%            - magit-git-wash
        8894  69%             - magit-diff-wash-diffs
        8894  69%              - magit-wash-sequence
        8894  69%               - #<compiled -0xae28e6034cd057f>
        8894  69%                - apply
        8890  69%                 - magit-diff-wash-diff
        4851  37%                  - magit-diff-insert-file-section
        4174  32%                   - magit-wash-sequence
        2400  18%                    - magit-diff-wash-hunk
        2316  18%                     - magit-delete-line
        2316  18%                      - delete-region
        2316  18%                       - apply
        2313  18%                        - ad-Advice-delete-region
         604   4%                         + #<compiled 0x8c5ddffe3ffe>
           7   0%                           vhl/.push-to-after-change-hook
           3   0%                           #<subr delete-region>
          37   0%                     + run-hook-with-args-until-success
           6   0%                     + magit-hunk-section
           3   0%                     + mapcar
           3   0%                     + magit-insert-heading
          24   0%                   + magit-insert-heading
          18   0%                     run-hook-with-args-until-success
          11   0%                   + magit-file-section
           4   0%                     eieio-oref
        2537  19%                  - magit-delete-match
        2537  19%                   - delete-region
        2537  19%                    - apply
        2533  19%                     - ad-Advice-delete-region
         559   4%                      - #<compiled 0x8c5ddffe3ffe>
         559   4%                         vhl/.pop-from-after-change-hook
           6   0%                        vhl/.push-to-after-change-hook
         835   6%                  + magit-delete-line
           3   0%                  + cl-member-if
         116   0%             + magit-git-insert
        2102  16%          + magit-insert-unpushed-to-upstream-or-recent
         457   3%          + magit-insert-staged-changes
         187   1%          + magit-insert-stashes
         116   0%          + magit-insert-status-headers
          32   0%          + magit-insert-untracked-files
          27   0%          + magit-insert-unpulled-from-upstream
           2   0%          + magit-insert-sequencer-sequence
          86   0%         + magit-section-show
           4   0%         + magit-git-exit-code
           7   0%        + magit-section-goto-successor
          37   0%       + magit-auto-revert-buffers
          42   0%      + magit-run-git-with-input
         310   2%  + byte-code
         183   1% + timer-event-handler

@ackerleytng
Copy link

If we use libgit2, can magit still work over tramp? I wonder if there's value in some kind of git server that magit can connect to, and perhaps the git server can watch key files indicating git's state (and update accordingly)

@kenkangxgwe
Copy link

Can we have a lite version of the status page?

If you work in AOSP or chromium, you will find the status pages are really slow to load, even though git status and git log --oneline are pretty fast in the command line.

@eeshugerman
Copy link

git maintenance run --task gc makes a big difference for me: magit-status was taking about ~3.5 seconds, now it's down to ~1.0 second.

@Atemu
Copy link

Atemu commented Dec 6, 2022

Interesting, that was the case for me aswell. Can you instruct git to schedule gcs more often somehow?

@corngood
Copy link

I'm doing quite a large rebase, where magit-insert-rebase-sequence takes most of the time (~5s):

         630  54%       - magit-insert-rebase-sequence
         625  54%        - magit-rebase-insert-merge-sequence
         342  29%         - magit-sequence-insert-sequence
         179  15%          - magit-sequence-insert-commit
         140  12%           - magit-format-rev-summary
         140  12%            - magit-rev-format
         138  12%             - magit-git-string
         128  11%              - apply
         128  11%               - magit-git-insert
         128  11%                - magit-process-git
         127  11%                 - magit-process-file
         127  11%                    process-file
           6   0%              + #<compiled -0x1ddfd105af7b8ad7>
         101   8%          - magit-rev-equal
         101   8%           - magit-git-success
         101   8%            - magit-git-exit-code
         101   8%             - magit-process-git
         100   8%              - magit-process-file
          99   8%                 process-file
           1   0%                 magit-process-environment
          55   4%          + magit-patch-id
           2   0%          + magit-git-lines
           1   0%          + magit-rev-parse
           1   0%          + magit-anything-unmerged-p
         277  24%         - magit-sequence-insert-commit
         226  19%          - magit-format-rev-summary
         225  19%           - magit-rev-format
         219  19%            - magit-git-string
         194  16%             - apply
         194  16%              - magit-git-insert
         194  16%               - magit-process-git
         193  16%                - magit-process-file
         193  16%                   process-file
          24   2%             + #<compiled -0x1ddfd1c6feac9ad7>
           2   0%          + magit-insert-heading
           1   0%          + run-hook-with-args-until-success
           1   0%          + eieio-oref
           3   0%         + magit-rebase--todo
           3   0%        + magit-rev-name
           2   0%        + magit-section-maybe-add-heading-map

I've removed it from magit-status-sections-hook for now, but I'll try to dig in to it when I get a chance.

@SteVwonder
Copy link

I was recently struggling with a slightly sluggish magit-status/magit-refresh in a large monorepo. After turning on verbose refresh, I noticed half the time was spent in magit-insert-untracked-files. I enabled git's new fsmonitor daemon, and the listing of untracked files went from 0.5s to 0.07s and noticeably sped up magit-refresh from 0.983s to 0.379s.

Just wanted to mention the git fsmonitor here since I didn't find any other references to it in the various performance threads in this repo. Here is the github blog post I followed to setup the daemon.

@tarsius
Copy link
Member Author

tarsius commented Jan 22, 2023

Thanks for the hint.

Yeah, I should look into that and add a note alongside the other performance hints. (I've added that to my todo list, but it might be a while until I get around to it.)

@ibizaman
Copy link

@SteVwonder thank you! Refresh on my monorepo went from 0.740s to 0.468s. Untracked time went from 0.235s to 0.028s.

@vermiculus
Copy link
Contributor

vermiculus commented Jan 6, 2024

I find that magit-insert-unpushed-to-upstream-or-recent is particularly expensive (>7s for our monorepo!) – and I wonder if it could be cached on the hashed contents of HEAD, git show-ref, and git config --list --local -z (to capture remote/merge configurations), all of which take 50ms combined on macOS. It seems this section (and a couple other sections!) are focused on answering the 'where am I now? where am I going?' questions, which these would probably answer.

Partially writing this here for its relevance – partially just writing it down so I can also investigate what can be done here once I'm back from running errands.

@tarsius
Copy link
Member Author

tarsius commented Jan 6, 2024

I find that magit-insert-unpushed-to-upstream-or-recent is particularly expensive (>7s for our monorepo!)

That surprises me. How many commits does that normally list in that repository?

@vermiculus
Copy link
Contributor

vermiculus commented Jan 6, 2024

That surprises me. How many commits does that normally list in that repository?

It's still up to the value of magit-log-section-commit-count, but this branch has >3.5 million commits with about 11GB of data in .git/objects/, so most of the time is coming from Git.

Worth noting that times improved with caching that I expect either Git or the OS is doing. I've included performance numbers in the commits for #5075.

Edit: oh, to answer your question directly, still just ten commits.

@zaphoid-beatlejuice
Copy link

Dear all,
I am an admittedly novice git and magit user, but perhaps my two cents might be of use.

(setq magit-refresh-status-buffer nil) did not affect my performance at all.

Working with a merge of a month's work I had a typical
magit-insert-staged-changes time of 42 seconds.

Every change (staging, unstaging, trying to select "ours" or "theirs" but only being told I could trash the file and refusing...) triggered it. The redisplay also constantly returned the cursor to the top of the buffer, which, it seemed, added insult to injury, if you follow my metaphor.

I found the idea of a lesser version of the status-buffer seductive, it would only show new/deleted/unmerged/modified + file path. I would deal with all of the files that I didn't need to look into, or use ediff. Perhaps commit the bulk of quick decisions and use a classic magit-status buffer for the rest.

All this now appears unnecessary.

Looking into code to try to bypass functions according to a flag, I found that there is already one: (setq magit-inhibit-refresh t) worked to effectively stop the status buffer from ever updating. (Even when pressing "g".)

Thus, after allowing the display to settle a first time, (setq magit-inhibit-refresh t) allowed me to go through each file making my decisions, sometimes ediffing, without ever lossing control. And with the diff information just a tab away the whole while.

I think I might put toggling this variable into a transient, and maybe add a (let ((magit-inhibit-refresh nil)) to (magit-refresh) when called with "g" to allow manual updating to override it.

I suggest this as an effective work-around for those whose workflow can deal with each file separately, or who are content to call updates manually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: abstraction enhancement New feature or request
Development

No branches or pull requests