Skip to content

Commit d32841b

Browse files
committed
fscache: remember not-found directories
Teach FSCACHE to remember "not found" directories. This is a performance optimization. FSCACHE is a performance optimization available for Windows. It intercepts Posix-style lstat() calls into an in-memory directory using FindFirst/FindNext. It improves performance on Windows by catching the first lstat() call in a directory, using FindFirst/ FindNext to read the list of files (and attribute data) for the entire directory into the cache, and short-cut subsequent lstat() calls in the same directory. This gives a major performance boost on Windows. However, it does not remember "not found" directories. When STATUS runs and there are missing directories, the lstat() interception fails to find the parent directory and simply return ENOENT for the file -- it does not remember that the FindFirst on the directory failed. Thus subsequent lstat() calls in the same directory, each re-attempt the FindFirst. This completely defeats any performance gains. This can be seen by doing a sparse-checkout on a large repo and then doing a read-tree to reset the skip-worktree bits and then running status. This change reduced status times for my very large repo by 60%. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 4af28e2 commit d32841b

File tree

1 file changed

+90
-2
lines changed

1 file changed

+90
-2
lines changed

compat/win32/fscache.c

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,83 @@
66
static int initialized;
77
static volatile long enabled;
88
static struct hashmap map;
9+
static struct hashmap map_nfd; /* not found directories */
910
static CRITICAL_SECTION mutex;
1011

12+
struct nfd_entry
13+
{
14+
struct hashmap_entry ent;
15+
int namelen;
16+
char *name;
17+
char buf[FLEX_ARRAY];
18+
};
19+
20+
static void nfd_init(struct nfd_entry *nfd, const char *name, size_t namelen, unsigned int hash)
21+
{
22+
nfd->namelen = namelen;
23+
nfd->name = name; /* borrow buffer from caller */
24+
hashmap_entry_init(&nfd->ent, hash);
25+
}
26+
27+
static struct nfd_entry *nfd_alloc(const char *name, size_t namelen, unsigned int hash)
28+
{
29+
struct nfd_entry *nfd = xcalloc(1, sizeof(struct nfd_entry) + namelen + 1);
30+
hashmap_entry_init(&nfd->ent, hash);
31+
nfd->namelen = namelen;
32+
nfd->name = nfd->buf;
33+
memcpy(nfd->buf, name, namelen+1);
34+
return nfd;
35+
}
36+
37+
static struct nfd_entry *nfd_find(const char *name, size_t namelen, unsigned int hash)
38+
{
39+
struct nfd_entry nfd_test;
40+
struct nfd_entry *nfd;
41+
42+
nfd_init(&nfd_test, name, namelen, hash);
43+
nfd = hashmap_get(&map_nfd, &nfd_test, NULL);
44+
45+
return nfd;
46+
}
47+
48+
static struct nfd_entry *nfd_add(const char *name, size_t namelen, unsigned int hash)
49+
{
50+
struct nfd_entry nfd_test;
51+
struct nfd_entry *nfd;
52+
53+
nfd = nfd_find(name, namelen, hash);
54+
if (!nfd) {
55+
nfd = nfd_alloc(name, namelen, hash);
56+
hashmap_add(&map_nfd, nfd);
57+
}
58+
return nfd;
59+
}
60+
61+
static void nfd_remove(struct nfd_entry *nfd)
62+
{
63+
hashmap_remove(&map_nfd, nfd, NULL);
64+
}
65+
66+
static void nfd_clear(void)
67+
{
68+
struct hashmap_iter iter;
69+
struct nfd_entry *nfd;
70+
while ((nfd = hashmap_iter_first(&map_nfd, &iter))) {
71+
nfd_remove(nfd);
72+
free(nfd);
73+
}
74+
}
75+
76+
static int nfd_cmp(const struct nfd_entry *nfd1, const struct nfd_entry *nfd2)
77+
{
78+
int max;
79+
80+
if (nfd1 == nfd2)
81+
return 0;
82+
max = ((nfd1->namelen >= nfd2->namelen) ? nfd1->namelen : nfd2->namelen);
83+
return memcmp(nfd1->name, nfd2->name, max);
84+
}
85+
1186
/*
1287
* An entry in the file system cache. Used for both entire directory listings
1388
* and file entries.
@@ -163,7 +238,7 @@ static struct fsentry *fseentry_create_entry(struct fsentry *list,
163238
* Dir should not contain trailing '/'. Use an empty string for the current
164239
* directory (not "."!).
165240
*/
166-
static struct fsentry *fsentry_create_list(const struct fsentry *dir)
241+
static struct fsentry *fsentry_create_list(const struct fsentry *dir, int *dir_not_found)
167242
{
168243
wchar_t pattern[MAX_LONG_PATH + 2]; /* + 2 for "\*" */
169244
WIN32_FIND_DATAW fdata;
@@ -172,6 +247,8 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir)
172247
struct fsentry *list, **phead;
173248
DWORD err;
174249

