Skip to content

Commit 4d3d891

Browse files
kbleesdscho
authored andcommitted
squash! Win32: add a cache below mingw's lstat and dirent implementations
mingw: add a cache below mingw's lstat and dirent implementations Checking the work tree status is quite slow on Windows, due to slow `lstat()` emulation (git calls `lstat()` once for each file in the index). Windows operating system APIs seem to be much better at scanning the status of entire directories than checking single files. Add an `lstat()` implementation that uses a cache for lstat data. Cache misses read the entire parent directory and add it to the cache. Subsequent `lstat()` calls for the same directory are served directly from the cache. Also implement `opendir()`/`readdir()`/`closedir()` so that they create and use directory listings in the cache. The cache doesn't track file system changes and doesn't plug into any modifying file APIs, so it has to be explicitly enabled for git functions that don't modify the working copy. Note: in an earlier version of this patch, the cache was always active and tracked file system changes via ReadDirectoryChangesW. However, this was much more complex and had negative impact on the performance of modifying git commands such as 'git checkout'. Signed-off-by: Karsten Blees <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 2396e2d commit 4d3d891

File tree

1 file changed

+58
-42
lines changed

1 file changed

+58
-42
lines changed

compat/win32/fscache.c

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,6 @@ struct fsentry {
4242
struct hashmap_entry ent;
4343
mode_t st_mode;
4444
ULONG reparse_tag;
45-
/* Length of name. */
46-
unsigned short len;
47-
/*
48-
* Name of the entry. For directory listings: relative path of the
49-
* directory, without trailing '/' (empty for cwd()). For file entries:
50-
* name of the file. Typically points to the end of the structure if
51-
* the fsentry is allocated on the heap (see fsentry_alloc), or to a
52-
* local variable if on the stack (see fsentry_init).
53-
*/
54-
const char *name;
5545
/* Pointer to the directory listing, or NULL for the listing itself. */
5646
struct fsentry *list;
5747
/* Pointer to the next file entry of the list. */
@@ -68,6 +58,22 @@ struct fsentry {
6858
struct timespec st_ctim;
6959
} s;
7060
} u;
61+
62+
/* Length of name. */
63+
unsigned short len;
64+
/*
65+
* Name of the entry. For directory listings: relative path of the
66+
* directory, without trailing '/' (empty for cwd()). For file entries:
67+
* name of the file. Typically points to the end of the structure if
68+
* the fsentry is allocated on the heap (see fsentry_alloc), or to a
69+
* local variable if on the stack (see fsentry_init).
70+
*/
71+
struct dirent dirent;
72+
};
73+
74+
struct heap_fsentry {
75+
struct fsentry ent;
76+
char dummy[MAX_LONG_PATH];
7177
};
7278

7379
/*
@@ -90,7 +96,7 @@ static int fsentry_cmp(void *unused_cmp_data,
9096
/* if list parts are equal, compare len and name */
9197
if (fse1->len != fse2->len)
9298
return fse1->len - fse2->len;
93-
return strnicmp(fse1->name, fse2->name, fse1->len);
99+
return strnicmp(fse1->dirent.d_name, fse2->dirent.d_name, fse1->len);
94100
}
95101

