From: Thomas Rast <trast@inf.ethz.ch>
To: <git@vger.kernel.org>
Subject: [RFC HACK] refresh_index: lstat() in inode order
Date: Tue, 6 Mar 2012 09:27:50 +0100 [thread overview]
Message-ID: <871up6cewp.fsf@thomas.inf.ethz.ch> (raw)
Hi,
This is prompted by a recent lkml discussion[1] which is also backed up
by old stuff threads like[2] where Theodore Ts'o writes about
spd_readdir.
As explained at the links, ext3&4 seem to show suboptimal performance if
you do the common pattern of lstat()'ing everything returned by
readdir() and the corresponding inodes must be fetched from disk. They
suggest (and spd_readdir does just this, in an LD_PRELOADable format)
sorting the entries by inode.
Let me emphasise: inodes fetched from disk. This does not matter at all
for the cached case.
Probably this also applies to us in the search for untracked files, but
right now I don't care about that too much because I have git-status
configured not to show them.
However, a similar trick could be applied to lstat() across the index,
which *is* a big part of the work required to accurately display
repository status in git-status and git-diff.
The dirty hack below uses the inode field in the index to sort the index
entries prior to the real work of refresh_index. I have been able to
measure a significant (in the statistical sense) speedup using
measurements like
( for i in $(seq 1 30); do
sudo sh -c 'echo 3 >/proc/sys/vm/drop_caches'
/usr/bin/time -f "time %U %S %E" ~/dev/git/git-status 2>&1 | grep time
done ) | tee results-patched
In numbers, across 30 trials for unpatched and patched git, my time
needed to get a cache-cold 'git status' went from 4.24s to 3.97s on
average, at p=0.006.
Note that this only has an effect if your directory has the inodes all
jumbled. I tried doing bigger trials, but I do not have a realistic
work repository bigger than git.git. You can look at the lstat() order
you are currently getting with
strace -e lstat -v git status 2>&1 | egrep -o 'st_ino=[0-9]+'
With the patch it should be in good sorted order. Note, however, that
at the very end it also runs lstat() on .git/refs/*; those will still be
out of order.
So I'd be interested to hear success (or non-success) stories from
people who are actively working with larger repos, perhaps linux-2.6 or
your favourite corporate repo. The lkml posts also seem to say that it
only matters on ext3&4, not on btrfs, but perhaps other FSes also suffer
in this area.
I'd also be interested to hear from the refresh_index experts whether my
change works in principle, or is already flawed in some major way. You
will note I sort by (inode,stage) to handle the search forward for the
highest stage entry, but maybe this is not the only twist. (As written
it is also not thread-safe.)
I did run the test suite and it passes, so it can't be *that* bad.
Footnotes:
[1] https://lkml.org/lkml/2012/2/29/210
[2] http://lkml.indiana.edu/hypermail/linux/kernel/0802.2/1076.html
---- 8< ----
diff --git i/read-cache.c w/read-cache.c
index 274e54b..b0d1942 100644
--- i/read-cache.c
+++ w/read-cache.c
@@ -1092,10 +1092,28 @@ static void show_file(const char * fmt, const char * name, int in_porcelain,
printf(fmt, name);
}
+static struct index_state *istate_for_cmp;
+
+int cmp_by_inode(const void *a, const void *b)
+{
+ struct cache_entry *ca, *cb;
+
+ ca = istate_for_cmp->cache[*(const int *)a];
+ cb = istate_for_cmp->cache[*(const int *)b];
+
+ if (ca->ce_ino < cb->ce_ino)
+ return -1;
+ if (ca->ce_ino > cb->ce_ino)
+ return 1;
+ if (ce_stage(ca) < ce_stage(cb))
+ return -1;
+ return 1;
+}
+
int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec,
char *seen, const char *header_msg)
{
- int i;
+ int i, j;
int has_errors = 0;
int really = (flags & REFRESH_REALLY) != 0;
int allow_unmerged = (flags & REFRESH_UNMERGED) != 0;
@@ -1110,18 +1128,28 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p
const char *typechange_fmt;
const char *added_fmt;
const char *unmerged_fmt;
+ int *by_inode_idx;
modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n");
added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n");
unmerged_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
- for (i = 0; i < istate->cache_nr; i++) {
+
+ by_inode_idx = xmalloc(istate->cache_nr * sizeof(int));
+ for (i = 0; i < istate->cache_nr; i++)
+ by_inode_idx[i] = i;
+ istate_for_cmp = istate;
+ qsort(by_inode_idx, istate->cache_nr, sizeof(int), cmp_by_inode);
+
+ for (j = 0; j < istate->cache_nr; j++) {
struct cache_entry *ce, *new;
int cache_errno = 0;
int changed = 0;
int filtered = 0;
+ i = by_inode_idx[j];
+
ce = istate->cache[i];
if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
continue;
--
Thomas Rast
trast@{inf,student}.ethz.ch
next reply other threads:[~2012-03-06 8:27 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-03-06 8:27 Thomas Rast [this message]
2012-03-06 18:37 ` [RFC HACK] refresh_index: lstat() in inode order Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=871up6cewp.fsf@thomas.inf.ethz.ch \
--to=trast@inf.ethz.ch \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).