250+
*dir_not_found = 0;
251+
175252
/* convert name to UTF-16 and check length */
176253
if ((wlen = xutftowcs_path_ex(pattern, dir->name, MAX_LONG_PATH,
177254
dir->len, MAX_PATH - 2, core_long_paths)) < 0)
@@ -190,6 +267,7 @@ static struct fsentry *fsentry_create_list(const struct fsentry *dir)
190267
h = FindFirstFileW(pattern, &fdata);
191268
if (h == INVALID_HANDLE_VALUE) {
192269
err = GetLastError();
270+
*dir_not_found = 1;
193271
errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
194272
return NULL;
195273
}
@@ -296,6 +374,7 @@ static struct fsentry *fscache_get_wait(struct fsentry *key)
296374
static struct fsentry *fscache_get(struct fsentry *key)
297375
{
298376
struct fsentry *fse, *future, *waiter;
377+
int dir_not_found;
299378

300379
EnterCriticalSection(&mutex);
301380
/* check if entry is in cache */
@@ -313,6 +392,11 @@ static struct fsentry *fscache_get(struct fsentry *key)
313392
/* dir entry without file entry -> file doesn't exist */
314393
errno = ENOENT;
315394
return NULL;
395+
} else if (nfd_find(key->list->name, key->list->len, key->list->ent.hash)) {
396+
/* The parent directory does not exist, so neither does the file. */
397+
LeaveCriticalSection(&mutex);
398+
errno = ENOENT;
399+
return NULL;
316400
}
317401
}
318402

@@ -324,7 +408,7 @@ static struct fsentry *fscache_get(struct fsentry *key)
324408

325409
/* create the directory listing (outside mutex!) */
326410
LeaveCriticalSection(&mutex);
327-
fse = fsentry_create_list(future);
411+
fse = fsentry_create_list(future, &dir_not_found);
328412
EnterCriticalSection(&mutex);
329413

330414
/* remove future entry and signal waiting threads */
@@ -338,6 +422,8 @@ static struct fsentry *fscache_get(struct fsentry *key)
338422

339423
/* leave on error (errno set by fsentry_create_list) */
340424
if (!fse) {
425+
if (dir_not_found && key->list)
426+
nfd_add(key->list->name, key->list->len, key->list->ent.hash);
341427
LeaveCriticalSection(&mutex);
342428
return NULL;
343429
}
@@ -374,6 +460,7 @@ int fscache_enable(int enable)
374460

375461
InitializeCriticalSection(&mutex);
376462
hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, 0);
463+
hashmap_init(&map_nfd, (hashmap_cmp_fn) nfd_cmp, 0);
377464
initialized = 1;
378465
}
379466

@@ -390,6 +477,7 @@ int fscache_enable(int enable)
390477
lstat = mingw_lstat;
391478
EnterCriticalSection(&mutex);
392479
fscache_clear();
480+
nfd_clear();
393481
LeaveCriticalSection(&mutex);
394482
}
395483
return result;

0 commit comments

Comments
 (0)