96102
/*
@@ -99,17 +105,21 @@ static int fsentry_cmp(void *unused_cmp_data,
99105
static unsigned int fsentry_hash(const struct fsentry *fse)
100106
{
101107
unsigned int hash = fse->list ? fse->list->ent.hash : 0;
102-
return hash ^ memihash(fse->name, fse->len);
108+
return hash ^ memihash(fse->dirent.d_name, fse->len);
103109
}
104110

105111
/*
106112
* Initialize an fsentry structure for use by fsentry_hash and fsentry_cmp.
107113
*/
108114
static void fsentry_init(struct fsentry *fse, struct fsentry *list,
109-
const char *name, size_t len)
115+
const char *name, size_t len)
110116
{
111117
fse->list = list;
112-
fse->name = name;
118+
if (len > MAX_LONG_PATH)
119+
BUG("Trying to allocate fsentry for long path '%.*s'",
120+
(int)len, name);
121+
memcpy(fse->dirent.d_name, name, len);
122+
fse->dirent.d_name[len] = 0;
113123
fse->len = len;
114124
hashmap_entry_init(&fse->ent, fsentry_hash(fse));
115125
}
@@ -121,12 +131,10 @@ static struct fsentry *fsentry_alloc(struct fscache *cache, struct fsentry *list
121131
size_t len)
122132
{
123133
/* overallocate fsentry and copy the name to the end */
124-
struct fsentry *fse = mem_pool_alloc(cache->mem_pool, sizeof(struct fsentry) + len + 1);
125-
char *nm = ((char*) fse) + sizeof(struct fsentry);
126-
memcpy(nm, name, len);
127-
nm[len] = 0;
134+
struct fsentry *fse =
135+
mem_pool_alloc(cache->mem_pool, sizeof(struct fsentry) + len + 1);
128136
/* init the rest of the structure */
129-
fsentry_init(fse, list, nm, len);
137+
fsentry_init(fse, list, name, len);
130138
fse->next = NULL;
131139
fse->u.refcnt = 1;
132140
return fse;
@@ -170,8 +178,9 @@ static int xwcstoutfn(char *utf, int utflen, const wchar_t *wcs, int wcslen)
170178
/*
171179
* Allocate and initialize an fsentry from a FILE_FULL_DIR_INFORMATION structure.
172180
*/
173-
static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsentry *list,
174-
PFILE_FULL_DIR_INFORMATION fdata)
181+
static struct fsentry *fseentry_create_entry(struct fscache *cache,
182+
struct fsentry *list,
183+
PFILE_FULL_DIR_INFORMATION fdata)
175184
{
176185
char buf[MAX_PATH * 3];
177186
int len;
@@ -199,15 +208,19 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
199208
is_inside_windows_container()) {
200209
size_t off = 0;
201210
if (list) {
202-
memcpy(buf, list->name, list->len);
211+
memcpy(buf, list->dirent.d_name, list->len);
203212
buf[list->len] = '/';
204213
off = list->len + 1;
205214
}
206-
memcpy(buf + off, fse->name, fse->len);
215+
memcpy(buf + off, fse->dirent.d_name, fse->len);
207216
buf[off + fse->len] = '\0';
208217
}
209218

210-
fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes, fdata->EaSize, buf);
219+
fse->st_mode =
220+
file_attr_to_st_mode(fdata->FileAttributes, fdata->EaSize, buf);
221+
fse->dirent.d_type = S_ISREG(fse->st_mode) ? DT_REG :
222+
S_ISDIR(fse->st_mode) ? DT_DIR : DT_LNK;
223+
211224
fse->u.s.st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
212225
fdata->EndOfFile.LowPart | (((off_t)fdata->EndOfFile.HighPart) << 32);
213226
filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime), &(fse->u.s.st_atim));
@@ -222,7 +235,8 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
222235
* Dir should not contain trailing '/'. Use an empty string for the current
223236
* directory (not "."!).
224237
*/
225-
static struct fsentry *fsentry_create_list(struct fscache *cache, const struct fsentry *dir,
238+
static struct fsentry *fsentry_create_list(struct fscache *cache,
239+
const struct fsentry *dir,
226240
int *dir_not_found)
227241
{
228242
wchar_t pattern[MAX_LONG_PATH];
@@ -257,14 +271,15 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
257271
err = GetLastError();
258272
*dir_not_found = 1; /* or empty directory */
259273
errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
260-
trace_printf_key(&trace_fscache, "fscache: error(%d) '%.*s'\n",
261-
errno, dir->len, dir->name);
274+
trace_printf_key(&trace_fscache, "fscache: error(%d) '%s'\n",
275+
errno, dir->dirent.d_name);
262276
return NULL;
263277
}
264278

265279
/* allocate object to hold directory listing */
266-
list = fsentry_alloc(cache, NULL, dir->name, dir->len);
280+
list = fsentry_alloc(cache, NULL, dir->dirent.d_name, dir->len);
267281
list->st_mode = S_IFDIR;
282+
list->dirent.d_type = DT_DIR;
268283

269284
/* walk directory and build linked list of fsentry structures */
270285
phead = &list->next;
@@ -312,8 +327,9 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
312327
return list;
313328

314329
Error:
315-
trace_printf_key(&trace_fscache, "fscache: status(%ld) unable to query directory contents '%.*s'\n",
316-
status, dir->len, dir->name);
330+
trace_printf_key(&trace_fscache, "fscache: status(%ld) unable to query "
331+
"directory contents '%s'\n",
332+
status, dir->dirent.d_name);
317333
CloseHandle(h);
318334
fsentry_release(list);
319335
return NULL;
@@ -553,7 +569,8 @@ void fscache_flush(void)
553569
int fscache_lstat(const char *filename, struct stat *st)
554570
{
555571
int dirlen, base, len;
556-
struct fsentry key[2], *fse;
572+
struct heap_fsentry key[2];
573+
struct fsentry *fse;
557574
struct fscache *cache = fscache_getcache();
558575

559576
if (!cache || !do_fscache_enabled(cache, filename))
@@ -570,9 +587,9 @@ int fscache_lstat(const char *filename, struct stat *st)
570587
dirlen = base ? base - 1 : 0;
571588

572589
/* lookup entry for path + name in cache */
573-
fsentry_init(key, NULL, filename, dirlen);
574-
fsentry_init(key + 1, key, filename + base, len - base);
575-
fse = fscache_get(cache, key + 1);
590+
fsentry_init(&key[0].ent, NULL, filename, dirlen);
591+
fsentry_init(&key[1].ent, &key[0].ent, filename + base, len - base);
592+
fse = fscache_get(cache, &key[1].ent);
576593
if (!fse)
577594
return -1;
578595

@@ -629,7 +646,7 @@ int fscache_is_mount_point(struct strbuf *path)
629646
typedef struct fscache_DIR {
630647
struct DIR base_dir; /* extend base struct DIR */
631648
struct fsentry *pfsentry;
632-
struct dirent dirent;
649+
struct dirent *dirent;
633650
} fscache_DIR;
634651

635652
/*
@@ -642,10 +659,8 @@ static struct dirent *fscache_readdir(DIR *base_dir)
642659
if (!next)
643660
return NULL;
644661
dir->pfsentry = next;
645-
dir->dirent.d_type = S_ISREG(next->st_mode) ? DT_REG :
646-
S_ISDIR(next->st_mode) ? DT_DIR : DT_LNK;
647-
dir->dirent.d_name = (char*) next->name;
648-
return &(dir->dirent);
662+
dir->dirent = &next->dirent;
663+
return dir->dirent;
649664
}
650665

651666
/*
@@ -665,7 +680,8 @@ static int fscache_closedir(DIR *base_dir)
665680
*/
666681
DIR *fscache_opendir(const char *dirname)
667682
{
668-
struct fsentry key, *list;
683+
struct heap_fsentry key;
684+
struct fsentry *list;
669685
fscache_DIR *dir;
670686
int len;
671687
struct fscache *cache = fscache_getcache();
@@ -681,8 +697,8 @@ DIR *fscache_opendir(const char *dirname)
681697
len--;
682698

683699
/* get directory listing from cache */
684-
fsentry_init(&key, NULL, dirname, len);
685-
list = fscache_get(cache, &key);
700+
fsentry_init(&key.ent, NULL, dirname, len);
701+
list = fscache_get(cache, &key.ent);
686702
if (!list)
687703
return NULL;
688704

0 commit comments

Comments
 (0)