Skip to contentSkip to conversation

diren scan profiling report

RFC In review Machida, Hiroyuki 4 participants 22 messages Aug 30, 2005 – Sep 14, 2005 VFAT/FAT/msdos filesystem

lore
Share & tools
lore
#0 lore raw

As I said in "[RFC] FAT dirent scan with hint"

	<[email protected]>, we realized that FAT/VFAT has

poor performance with scanning directory entries.

Per discussions with Ogawa-san, VFAT maintainer, I took profiling data to seek better solution. Here are results attached.

In short, I would say we need to reduce following factors.
a) number of iterations inside fat_search_long()
b) number of callings to uni16_to_x8()
c) number of callings to fat__get_entry(), for short name scan.

In another E-mail, I'll send revised version patch which use hint values to scan dirent. That patch would reduce number of iterations inside fat_search_long() and fat_scan(). Those contributes reductions above a)-c) factors.

RESULTS:

1-1) Top 10 function consuming time on long file name dir scan (vfat_lookup) for 4095th LFN entry.

% kd -n 10 /tmp/lfn_kft-a.log
Function Count Time Average Local
----------------------------------- ----- -------- -------- --------

vfat_lookup                             1  1242285  1242285      705
vfat_find                               1  1241522  1241522      629
fat_search_long                         1  1240893  1240893   887490
uni16_to_x8                          4209   250222       59   249143
fat_get_entry                         765    69908       91     3306
fat__get_entry                        762    66602       87    50796
fat_shortname2uni                     593    33158       55    11393
fat_short2lower_uni                   414    21765       52    20860
fat_bmap                              202    13425       66      857
fat_bmap_cluster                      201    12568       62     1015

*)To exclude profiling overhead, doesn't count functions < 50usec **) Remove "inline" from fat/dir.c to count up inline funcs.

1-2) Top 10 function consuming time on short file name dir scan (fat_scan) for 4095th short file name entry.

% kd -n 10 /tmp/kft-a.log
Function Count Time Average Local
----------------------------------- ----- -------- -------- --------
fat_scan 1 149743 149743 68069

fat_get_short_entry                   812    81512      100    11706
fat_get_entry                         765    69806       91     3252
fat__get_entry                        762    66554       87    50425
fat_bmap                              199    13181       66      838
fat_bmap_cluster                      198    12343       62     1055
fat_get_cluster                       194    11288       58     8481
fat_ent_read                           52     2807       53     2583
__getblk                               32     2339       73      326
__bread                                29     2145       73      598

*)To exclude profiling overhead, doesn't count functions < 50usec **) Remove "inline" from fat/dir.c to count up inline funcs.

2-1) how to get result 1-1)

% ( cat <<__CONF
new
begin

        trigger start entry 0xc00cd904  # vfat_lookup
        trigger stop exit 0xc00cd904    # vfat_lookup
        filter mintime 50
        filter maxtime 0
        filter noints
        logentries      5000000

end

__CONF
) > /proc/kft

% echo prime > /proc/kft

# mount vtat

% time stat 4095th-shortfilename-entry

real    0m1.351s
user    0m0.007s
sys     0m1.295s
# umount
# get data from /proc/kft_data

2-2) how to get result 1-2)

% ( cat <<__CONF
new
begin

        trigger start entry 0xc00c36dc # fat_scan
        trigger stop exit 0xc00c36dc # fat_scan
        filter mintime 50
        filter maxtime 0
        filter noints
        logentries      5000000

end
__CONF
) > /proc/kft

% echo prime > /proc/kft

# mount msdos

% time stat 4095th-shortname-entry

real    0m0.216s
user    0m0.002s
sys     0m0.200s
# umount
# get data from /proc/kft_data
Signature · 2 lines
Hiroyuki Machida		[email protected]		
SSW Dept. HENC, Sony Corp.
#1 lore raw

PATCH 1/1 FAT dirent scan with hin take #2

Here is a revised version of dirent scan patch, mentioned at following E-mail.

This patch addresses performance damages on "ls | xargs xxx" and reverse order scan which are reported to the previous patch.

With this patch, fat_search_long() and fat_scan() use hint value as start of scan. For each directory holds multiple hint value entries. The entry would be selected by hash value based on scan target name and PID. Hint value would be calculated based on the entry previously found entry, so that the hint can cover backward neighborhood.

This patch is for 2.6.12, because I tested it at the last weekend...

Quoted 13 lines · Hiroyuki

As I said in "[RFC] FAT dirent scan with hint"

	<[email protected]>, we realized that FAT/VFAT has

poor performance with scanning directory entries.

Per discussions with Ogawa-san, VFAT maintainer, I took profiling data to seek better solution. Here are results attached.

In short, I would say we need to reduce following factors.
a) number of iterations inside fat_search_long()
b) number of callings to uni16_to_x8()
c) number of callings to fat__get_entry(), for short name scan.

Quoted 5 lines

In another E-mail, I'll send revised version patch which use hint values to scan dirent. That patch would reduce number of iterations inside fat_search_long() and fat_scan(). Those contributes reductions above a)-c) factors.

	:
	snip
	:
Signature · 2 lines
Hiroyuki Machida		[email protected]		
SSW Dept. HENC, Sony Corp.

This patch enables using hint nformation on scanning dir. It reaches excelent performance with "ls -l" for over 1000 entries.

* fat-dirscan-with-hint.patch

 fs/fat/dir.c             |  133 ++++++++++++++++++++++++++++++++++++++++++++---
 fs/fat/inode.c           |   13 ++++
 include/linux/msdos_fs.h |    2 
 3 files changed, 140 insertions(+), 8 deletions(-)

* modified files

include/linux/msdos_fs.h +2 -0
@@ -255,6 +255,8 @@ struct msdos_inode_info {
 	/* for avoiding the race between fat_free() and fat_get_cluster() */
 	unsigned int cache_valid_id;
 
+	struct semaphore *scan_lock;	/* lock for dirscan hints */
+	loff_t *scan_hints;	/* dirscan hints */
 	loff_t mmu_private;
 	int i_start;		/* first cluster or 0 */
 	int i_logstart;		/* logical first cluster */
fs/fat/dir.c +125 -8

175 lines not on this page · raw message

fs/fat/inode.c +13 -0
@@ -242,6 +242,8 @@ static int fat_fill_inode(struct inode *
 	inode->i_version++;
 	inode->i_generation = get_seconds();
 
+	init_MUTEX(&(MSDOS_I(inode)->scan_lock);
+	MSDOS_I(inode)->scan_hints = 0;
 	if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {
 		inode->i_generation &= ~1;
 		inode->i_mode = MSDOS_MKMODE(de->attr,
@@ -345,6 +347,15 @@ static void fat_delete_inode(struct inod
 static void fat_clear_inode(struct inode *inode)
 {
 	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+	void *hints;
+
+	down(&(MSDOS_I(inode)->scan_lock);
+	hints = (void *)(MSDOS_I(inode)->scan_hints);
+	if (hints) {
+		MSDOS_I(inode)->scan_hints = 0;
+	}
+	up(&(MSDOS_I(inode)->scan_lock);
+	if (hints) kfree(hints);
 
 	if (is_bad_inode(inode))
 		return;
@@ -1011,6 +1022,8 @@ static int fat_read_root(struct inode *i
 	struct msdos_sb_info *sbi = MSDOS_SB(sb);
 	int error;
 
+	init_MUTEX(&(MSDOS_I(inode)->scan_lock);
+	MSDOS_I(inode)->scan_hints = 0;
 	MSDOS_I(inode)->i_pos = 0;
 	inode->i_uid = sbi->options.fs_uid;
 	inode->i_gid = sbi->options.fs_gid;
#2 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #2

Hi,

Some coding style nitpicks.

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 222

Quoted 12 lines · Hiroyuki
fs/fat/dir.c +12 -0
+inline
+static int hint_allocate(struct inode *dir)
+{
+       void *hints;
+       int err = 0;
+
+       if (!MSDOS_I(dir)->scan_hints) {
+               hints = kmalloc(FAT_SCAN_NWAY*sizeof(loff_t),GFP_KERNEL);
+               if (hints)
+                       memset(hints, 0, FAT_SCAN_NWAY*sizeof(loff_t));
+               else
+                       err = -ENOMEM;

Please consider using kcalloc().

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 225

Quoted 3 lines
fs/fat/dir.c +3 -0
+
+               down(&MSDOS_I(dir)->scan_lock);
+               if (MSDOS_I(dir)->scan_hints) err = -EINVAL;

Please put the statement after if clause to a separate line. The above makes code very hard to read.

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 229

Quoted 4 lines
fs/fat/dir.c +4 -0
+               if (!err) MSDOS_I(dir)->scan_hints = hints;
+               up(&MSDOS_I(dir)->scan_lock);
+               if (err == -EINVAL) {
+                       if (hints) kfree(hints);

kfree() can handle NULLs just fine so please drop the redundant check.

Pekka

#3 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #2

In reply to Machida, Hiroyuki

Hi,

Some more.

Quoted 5 lines · Hiroyuki
fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 renamed from fs/fat/inode.c 2005-08-29 09:38:53.308587787 +0900 +0 -0
@@ -345,6 +347,15 @@ static void fat_delete_inode(struct inod
 static void fat_clear_inode(struct inode *inode)
 {

fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 static void fat_delete_inode(struct inod · line 358

Quoted 10 lines
fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 renamed from fs/fat/inode.c 2005-08-29 09:38:53.308587787 +0900 +9 -0
        struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+       void *hints;
+
+       down(&(MSDOS_I(inode)->scan_lock);
+       hints = (void *)(MSDOS_I(inode)->scan_hints);
+       if (hints) {
+               MSDOS_I(inode)->scan_hints = 0;
+       }
+       up(&(MSDOS_I(inode)->scan_lock);
+       if (hints) kfree(hints);

Please just make the local variable hints of type loff_t * to get rid of the pointless casting.

fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 static int fat_read_root(struct inode *i · line 1026

Quoted 9 lines
fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 renamed from fs/fat/inode.c 2005-08-29 09:38:53.308587787 +0900 +2 -0
        if (is_bad_inode(inode))
                return;
@@ -1011,6 +1022,8 @@ static int fat_read_root(struct inode *i
        struct msdos_sb_info *sbi = MSDOS_SB(sb);
        int error;
 
+       init_MUTEX(&(MSDOS_I(inode)->scan_lock);
+       MSDOS_I(inode)->scan_hints = 0;

Use NULLs instead of 0 for pointers to keep new code sparse-clean.

#4 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #2

Quoted 15 lines · Pekka Enberg

Hi,

Some more.

Quoted 10 lines · Hiroyuki
fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 renamed from fs/fat/inode.c 2005-08-29 09:38:53.308587787 +0900 +4 -0
@@ -345,6 +347,15 @@ static void fat_delete_inode(struct inod
 static void fat_clear_inode(struct inode *inode)
 {
        struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+       void *hints;
+
+       down(&(MSDOS_I(inode)->scan_lock);
+       hints = (void *)(MSDOS_I(inode)->scan_hints);
Quoted 8 lines

fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 static void fat_delete_inode(struct inod · line 358

Quoted 5 lines
fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 renamed from fs/fat/inode.c 2005-08-29 09:38:53.308587787 +0900 +5 -0
+       if (hints) {
+               MSDOS_I(inode)->scan_hints = 0;
+       }
+       up(&(MSDOS_I(inode)->scan_lock);
+       if (hints) kfree(hints);

Please just make the local variable hints of type loff_t * to get rid of the pointless casting.

For voids no casting is needed anyway, the type exists for the very reason of being compatible with any other.

Quoted 10 lines

fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 static int fat_read_root(struct inode *i · line 1026

Quoted 8 lines
fs/fat/inode.c 2005-08-29 09:39:33.889555606 +0900 renamed from fs/fat/inode.c 2005-08-29 09:38:53.308587787 +0900 +2 -0
        if (is_bad_inode(inode))
                return;
@@ -1011,6 +1022,8 @@ static int fat_read_root(struct inode *i
        struct msdos_sb_info *sbi = MSDOS_SB(sb);
        int error;
 
+       init_MUTEX(&(MSDOS_I(inode)->scan_lock);
+       MSDOS_I(inode)->scan_hints = 0;

Use NULLs instead of 0 for pointers to keep new code sparse-clean.

It's a good idea for readability, too.

Signature · 2 lines
Vojtech Pavlik
SuSE Labs, SuSE CR
maintainer
#5 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #2

In reply to Machida, Hiroyuki
Quoted 11 lines · Hiroyuki

Here is a revised version of dirent scan patch, mentioned at following E-mail.

This patch addresses performance damages on "ls | xargs xxx" and reverse order scan which are reported to the previous patch.

With this patch, fat_search_long() and fat_scan() use hint value as start of scan. For each directory holds multiple hint value entries. The entry would be selected by hash value based on scan target name and PID. Hint value would be calculated based on the entry previously found entry, so that the hint can cover backward neighborhood.

This patch couldn't compile. I assume you post a wrong patch...?

The code is strange... Is the hint value related to the previously accessed entry?

This seems to be randomly cacheing the recent access position... Is it your intention of this patch?

Signature · 1 line
OGAWA Hirofumi <[email protected]>
maintainer
#6 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #2

Quoted 1 line · OGAWA Hirofumi

This patch couldn't compile. I assume you post a wrong patch...?

                                                         ^^^^^
                                                         version
Signature · 1 line
OGAWA Hirofumi <[email protected]>
#7 lore raw

PATCH 1/1 FAT dirent scan with hin take #3

In reply to OGAWA Hirofumi

Sorry, I send out wrong version. I attached the latest patch to 2.6.13.

Quoted 16 lines · OGAWA Hirofumi
Quoted 11 lines · Hiroyuki

Here is a revised version of dirent scan patch, mentioned at following E-mail.

This patch addresses performance damages on "ls | xargs xxx" and reverse order scan which are reported to the previous patch.

With this patch, fat_search_long() and fat_scan() use hint value as start of scan. For each directory holds multiple hint value entries. The entry would be selected by hash value based on scan target name and PID. Hint value would be calculated based on the entry previously found entry, so that the hint can cover backward neighborhood.

Quoted 7 lines

This patch couldn't compile. I assume you post a wrong patch...?

The code is strange... Is the hint value related to the previously accessed entry?

This seems to be randomly cacheing the recent access position... Is it your intention of this patch?

Right, it looks like TLB, which holds cache "Physical addres" correponding to "Logical address". In this case, PID and file name to be looked up, perform role of "Logical address".

I prepared vfat16 fs where over 4000 files in /foo and 200 files in root, then checked with following cases;

without patch with patch
ls-vfat 59.0 2.49
xargs-vfat 61.3 11.9
stat-vfat 41 17
stat-vfat-reverse 56 32

ls-msdos 14.2 0.62
xargs-msdos 16.8 16.7
stat-msdos 21 15
stat-msdos-reverse 22 16
- all valuses have sec unit

1-1 ls-vat)
mount vfat to /a
/usr/bin/time -p sh -c "/usr/bin/ls -Rl /a > /dev/null" umount /a

1-2 ls-msdos)
mount msdos to /a
/usr/bin/time -p sh -c "/usr/bin/ls -Rl /a > /dev/null" umount /a

2-1 xargs-vfat)
mount vfat to /a
cd /a/foo
/usr/bin/time -p sh -c "(/usr/bin/ls | /usr/in/xargs stat) > /dev/null" umount /a

2-2 xargs-msdos)
mount msdos to /a
cd /a/foo
/usr/bin/time -p sh -c "(/usr/bin/ls | /usr/in/xargs stat) > /dev/null" umount /a

3-1 stat-vfat)
mount vfat to /a
cd /a/foo
repeat stat files which have located in the last 1024 dir entries
with incremental order.
umount /a

3-2 stat-vfat-reverse)
do 3-1 with decremental order

3-3 stat-msdos)
do 3-1 with msdos fs

3-4 stat-msdos-reverse)
do 3-2 with msdos fs

Signature · 1 line
Hiroyuki Machida

This patch enables using hint information on scanning dir. It achieves excellent performance with "ls -l" for over 1000 entries.

* fat-dirscan-with-hint_3.patch for linux 2.6.13

 fs/fat/dir.c             |  130 ++++++++++++++++++++++++++++++++++++++++++++---
 fs/fat/inode.c           |   13 ++++
 include/linux/msdos_fs.h |    2 
 3 files changed, 137 insertions(+), 8 deletions(-)

* modified files

fs/fat/dir.c +122 -8
@@ -201,6 +201,88 @@ fat_shortname2uni(struct nls_table *nls,
  * Return values: negative -> error, 0 -> not found, positive -> found,
  * value is the total amount of slots, including the shortname entry.
  */
+
+#define FAT_SCAN_SHIFT	4			/* 2x8 way scan hints  */
+#define FAT_SCAN_NWAY	(1<<FAT_SCAN_SHIFT)
+
+inline
+static int hint_allocate(struct inode *dir)
+{
+	loff_t *hints;
+	int err = 0;
+
+	if (!MSDOS_I(dir)->scan_hints) {
+		hints = kcalloc(FAT_SCAN_NWAY, sizeof(loff_t), GFP_KERNEL);
+		if (!hints) 
+			err = -ENOMEM;
+
+		down(&MSDOS_I(dir)->scan_lock);
+		if (MSDOS_I(dir)->scan_hints)
+			err = -EINVAL;
+		if (!err)
+			MSDOS_I(dir)->scan_hints = hints;
+		up(&MSDOS_I(dir)->scan_lock);
+		if (err == -EINVAL) {
+			kfree(hints);
+			err = 0;
+		}
+	}
+	return err;
+}
+
+
+inline
+static void hint_record(struct inode *dir, struct fat_slot_info *sinfo, 
+			  int hindex)
+{
+	loff_t under_scan_off, nent;
+
+	nent = (dir->i_size > PAGE_SIZE ? dir->i_size : PAGE_SIZE)
+		/ sizeof(struct msdos_dir_entry);
+
+	/* educational guess; try to cover 1/4 previous range */
+	nent >>= (FAT_SCAN_SHIFT + 2);
+	under_scan_off = nent * sizeof(struct msdos_dir_entry);
+
+	if (sinfo->slot_off > under_scan_off) 
+		MSDOS_I(dir)->scan_hints[hindex] =
+			sinfo->slot_off - under_scan_off;  
+	else
+		MSDOS_I(dir)->scan_hints[hindex] = 0;  
+}
+
+
+inline 
+static int hint_index_body(const unsigned char *name, int name_len, int check_null)
+{
+	int i;
+	int val = 0;
+	unsigned char *p = (unsigned char *) name;
+	int id = current->pid;
+	
+	for (i=0; i<name_len; i++) {
+		if (check_null && !*p) break;
+		val = ((val << 1) & 0xfe) | ((val & 0x80) ? 1 : 0);
+		val ^= *p;
+		p ++;
+	}
+	id = ((id >> 8) & 0xf) ^ (id & 0xf);
+	val = (val << 1) | (id & 1);
+	return val & (FAT_SCAN_NWAY-1);
+}
+
+inline 
+static int lfn_hint_index(const unsigned char *name, int name_len)
+{
+	return hint_index_body(name, name_len, 0);
+}
+
+inline 
+static int hint_index(const unsigned char *name)
+{
+	return hint_index_body(name, MSDOS_NAME, 1);
+}
+
 int fat_search_long(struct inode *inode, const unsigned char *name,
 		    int name_len, struct fat_slot_info *sinfo)
 {
@@ -218,13 +300,26 @@ int fat_search_long(struct inode *inode,
 	int utf8 = sbi->options.utf8;
 	int anycase = (sbi->options.name_check != 's');
 	unsigned short opt_shortname = sbi->options.shortname;
-	loff_t cpos = 0;
 	int chl, i, j, last_u, err;
+	loff_t cpos, start_off;
+	int reach_bottom = 0;
+	int hindex;
+	int ret;
+
+	ret = hint_allocate(inode); 
+	if (ret < 0) return ret;
+	hindex = lfn_hint_index(name, name_len);
+	start_off = cpos =  MSDOS_I(inode)->scan_hints[hindex];
 
 	err = -ENOENT;
 	while(1) {
-		if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
-			goto EODir;
+Top:
+		if (reach_bottom && cpos >= start_off) goto EODir;
+		if (fat_get_entry(inode, &cpos, &bh, &de) == -1) {
+			if (!start_off) goto EODir;
+			reach_bottom ++; cpos = 0;
+			continue;
+		}
 parse_record:
 		nr_slots = 0;
 		if (de->name[0] == DELETED_FLAG)
@@ -274,8 +369,11 @@ parse_long:
 				if (ds->id & 0x40) {
 					unicode[offset + 13] = 0;
 				}
-				if (fat_get_entry(inode, &cpos, &bh, &de) < 0)
-					goto EODir;
+				if (fat_get_entry(inode, &cpos, &bh, &de) <0 ) {
+					if (!start_off) goto EODir;
+					reach_bottom ++; cpos = 0;
+					goto Top;
+				}
 				if (slot == 0)
 					break;
 				ds = (struct msdos_dir_slot *) de;
@@ -363,6 +461,7 @@ Found:
 	sinfo->de = de;
 	sinfo->bh = bh;
 	sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
+	hint_record(inode, sinfo, hindex);
 	err = 0;
 EODir:
 	if (unicode)
@@ -838,17 +937,32 @@ int fat_scan(struct inode *dir, const un
 	     struct fat_slot_info *sinfo)
 {
 	struct super_block *sb = dir->i_sb;
+	loff_t	start_off;
+	int	hindex;
+	int	ret;
+	int reach_bottom = 0;
+
+	ret = hint_allocate(dir); 
+	if (ret < 0) return ret;
+	hindex = hint_index(name);
 
-	sinfo->slot_off = 0;
+	sinfo->slot_off = start_off = MSDOS_I(dir)->scan_hints[hindex];
 	sinfo->bh = NULL;
-	while (fat_get_short_entry(dir, &sinfo->slot_off, &sinfo->bh,
-				   &sinfo->de) >= 0) {
+	while (1) {
+		if (fat_get_short_entry(dir, &sinfo->slot_off, 
+				        &sinfo->bh, &sinfo->de) <0 ) { 
+			if (!start_off) break;
+			sinfo->slot_off = 0LL; reach_bottom ++;
+			continue;
+		}
 		if (!strncmp(sinfo->de->name, name, MSDOS_NAME)) {
 			sinfo->slot_off -= sizeof(*sinfo->de);
 			sinfo->nr_slots = 1;
 			sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
+			hint_record(dir, sinfo, hindex);
 			return 0;
 		}
+		if (reach_bottom && (start_off <= sinfo->slot_off)) break;
 	}
 	return -ENOENT;
 }
fs/fat/inode.c +13 -0
@@ -242,6 +242,8 @@ static int fat_fill_inode(struct inode *
 	inode->i_version++;
 	inode->i_generation = get_seconds();
 
+	init_MUTEX(&MSDOS_I(inode)->scan_lock);
+	MSDOS_I(inode)->scan_hints = 0;
 	if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {
 		inode->i_generation &= ~1;
 		inode->i_mode = MSDOS_MKMODE(de->attr,
@@ -345,6 +347,15 @@ static void fat_delete_inode(struct inod
 static void fat_clear_inode(struct inode *inode)
 {
 	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+	loff_t *hints;
+
+	down(&MSDOS_I(inode)->scan_lock);
+	hints = (MSDOS_I(inode)->scan_hints);
+	if (hints) {
+		MSDOS_I(inode)->scan_hints = NULL;
+	}
+	up(&MSDOS_I(inode)->scan_lock);
+	kfree(hints);
 
 	if (is_bad_inode(inode))
 		return;
@@ -1011,6 +1022,8 @@ static int fat_read_root(struct inode *i
 	struct msdos_sb_info *sbi = MSDOS_SB(sb);
 	int error;
 
+	init_MUTEX(&MSDOS_I(inode)->scan_lock);
+	MSDOS_I(inode)->scan_hints = 0;
 	MSDOS_I(inode)->i_pos = 0;
 	inode->i_uid = sbi->options.fs_uid;
 	inode->i_gid = sbi->options.fs_gid;
include/linux/msdos_fs.h +2 -0
@@ -255,6 +255,8 @@ struct msdos_inode_info {
 	/* for avoiding the race between fat_free() and fat_get_cluster() */
 	unsigned int cache_valid_id;
 
+	struct semaphore scan_lock;	/* lock for dirscan hints */
+	loff_t *scan_hints;	/* dirscan hints */
 	loff_t mmu_private;
 	int i_start;		/* first cluster or 0 */
 	int i_logstart;		/* logical first cluster */
#8 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Hi,

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 217

Quoted 10 lines · Hiroyuki
fs/fat/dir.c +10 -0
+inline
+static int hint_allocate(struct inode *dir)
+{
+       loff_t *hints;
+       int err = 0;
+
+       if (!MSDOS_I(dir)->scan_hints) {
+               hints = kcalloc(FAT_SCAN_NWAY, sizeof(loff_t), GFP_KERNEL);
+               if (!hints)
+                       err = -ENOMEM;

Better to bail out here as...

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 221

Quoted 4 lines
fs/fat/dir.c +4 -0
+
+               down(&MSDOS_I(dir)->scan_lock);
+               if (MSDOS_I(dir)->scan_hints)
+                       err = -EINVAL;

...you might overwrite -ENOMEM here masking the real problem.

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 225

Quoted 4 lines
fs/fat/dir.c +4 -0
+               if (!err)
+                       MSDOS_I(dir)->scan_hints = hints;
+               up(&MSDOS_I(dir)->scan_lock);
+               if (err == -EINVAL) {

Gotos would make error handling much cleaner.

Review

Quoted 2 lines
(quoted diff) +2 -0
+inline
+static int hint_index_body(const unsigned char *name, int name_len, int check_null)

Please consider calling this __hint_index() instead as per normal naming conventions.

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 264

Quoted 8 lines
fs/fat/dir.c +8 -0
+{
+       int i;
+       int val = 0;
+       unsigned char *p = (unsigned char *) name;
+       int id = current->pid;
+
+       for (i=0; i<name_len; i++) {
+               if (check_null && !*p) break;

Please put break on separate line. You still have quite a few of these.

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 272

Quoted 8 lines
fs/fat/dir.c +8 -0
+               val = ((val << 1) & 0xfe) | ((val & 0x80) ? 1 : 0);
+               val ^= *p;
+               p ++;
+       }
+       id = ((id >> 8) & 0xf) ^ (id & 0xf);
+       val = (val << 1) | (id & 1);
+       return val & (FAT_SCAN_NWAY-1);
+}

Pekka

#9 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Hi,

Quoted 19 lines · Pekka Enberg

Hi,

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 217

Quoted 10 lines · Hiroyuki
fs/fat/dir.c +10 -0
+inline
+static int hint_allocate(struct inode *dir)
+{
+       loff_t *hints;
+       int err = 0;
+
+       if (!MSDOS_I(dir)->scan_hints) {
+               hints = kcalloc(FAT_SCAN_NWAY, sizeof(loff_t), GFP_KERNEL);
+               if (!hints)
+                       err = -ENOMEM;

Better to bail out here as...

Quoted 8 lines

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 221

Quoted 4 lines
fs/fat/dir.c +4 -0
+
+               down(&MSDOS_I(dir)->scan_lock);
+               if (MSDOS_I(dir)->scan_hints)
+                       err = -EINVAL;

...you might overwrite -ENOMEM here masking the real problem.

It's ok. If MSDOS_I(dir)->scan_hints isn't NULL, it's not error case. We need just kfree(hints) and return 0.(Assuming kfree() accepts NULL) I think EINVAL confuse you.

Quoted 9 lines

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 225

Quoted 5 lines
fs/fat/dir.c +4 -0
+               if (!err)
+                       MSDOS_I(dir)->scan_hints = hints;
+               up(&MSDOS_I(dir)->scan_lock);
+               if (err == -EINVAL) {

Gotos would make error handling much cleaner.

How about this ?

	if (!MSDOS_I(dir)->scan_hints) {
		hints  = kcalllo(....);

		down
		if (MSDOS_I(dir)->scan_hints) {
			up
			goto already_allocated;
		}
		if (hints)
			MSDOS_I(dir)->scan_hints = hints;
		up
	}
	return (hints == 0) ? -ENOMEM : 0;

already_allocated:

	kfree(hints); /* kfree accepts NULL */
	return 0;
Quoted 7 lines

Review

Quoted 3 lines
(quoted diff) +2 -0
+inline
+static int hint_index_body(const unsigned char *name, int name_len, int check_null)

Please consider calling this __hint_index() instead as per normal naming conventions.

Agree.

Quoted 12 lines

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 264

Quoted 9 lines
fs/fat/dir.c +8 -0
+{
+       int i;
+       int val = 0;
+       unsigned char *p = (unsigned char *) name;
+       int id = current->pid;
+
+       for (i=0; i<name_len; i++) {
+               if (check_null && !*p) break;

Please put break on separate line. You still have quite a few of these.

Agree

Quoted 13 lines

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 272

Quoted 10 lines
fs/fat/dir.c +8 -0
+               val = ((val << 1) & 0xfe) | ((val & 0x80) ? 1 : 0);
+               val ^= *p;
+               p ++;
+       }
+       id = ((id >> 8) & 0xf) ^ (id & 0xf);
+       val = (val << 1) | (id & 1);
+       return val & (FAT_SCAN_NWAY-1);
+}

Pekka

Signature · 2 lines
Hiroyuki Machida		[email protected]		
SSW Dept. HENC, Sony Corp.
#10 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Quoted 16 lines · Hiroyuki

How about this ?

        if (!MSDOS_I(dir)->scan_hints) {
                hints  = kcalllo(....);

                down
                if (MSDOS_I(dir)->scan_hints) {
                        up
                        goto already_allocated;
                }
                if (hints)
                        MSDOS_I(dir)->scan_hints = hints;
                up
        }
        return (hints == 0) ? -ENOMEM : 0;
Quoted 3 lines

already_allocated:
kfree(hints); /* kfree accepts NULL */
return 0;

After finally understanding what you're doing, how about:

static inline int hint_allocate(struct inode *dir)

{
        loff_t *hints;
        int err = 0;

        if (!MSDOS_I(dir)->scan_hints)
                return 0;

        hints = kcalloc(FAT_SCAN_NWAY, sizeof(loff_t), GFP_KERNEL);
        if (!hints)
                err = -ENOMEM;

        down(&MSDOS_I(dir)->scan_lock);
        /*
         * We allocated memory without scan_lock so lets make sure
         * no other thread completed hint_allocate() before us.
         */
        if (!MSDOS_I(dir)->scan_hints) {
                MSDOS_I(dir)->scan_hints = hints;
                hints = NULL;
        }
        up(&MSDOS_I(dir)->scan_lock);

        kfree(hints);
        return err;
}

Pekka

#11 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Quoted 8 lines · Pekka Enberg

After finally understanding what you're doing, how about:

static inline int hint_allocate(struct inode *dir)

{
        loff_t *hints;
        int err = 0;

        if (!MSDOS_I(dir)->scan_hints)

Should read:

if (MSDOS_I(dir)->scan_hints)

Quoted 23 lines
                return 0;

        hints = kcalloc(FAT_SCAN_NWAY, sizeof(loff_t), GFP_KERNEL);
        if (!hints)
                err = -ENOMEM;

        down(&MSDOS_I(dir)->scan_lock);
        /*
         * We allocated memory without scan_lock so lets make sure
         * no other thread completed hint_allocate() before us.
         */
        if (!MSDOS_I(dir)->scan_hints) {
                MSDOS_I(dir)->scan_hints = hints;
                hints = NULL;
        }
        up(&MSDOS_I(dir)->scan_lock);

        kfree(hints);
        return err;
}

Pekka

#12 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

In reply to Machida, Hiroyuki
Quoted 7 lines · Hiroyuki
fs/fat/dir.c +7 -0
+inline
+static int hint_index_body(const unsigned char *name, int name_len, int check_null)
+{
+       int i;
+       int val = 0;
+       unsigned char *p = (unsigned char *) name;
+       int id = current->pid;

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 271

Quoted 10 lines
fs/fat/dir.c +10 -0
+
+       for (i=0; i<name_len; i++) {
+               if (check_null && !*p) break;
+               val = ((val << 1) & 0xfe) | ((val & 0x80) ? 1 : 0);
+               val ^= *p;
+               p ++;
+       }
+       id = ((id >> 8) & 0xf) ^ (id & 0xf);
+       val = (val << 1) | (id & 1);
+       return val & (FAT_SCAN_NWAY-1);

Couldn't you use jhash() from <linux/jhash.h> here?

Pekka

#13 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Quoted 23 lines · Pekka Enberg
Quoted 7 lines · Hiroyuki
fs/fat/dir.c +7 -0
+inline
+static int hint_index_body(const unsigned char *name, int name_len, int check_null)
+{
+       int i;
+       int val = 0;
+       unsigned char *p = (unsigned char *) name;
+       int id = current->pid;

fs/fat/dir.c fat_shortname2uni(struct nls_table *nls, · line 271

Quoted 10 lines
fs/fat/dir.c +10 -0
+
+       for (i=0; i<name_len; i++) {
+               if (check_null && !*p) break;
+               val = ((val << 1) & 0xfe) | ((val & 0x80) ? 1 : 0);
+               val ^= *p;
+               p ++;
+       }
+       id = ((id >> 8) & 0xf) ^ (id & 0xf);
+       val = (val << 1) | (id & 1);
+       return val & (FAT_SCAN_NWAY-1);

Couldn't you use jhash() from <linux/jhash.h> here?

Quoted 1 line

Pekka

Thanks, I'll replace it with functions in jhash.h, then check performance again.

Signature · 1 line
Hiroyuki Machida		[email protected]		
#14 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

In reply to Machida, Hiroyuki

Hi,

Quoted 113 lines · Hiroyuki

Sorry, I send out wrong version. I attached the latest patch to 2.6.13.

Quoted 16 lines · OGAWA Hirofumi
Quoted 11 lines · Hiroyuki

Here is a revised version of dirent scan patch, mentioned at following E-mail.

This patch addresses performance damages on "ls | xargs xxx" and reverse order scan which are reported to the previous patch.

With this patch, fat_search_long() and fat_scan() use hint value as start of scan. For each directory holds multiple hint value entries. The entry would be selected by hash value based on scan target name and PID. Hint value would be calculated based on the entry previously found entry, so that the hint can cover backward neighborhood.

Quoted 7 lines

This patch couldn't compile. I assume you post a wrong patch...?

The code is strange... Is the hint value related to the previously accessed entry?

This seems to be randomly cacheing the recent access position... Is it your intention of this patch?

Right, it looks like TLB, which holds cache "Physical addres" correponding to "Logical address". In this case, PID and file name to be looked up, perform role of "Logical address".

I prepared vfat16 fs where over 4000 files in /foo and 200 files in root, then checked with following cases;

                                without patch   with patch
ls-vfat                         59.0            2.49
xargs-vfat                      61.3            11.9
stat-vfat                       41              17
stat-vfat-reverse               56              32
ls-msdos                        14.2            0.62
xargs-msdos                     16.8            16.7
stat-msdos                      21              15
stat-msdos-reverse              22              16

- all valuses have sec unit

1-1 ls-vat)
mount vfat to /a
/usr/bin/time -p sh -c "/usr/bin/ls -Rl /a > /dev/null" umount /a

1-2 ls-msdos)
mount msdos to /a
/usr/bin/time -p sh -c "/usr/bin/ls -Rl /a > /dev/null" umount /a

2-1 xargs-vfat)
mount vfat to /a
cd /a/foo
/usr/bin/time -p sh -c "(/usr/bin/ls | /usr/in/xargs stat) > /dev/null" umount /a

2-2 xargs-msdos)
mount msdos to /a
cd /a/foo
/usr/bin/time -p sh -c "(/usr/bin/ls | /usr/in/xargs stat) > /dev/null" umount /a

3-1 stat-vfat)
mount vfat to /a
cd /a/foo
repeat stat files which have located in the last 1024 dir entries
with incremental order.
umount /a

3-2 stat-vfat-reverse)
do 3-1 with decremental order

3-3 stat-msdos)
do 3-1 with msdos fs

3-4 stat-msdos-reverse)
do 3-2 with msdos fs

--
Hiroyuki Machida

This patch enables using hint information on scanning dir. It achieves excellent performance with "ls -l" for over 1000 entries.

* fat-dirscan-with-hint_3.patch for linux 2.6.13

 fs/fat/dir.c             |  130 ++++++++++++++++++++++++++++++++++++++++++++---
 fs/fat/inode.c           |   13 ++++
 include/linux/msdos_fs.h |    2
 3 files changed, 137 insertions(+), 8 deletions(-)

* modified files

fs/fat/dir.c 2005-08-31 13:48:01.001119437 +0900 renamed from fs/fat/dir.c 2005-08-29 08:41:01.000000000 +0900 +1 -0
@@ -201,6 +201,88 @@ fat_shortname2uni(struct nls_table *nls,
  * Return values: negative -> error, 0 -> not found, positive -> found,
  * value is the total amount of slots, including the shortname entry.
  */
+

fs/fat/dir.c 2005-08-31 13:48:01.001119437 +0900 fat_shortname2uni(struct nls_table *nls, · line 214

Quoted 10 lines
fs/fat/dir.c 2005-08-31 13:48:01.001119437 +0900 renamed from fs/fat/dir.c 2005-08-29 08:41:01.000000000 +0900 +10 -0
+#define FAT_SCAN_SHIFT 4                       /* 2x8 way scan hints  */
+#define FAT_SCAN_NWAY  (1<<FAT_SCAN_SHIFT)
+
+inline
+static int hint_allocate(struct inode *dir)
+{
+       loff_t *hints;
+       int err = 0;
+
+       if (!MSDOS_I(dir)->scan_hints) {

Please consider moving this check to callers. Conditional allocation makes this bit strange API-wise. Or alternatively, give hint_allocate() a better name.

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static int fat_fill_inode(struct inode * · line 246

Quoted 8 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +2 -0
@@ -242,6 +242,8 @@ static int fat_fill_inode(struct inode *
        inode->i_version++;
        inode->i_generation = get_seconds();
 
+       init_MUTEX(&MSDOS_I(inode)->scan_lock);
+       MSDOS_I(inode)->scan_hints = 0;

Please use NULL.

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static void fat_delete_inode(struct inod · line 353

Quoted 11 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +4 -0
        if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {
                inode->i_generation &= ~1;
                inode->i_mode = MSDOS_MKMODE(de->attr,
@@ -345,6 +347,15 @@ static void fat_delete_inode(struct inod
 static void fat_clear_inode(struct inode *inode)
 {
        struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+       loff_t *hints;
+
+       down(&MSDOS_I(inode)->scan_lock);
+       hints = (MSDOS_I(inode)->scan_hints);

Pleas drop redundant parenthesis.

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static void fat_delete_inode(struct inod · line 358

Quoted 5 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +5 -0
+       if (hints) {
+               MSDOS_I(inode)->scan_hints = NULL;
+       }
+       up(&MSDOS_I(inode)->scan_lock);
+       kfree(hints);

Why can't you do kfree() under scan_lock?

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static int fat_read_root(struct inode *i · line 1026

Quoted 6 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +2 -0
@@ -1011,6 +1022,8 @@ static int fat_read_root(struct inode *i
        struct msdos_sb_info *sbi = MSDOS_SB(sb);
        int error;
 
+       init_MUTEX(&MSDOS_I(inode)->scan_lock);
+       MSDOS_I(inode)->scan_hints = 0;

Please use NULL here.

Pekka

#15 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Hi,

Thank you checking code...

Quoted 2 lines · Pekka Enberg

Hi,

	:
	snip
Quoted 30 lines
Quoted 30 lines

This patch enables using hint information on scanning dir. It achieves excellent performance with "ls -l" for over 1000 entries.

* fat-dirscan-with-hint_3.patch for linux 2.6.13

fs/fat/dir.c             |  130 ++++++++++++++++++++++++++++++++++++++++++++---
fs/fat/inode.c           |   13 ++++
include/linux/msdos_fs.h |    2
3 files changed, 137 insertions(+), 8 deletions(-)

* modified files

fs/fat/dir.c 2005-08-31 13:48:01.001119437 +0900 renamed from fs/fat/dir.c 2005-08-29 08:41:01.000000000 +0900 +8 -0
@@ -201,6 +201,88 @@ fat_shortname2uni(struct nls_table *nls,
 * Return values: negative -> error, 0 -> not found, positive -> found,
 * value is the total amount of slots, including the shortname entry.
 */
+
+#define FAT_SCAN_SHIFT 4                       /* 2x8 way scan hints  */
+#define FAT_SCAN_NWAY  (1<<FAT_SCAN_SHIFT)
+
+inline
+static int hint_allocate(struct inode *dir)
+{
+       loff_t *hints;
Quoted 8 lines

fs/fat/dir.c 2005-08-31 13:48:01.001119437 +0900 fat_shortname2uni(struct nls_table *nls, · line 214

Quoted 3 lines
fs/fat/dir.c 2005-08-31 13:48:01.001119437 +0900 renamed from fs/fat/dir.c 2005-08-29 08:41:01.000000000 +0900 +3 -0
+       int err = 0;
+
+       if (!MSDOS_I(dir)->scan_hints) {

Please consider moving this check to callers. Conditional allocation makes this bit strange API-wise. Or alternatively, give hint_allocate() a better name.

How about hint_allocate_conditional() ?

Quoted 7 lines
Quoted 7 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +0 -0
@@ -242,6 +242,8 @@ static int fat_fill_inode(struct inode *
       inode->i_version++;
       inode->i_generation = get_seconds();
 
Quoted 5 lines

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static int fat_fill_inode(struct inode * · line 246

Quoted 2 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +2 -0
+       init_MUTEX(&MSDOS_I(inode)->scan_lock);
+       MSDOS_I(inode)->scan_hints = 0;

Please use NULL.

Ok,

Quoted 15 lines

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static void fat_delete_inode(struct inod · line 353

Quoted 12 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +4 -0
       if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {
               inode->i_generation &= ~1;
               inode->i_mode = MSDOS_MKMODE(de->attr,
@@ -345,6 +347,15 @@ static void fat_delete_inode(struct inod
 static void fat_clear_inode(struct inode *inode)
 {
       struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+       loff_t *hints;
+
+       down(&MSDOS_I(inode)->scan_lock);
+       hints = (MSDOS_I(inode)->scan_hints);

Pleas drop redundant parenthesis.

Ok,

Quoted 10 lines

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static void fat_delete_inode(struct inod · line 358

Quoted 6 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +5 -0
+       if (hints) {
+               MSDOS_I(inode)->scan_hints = NULL;
+       }
+       up(&MSDOS_I(inode)->scan_lock);
+       kfree(hints);

Why can't you do kfree() under scan_lock?

Just try to minimize blocked period.

Quoted 10 lines

fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 static int fat_read_root(struct inode *i · line 1026

Quoted 7 lines
fs/fat/inode.c 2005-08-31 12:59:54.292274060 +0900 renamed from fs/fat/inode.c 2005-08-29 08:41:01.000000000 +0900 +2 -0
@@ -1011,6 +1022,8 @@ static int fat_read_root(struct inode *i
       struct msdos_sb_info *sbi = MSDOS_SB(sb);
       int error;
 
+       init_MUTEX(&MSDOS_I(inode)->scan_lock);
+       MSDOS_I(inode)->scan_hints = 0;

Please use NULL here.

Ok.

Quoted 6 lines

Pekka
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

Signature · 2 lines
Hiroyuki Machida		[email protected]		
SSW Dept. HENC, Sony Corp.
#16 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Quoted 5 lines · Hiroyuki
Quoted 3 lines

Please consider moving this check to callers. Conditional allocation makes this bit strange API-wise. Or alternatively, give hint_allocate() a better name.

How about hint_allocate_conditional() ?

hint_get() sounds better to me.

Pekka

maintainer
#17 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

In reply to Machida, Hiroyuki
Quoted 3 lines · Hiroyuki

Right, it looks like TLB, which holds cache "Physical addres" correponding to "Logical address". In this case, PID and file name to be looked up, perform role of "Logical address".

But, there is the big difference between hint table and TLB. TLB is just the cache, and TLB hit is perfectly good, because kernel is flushing the wrong values.

But this hint table is just collecting the recent access, it's not cache, and it's not tracking the process's access at all. So, since the hint value is really random, the hint value may be bad.

I worry bad cases of this.

Umm... How about tracking the access pattern of process? If that seems randomly access, just give up tracking and return no hint. And, probably, I think it would be easy to improve the behavior later.

What do you think?

e.g.

#define FAT_LOOKUP_HINT_MAX 16

/* this data per task */

struct fat_lookup_hint {
	struct list_head lru;
	pid_t pid;
	struct super_block *sb;
	struct inode *dir;
	loff_t last_pos;

/* int state;*/
};

static void fat_lkup_hint_inval(struct super_block *, struct inode *); static loff_t fat_lkup_hint_get(struct super_block *, struct inode *); static void fat_lkup_hint_add(struct super_block *, struct inode *, loff_t); static int fat_lkup_hint_init(void);

Signature · 1 line
OGAWA Hirofumi <[email protected]>
#18 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Quoted 16 lines · OGAWA Hirofumi
Quoted 3 lines · Hiroyuki

Right, it looks like TLB, which holds cache "Physical addres" correponding to "Logical address". In this case, PID and file name to be looked up, perform role of "Logical address".

But, there is the big difference between hint table and TLB. TLB is just the cache, and TLB hit is perfectly good, because kernel is flushing the wrong values.

But this hint table is just collecting the recent access, it's not cache, and it's not tracking the process's access at all. So, since the hint value is really random, the hint value may be bad.

Quoted 8 lines

I worry bad cases of this.

Umm... How about tracking the access pattern of process? If that seems randomly access, just give up tracking and return no hint. And, probably, I think it would be easy to improve the behavior later.

What do you think?

Sounds interesting...

Once concern about global URL in general, it tends to be occupied by specific pattern, like accesses from one process or to on dir. It prevents to realize locality.

I think it's better to have limitations like;
entries for same process would be limited to 2/3 entries for same dir would be limited to 1/3

Quoted 5 lines

e.g.

#define FAT_LOOKUP_HINT_MAX 16

/* this data per task */

Quoted 8 lines
struct fat_lookup_hint {
	struct list_head lru;
	pid_t pid;
	struct super_block *sb;
	struct inode *dir;
	loff_t last_pos;

/* int state;*/
};

Does this mean for each process recording last recent 16 accesses to FAT file system ? If true, pid would be eliminated.

I guess it's better to record nr_slots for this entry.

As implementation issue, if number of entires is small enough, we can use an array, not a list.

Quoted 4 lines

static void fat_lkup_hint_inval(struct super_block *, struct inode *); static loff_t fat_lkup_hint_get(struct super_block *, struct inode *); static void fat_lkup_hint_add(struct super_block *, struct inode *, loff_t); static int fat_lkup_hint_init(void);

I think super_block can be retrieved from inode, any other intention do you have?

In addtion, we can do follwoing to check the exact match case;

0. Record hash value of file name in struct fat_lookup_hint

1. Check hash value to find exact match case, 1-1. If matched entry is found, check if file name and file name retieved from dirent corresponding
1-2. We found the entry

2. Get hint value, if there seem to have locality 2-1. Check locality of access pattern for this PID and this DIR.
2-2. If we relize access locality, return hit value so that
it covers a potential working set.
2-3. Use hint value as start position of dirscan.

Signature · 1 line
Hiroyuki Machida
#19 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

Quoted 26 lines · Hiroyuki
Quoted 24 lines · OGAWA Hirofumi
Quoted 3 lines · Hiroyuki

Right, it looks like TLB, which holds cache "Physical addres" correponding to "Logical address". In this case, PID and file name to be looked up, perform role of "Logical address".

But, there is the big difference between hint table and TLB. TLB is just the cache, and TLB hit is perfectly good, because kernel is flushing the wrong values.

But this hint table is just collecting the recent access, it's not cache, and it's not tracking the process's access at all. So, since the hint value is really random, the hint value may be bad.

I worry bad cases of this.

Umm... How about tracking the access pattern of process? If that seems randomly access, just give up tracking and return no hint. And, probably, I think it would be easy to improve the behavior later.

Quoted 6 lines
Quoted 1 line

What do you think?

Sounds interesting...

Once concern about global URL in general, it tends to be occupied

                            ^^^

sorry, LRU not URL.

Quoted 1 line

by specific pattern, like accesses from one process or to on dir.

one dir.

Quoted 57 lines

It prevents to realize locality.

I think it's better to have limitations like;
entries for same process would be limited to 2/3 entries for same dir would be limited to 1/3

Quoted 5 lines

e.g.

#define FAT_LOOKUP_HINT_MAX 16

/* this data per task */

Quoted 8 lines
struct fat_lookup_hint {
    struct list_head lru;
    pid_t pid;
    struct super_block *sb;
    struct inode *dir;
    loff_t last_pos;

/* int state;*/
};

Does this mean for each process recording last recent 16 accesses to FAT file system ? If true, pid would be eliminated.

I guess it's better to record nr_slots for this entry.

As implementation issue, if number of entires is small enough, we can use an array, not a list.

Quoted 4 lines

static void fat_lkup_hint_inval(struct super_block *, struct inode *); static loff_t fat_lkup_hint_get(struct super_block *, struct inode *); static void fat_lkup_hint_add(struct super_block *, struct inode *, loff_t); static int fat_lkup_hint_init(void);

I think super_block can be retrieved from inode, any other intention do you have?

In addtion, we can do follwoing to check the exact match case;

0. Record hash value of file name in struct fat_lookup_hint

1. Check hash value to find exact match case, 1-1. If matched entry is found, check if file name and file name retieved from dirent corresponding
1-2. We found the entry

2. Get hint value, if there seem to have locality 2-1. Check locality of access pattern for this PID and this DIR.
2-2. If we relize access locality, return hit value so that
it covers a potential working set.
2-3. Use hint value as start position of dirscan.

Signature · 1 line
Hiroyuki Machida
maintainer
#20 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

In reply to Machida, Hiroyuki
Quoted 7 lines · Hiroyuki

Once concern about global URL in general, it tends to be occupied by specific pattern, like accesses from one process or to on dir. It prevents to realize locality.

I think it's better to have limitations like;
entries for same process would be limited to 2/3 entries for same dir would be limited to 1/3

[...]

Quoted 4 lines

Does this mean for each process recording last recent 16 accesses to FAT file system ? If true, pid would be eliminated.

I guess it's better to record nr_slots for this entry.

The fat_lookup_hint is one per task. And fat_lookup_hint is tracking the task's access. If access is sequential, it returns the hint, and if not, it returns 0. I made the dirty patch just for explaining what I said.

However, these lookup-hint approaches seems the hack after all.... Hhmmm...

Signature · 1 line
OGAWA Hirofumi <[email protected]>

---

 fs/fat/dir.c   |  166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 fs/fat/inode.c |    2 
 2 files changed, 164 insertions(+), 4 deletions(-)
fs/fat/dir.c renamed from fs/fat/dir.c~fat_lookup-hint +162 -4
diff -puN fs/fat/dir.c~fat_lookup-hint fs/fat/dir.c
@@ -22,6 +22,148 @@
 #include <linux/buffer_head.h>
 #include <asm/uaccess.h>
 
+#define FAT_LOOKUP_HINT_MAX	16
+
+struct fat_lookup_hint {
+	struct list_head lru_list;
+	pid_t pid;
+	struct super_block *sb;
+	struct inode *dir;
+	loff_t last_pos;
+#define HINT_STATE_RANDOM	1
+	int state;
+};
+
+static spinlock_t fat_lookup_hint_lock = SPIN_LOCK_UNLOCKED;
+static LIST_HEAD(fat_lookup_hint_head);
+static int fat_lookup_hint_count;
+
+#if 0
+static int fat_lkup_hint_init(void)
+{
+	/* replace with kmem_cache */
+	return 0;
+}
+#endif
+
+static inline struct fat_lookup_hint *fat_lkup_hint_alloc(void)
+{
+	struct fat_lookup_hint *hint;
+
+	/* replace with kmem_cache */
+	hint = kmalloc(sizeof(*hint), GFP_KERNEL);
+	if (hint) {
+		INIT_LIST_HEAD(&hint->lru_list);
+		hint->pid = 0;
+		hint->sb = NULL;
+		hint->dir = NULL;
+		hint->last_pos = 0;
+	}
+	return hint;
+}
+
+static inline void fat_lkup_hint_free(struct fat_lookup_hint *hint)
+{
+	/* replace with kmem_cache */
+	BUG_ON(!list_empty(&hint->lru_list));
+	kfree(hint);
+}
+
+void fat_lkup_hint_inval(struct inode *dir)
+{
+	struct super_block *sb = dir->i_sb;
+	struct fat_lookup_hint *hint, *n;
+
+	spin_lock(&fat_lookup_hint_lock);
+	list_for_each_entry_safe(hint, n, &fat_lookup_hint_head, lru_list) {
+		if (hint->sb == sb && hint->dir == dir) {
+			list_del_init(&hint->lru_list);
+			fat_lkup_hint_free(hint);
+			fat_lookup_hint_count--;
+		}
+	}
+	spin_unlock(&fat_lookup_hint_lock);
+}
+
+#define WIN_TOP		(2 * MSDOS_SLOTS * sizeof(struct msdos_dir_entry))
+#define WIN_BOTTOM	(5 * MSDOS_SLOTS * sizeof(struct msdos_dir_entry))
+static loff_t fat_lkup_hint_get(struct inode *dir)
+{
+	struct super_block *sb = dir->i_sb;
+	struct fat_lookup_hint *hint;
+	loff_t pos = 0;
+
+	spin_lock(&fat_lookup_hint_lock);
+	list_for_each_entry(hint, &fat_lookup_hint_head, lru_list) {
+		if (hint->pid == current->pid &&
+		    hint->sb == sb &&
+		    hint->dir == dir) {
+			if (!(hint->state & HINT_STATE_RANDOM))
+				pos = hint->last_pos;
+			break;
+		}
+	}
+	spin_unlock(&fat_lookup_hint_lock);
+	return max(0LL, pos - WIN_TOP);
+}
+
+/* caller must hold dir->i_sem */
+static void fat_lkup_hint_add(struct inode *dir, loff_t pos)
+{
+	struct super_block *sb = dir->i_sb;
+	struct fat_lookup_hint *p, *hint = NULL;
+	loff_t win_start, win_end;
+
+	spin_lock(&fat_lookup_hint_lock);
+	list_for_each_entry(p, &fat_lookup_hint_head, lru_list) {
+		if (p->pid == current->pid) {
+			hint = p;
+			break;
+		}
+	}
+	if (hint == NULL) {
+		if (fat_lookup_hint_count < FAT_LOOKUP_HINT_MAX) {
+			fat_lookup_hint_count++;
+			spin_unlock(&fat_lookup_hint_lock);
+
+			hint = fat_lkup_hint_alloc();
+			if (hint == NULL)
+				return;
+			spin_lock(&fat_lookup_hint_lock);
+			/* don't need to recheck hint */
+		} else {
+			struct list_head *p = fat_lookup_hint_head.prev;
+			hint = list_entry(p, struct fat_lookup_hint, lru_list);
+		}
+		hint->pid = current->pid;
+		hint->sb = sb;
+		hint->dir = dir;
+	}
+
+	if (hint->sb != sb || hint->dir != dir) {
+		hint->sb = sb;
+		hint->dir = dir;
+		hint->state |= HINT_STATE_RANDOM;
+	} else {
+		win_start = hint->last_pos - WIN_TOP;
+		win_end = hint->last_pos + WIN_BOTTOM;
+		if (win_start <= pos && pos <= win_end) {
+			hint->state &= ~HINT_STATE_RANDOM;
+			hint->last_pos = pos;
+		} else {
+			/* seems random access */
+			hint->last_pos = pos;
+			hint->state |= HINT_STATE_RANDOM;
+		}
+	}
+	hint->last_pos = pos;
+
+	/* update LRU */
+	list_del(&hint->lru_list);
+	list_add(&hint->lru_list, &fat_lookup_hint_head);
+	spin_unlock(&fat_lookup_hint_lock);
+}
+
 static inline loff_t fat_make_i_pos(struct super_block *sb,
 				    struct buffer_head *bh,
 				    struct msdos_dir_entry *de)
@@ -243,13 +385,23 @@ int fat_search_long(struct inode *inode,
 	int utf8 = sbi->options.utf8;
 	int anycase = (sbi->options.name_check != 's');
 	unsigned short opt_shortname = sbi->options.shortname;
-	loff_t cpos = 0;
 	int chl, i, j, last_u, err;
+	loff_t cpos, start_off;
+	int reach_bottom = 0;
 
+	start_off = cpos = fat_lkup_hint_get(inode);
 	err = -ENOENT;
 	while(1) {
-		if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
+top:
+		if (reach_bottom && cpos >= start_off)
 			goto EODir;
+		if (fat_get_entry(inode, &cpos, &bh, &de) == -1) {
+			if (!start_off)
+				goto EODir;
+			reach_bottom++;
+			cpos = 0;
+			continue;
+		}
 parse_record:
 		nr_slots = 0;
 		if (de->name[0] == DELETED_FLAG)
@@ -298,8 +450,13 @@ parse_long:
 				if (ds->id & 0x40) {
 					unicode[offset + 13] = 0;
 				}
-				if (fat_get_entry(inode, &cpos, &bh, &de) < 0)
-					goto EODir;
+				if (fat_get_entry(inode, &cpos, &bh, &de) < 0) {
+					if (!start_off)
+						goto EODir;
+					reach_bottom++;
+					cpos = 0;
+					goto top;
+				}
 				if (slot == 0)
 					break;
 				ds = (struct msdos_dir_slot *) de;
@@ -385,6 +542,7 @@ Found:
 	sinfo->de = de;
 	sinfo->bh = bh;
 	sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
+	fat_lkup_hint_add(inode, sinfo->slot_off);
 	err = 0;
 EODir:
 	if (unicode)
fs/fat/inode.c renamed from fs/fat/inode.c~fat_lookup-hint +2 -0
diff -puN fs/fat/inode.c~fat_lookup-hint fs/fat/inode.c
@@ -342,6 +342,7 @@ static void fat_delete_inode(struct inod
 	clear_inode(inode);
 }
 
+extern void fat_lkup_hint_inval(struct inode*);
 static void fat_clear_inode(struct inode *inode)
 {
 	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
@@ -349,6 +350,7 @@ static void fat_clear_inode(struct inode
 	if (is_bad_inode(inode))
 		return;
 	lock_kernel();
+	fat_lkup_hint_inval(inode);
 	spin_lock(&sbi->inode_hash_lock);
 	fat_cache_inval_inode(inode);
 	hlist_del_init(&MSDOS_I(inode)->i_fat_hash);

_

#21 lore raw

Re: [PATCH][FAT] FAT dirent scan with hin take #3

OGAWA Hirofumi wrote:

	:
Quoted 6 lines

The fat_lookup_hint is one per task. And fat_lookup_hint is tracking the task's access. If access is sequential, it returns the hint, and if not, it returns 0. I made the dirty patch just for explaining what I said.

Sorry late, at the last weekend I looked into the patch you post and run it. I got your idea, now. I would propose following changes on it and attached modified patch into this mail.

1. The original patch has system wide 16 entries access-tracking buffer. I modified system has one access-tracking buffer for each mounted FS instance. And it allocates the buffer on mounting as a part of FS dependent super block.

2. On looking up hints, the original patch try to find the entry which has same pid and dir. If matched entry is not found, it uses always zero. However we can expect locality beyond process, so I changes a little bit, behavior of fail to find the matched entry.

try to find the entry which has same pid and dir.
if matched entry is not found, try to find recent access to same dir.

3. With the original code, the access-tracking buffer can hold just one entry for one pid. I think it is better to have few entries for one pid. I modified patch to have up to 3 entires (less than 1/4 of total entries) for each pid.

4. Originally window size to determine locality seems wide due to using max len of LFN(MSDOS_SLOTS), like;
WIN_BOTTOM = 5 * MSDOS_SLOTS * size of entry
We can use the value based on statistic instead of MSDOS_SLOTS. I checked with my FC3 installed system, most files(over 99%) have less than or equal to 48 chars file name length. So use 9 instead of 21(MSDOS_SLOTS)

5. Adding short name entry support.

And I have fixed a bug which makes infinite loop on lookup empty dir after all files were removed.

Quoted 2 lines

However, these lookup-hint approaches seems the hack after all.... Hhmmm...

I have another idea as following;

On reading blocks ahead, try to build dentry caches for all FAT dir entries which reside in those blocks. And start dir-scan at some position close to bottom of the latest read-ahead block, instead of zero.

However, I guess this tends to consume dentry caches and it may have affects on other area.

As you said, I think "lookup-hint" is hack, but not so bad. Because it has no side effect to other area and memory consumption is predictable.

Signature · 1 line
Hiroyuki Machida

---

 fs/fat/dir.c             |  232 ++++++++++++++++++++++++++++++++++++++++++++---
 fs/fat/inode.c           |    7 +
 include/linux/msdos_fs.h |   19 +++
 3 files changed, 246 insertions(+), 12 deletions(-)
fs/fat/dir.c +220 -12
diff -r -up linux-2.6.13/fs/fat/dir.c linux-2.6.13-scan2/fs/fat/dir.c
@@ -22,6 +22,185 @@
 #include <linux/buffer_head.h>
 #include <asm/uaccess.h>
 
+void fat_lkup_hint_init_sb(struct super_block *sb)
+{
+	struct msdos_sb_info *sbi = MSDOS_SB(sb);
+
+	sbi->lkup_hint_lock = SPIN_LOCK_UNLOCKED;
+	INIT_LIST_HEAD(&sbi->lkup_hint_head);
+	sbi->lkup_hint_freemap = 0;
+	sbi->lkup_hint_freemap = ~sbi->lkup_hint_freemap;
+}
+
+void fat_lkup_hint_inval(struct inode *dir)
+{
+	struct super_block *sb = dir->i_sb;
+	struct msdos_sb_info *sbi = MSDOS_SB(sb);
+	struct fat_lookup_hint *hint, *n;
+	int i;
+
+	spin_lock(&sbi->lkup_hint_lock);
+	list_for_each_entry_safe(hint, n, &sbi->lkup_hint_head, lru_list) {
+		if (hint->dir == dir) {
+			list_del_init(&hint->lru_list);
+			i = hint - &(sbi->lkup_hint[0]);
+			sbi->lkup_hint_freemap |= (1 << i);
+		}
+	}
+	spin_unlock(&sbi->lkup_hint_lock);
+}
+
+/* 
+ * AVG_MSDOS_SLOTS is selected so that it can store 48 chars file name length.
+ *
+ * According raugh observation, most files in Linux installed system have 
+ * less than or equal to 48 chars file name length. 
+ *
+ * Following is one of statistic information;
+ *
+ *	total # of files(*1) 			9193
+ *	max length of file name			52 chars
+ *
+ *	# of files over 48 chars name length	36
+ *	  % covered by 48 chars name length	 99.6%
+ *
+ *	# of files over 32 chars name length	517
+ *	  % covered by 32 chars name length	 94.3%
+ *
+ *	*1) # of file, dir and symlink which has over 2 chars 
+ *	file name length.
+ * 
+ */
+#define AVG_MSDOS_SLOTS	(8+1)
+#define WIN_LFN_TOP	(3 * AVG_MSDOS_SLOTS * sizeof(struct msdos_dir_entry))
+#define WIN_LFN_BOTTOM	(5 * AVG_MSDOS_SLOTS * sizeof(struct msdos_dir_entry))
+
+#define WIN_TOP		(3 * sizeof(struct msdos_dir_entry))
+#define WIN_BOTTOM	(5 * sizeof(struct msdos_dir_entry))
+
+static inline
+loff_t get_syswide_hint(loff_t pos, struct fat_lookup_hint *hint)
+{
+	/* 
+	 * NOTE: If you don't expect locality beyond process, 
+	 * make this return 0 always, instead of following.
+	 */
+	return  (pos == 0) ? hint->last_pos : pos;
+}
+
+static loff_t fat_lkup_hint_get(struct inode *dir, int is_lfn)
+{
+	struct super_block *sb = dir->i_sb;
+	struct fat_lookup_hint *hint;
+	struct msdos_sb_info *sbi = MSDOS_SB(sb);
+	loff_t win_top;
+	unsigned int rnd_flg;
+	loff_t pos = 0;
+
+	if (is_lfn) {
+		rnd_flg = FAT_LKUP_ST_LFN_RANDOM;
+		win_top = WIN_LFN_TOP;
+	} else {
+		rnd_flg = FAT_LKUP_ST_RANDOM;
+		win_top = WIN_TOP;
+	}
+
+	spin_lock(&sbi->lkup_hint_lock);
+	list_for_each_entry(hint, &sbi->lkup_hint_head, lru_list) {
+		if (hint->dir == dir) {
+			if (!(hint->state & rnd_flg)) {
+				if (hint->pid == current->pid)  {
+					pos = hint->last_pos;
+					break;
+				}
+				/* expect locality beyond process */
+				pos = get_syswide_hint(pos, hint);
+			}
+		}
+	}
+	spin_unlock(&sbi->lkup_hint_lock);
+	return max(0LL, pos - win_top);
+}
+
+/* 
+ * NOTE: If you don't want multiple entries for 
+ * same pid, set this to 1.
+ */
+#define LKUP_MULTIENT_MAX	(FAT_LKUP_HINT_MAX/4-1)
+
+
+/* caller must hold dir->i_sem */
+static void fat_lkup_hint_add(struct inode *dir, loff_t pos)
+{
+	struct super_block *sb = dir->i_sb;
+	struct fat_lookup_hint *p, *hint = NULL;
+	loff_t win_start, win_end;
+	struct msdos_sb_info *sbi = MSDOS_SB(sb);
+	int nr_pid_match = 0;
+	unsigned int state;
+
+	spin_lock(&sbi->lkup_hint_lock);
+	/* find out same pid entry */
+	list_for_each_entry(p, &sbi->lkup_hint_head, lru_list) {
+		if (p->pid == current->pid) {
+			nr_pid_match ++;
+			if (p->dir == dir) {
+				hint = p;
+				goto match;
+			}
+			if (!hint) 
+				hint = p;
+		}
+	}
+
+	if (nr_pid_match < LKUP_MULTIENT_MAX)
+		hint = NULL;
+
+	if (hint == NULL) {
+		int free_hint;
+		free_hint = ffs(sbi->lkup_hint_freemap);
+		if (free_hint) {
+			/* add the new entry */
+			free_hint --;
+			sbi->lkup_hint_freemap &= ~ (1<<(free_hint));
+			hint = &sbi->lkup_hint[free_hint];
+			INIT_LIST_HEAD(&hint->lru_list);
+			/* don't need to recheck hint */
+		} else {
+			/* replace the last entry */
+			struct list_head *p = sbi->lkup_hint_head.prev;
+			hint = list_entry(p, struct fat_lookup_hint, lru_list);
+		}
+		hint->pid = current->pid;
+		hint->dir = dir;
+		hint->last_pos = 0;
+	}
+
+match:
+	state = (FAT_LKUP_ST_RANDOM | FAT_LKUP_ST_LFN_RANDOM);
+	hint->last_pos = pos;
+	if (hint->dir != dir) {
+		hint->dir = dir;
+	} else {
+		win_start = hint->last_pos - WIN_TOP;
+		win_end = hint->last_pos + WIN_BOTTOM;
+		if (win_start <= pos && pos <= win_end) {
+			state &= ~FAT_LKUP_ST_RANDOM;
+		}
+		win_start = hint->last_pos - WIN_LFN_TOP;
+		win_end = hint->last_pos + WIN_LFN_BOTTOM;
+		if (win_start <= pos && pos <= win_end) {
+			state &= ~FAT_LKUP_ST_LFN_RANDOM;
+		}
+	}
+	hint->state = state;
+
+	/* update LRU list */
+	list_del(&hint->lru_list);
+	list_add(&hint->lru_list, &sbi->lkup_hint_head);	/* put hint to head */
+	spin_unlock(&sbi->lkup_hint_lock);
+}
+
 static inline loff_t fat_make_i_pos(struct super_block *sb,
 				    struct buffer_head *bh,
 				    struct msdos_dir_entry *de)
@@ -218,13 +397,23 @@ int fat_search_long(struct inode *inode,
 	int utf8 = sbi->options.utf8;
 	int anycase = (sbi->options.name_check != 's');
 	unsigned short opt_shortname = sbi->options.shortname;
-	loff_t cpos = 0;
 	int chl, i, j, last_u, err;
+	loff_t cpos, start_off;
+	int reach_bottom = 0;
 
+	start_off = cpos = fat_lkup_hint_get(inode, 1);
 	err = -ENOENT;
 	while(1) {
-		if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
+top:
+		if (reach_bottom && cpos >= start_off)
 			goto EODir;
+		if (fat_get_entry(inode, &cpos, &bh, &de) == -1) {
+			if (!start_off || reach_bottom)
+				goto EODir;
+			reach_bottom++;
+			cpos = 0;
+			continue;
+		}
 parse_record:
 		nr_slots = 0;
 		if (de->name[0] == DELETED_FLAG)
@@ -274,8 +463,13 @@ parse_long:
 				if (ds->id & 0x40) {
 					unicode[offset + 13] = 0;
 				}
-				if (fat_get_entry(inode, &cpos, &bh, &de) < 0)
-					goto EODir;
+				if (fat_get_entry(inode, &cpos, &bh, &de) < 0) {
+					if (!start_off || reach_bottom)
+						goto EODir;
+					reach_bottom++;
+					cpos = 0;
+					goto top;
+				}
 				if (slot == 0)
 					break;
 				ds = (struct msdos_dir_slot *) de;
@@ -363,6 +557,7 @@ Found:
 	sinfo->de = de;
 	sinfo->bh = bh;
 	sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
+	fat_lkup_hint_add(inode, sinfo->slot_off);
 	err = 0;
 EODir:
 	if (unicode)
@@ -838,16 +1033,29 @@ int fat_scan(struct inode *dir, const un
 	     struct fat_slot_info *sinfo)
 {
 	struct super_block *sb = dir->i_sb;
+	int reach_bottom = 0;
+	loff_t start_off;
 
-	sinfo->slot_off = 0;
+	start_off = sinfo->slot_off = fat_lkup_hint_get(dir, 0);
 	sinfo->bh = NULL;
-	while (fat_get_short_entry(dir, &sinfo->slot_off, &sinfo->bh,
-				   &sinfo->de) >= 0) {
-		if (!strncmp(sinfo->de->name, name, MSDOS_NAME)) {
-			sinfo->slot_off -= sizeof(*sinfo->de);
-			sinfo->nr_slots = 1;
-			sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
-			return 0;
+	while (1) {
+		if  (fat_get_short_entry(dir, &sinfo->slot_off, &sinfo->bh,
+				         &sinfo->de) >= 0) {
+			if (!strncmp(sinfo->de->name, name, MSDOS_NAME)) {
+				sinfo->slot_off -= sizeof(*sinfo->de);
+				sinfo->nr_slots = 1;
+				sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, 
+							      sinfo->de);
+				fat_lkup_hint_add(dir, sinfo->slot_off);
+				return 0;
+			}
+			if (reach_bottom && (start_off <= sinfo->slot_off)) 
+				break;
+		} else {
+			if (!start_off || reach_bottom)
+				break;
+			reach_bottom ++;
+			sinfo->slot_off = 0;
 		}
 	}
 	return -ENOENT;
fs/fat/inode.c +7 -0
diff -r -up linux-2.6.13/fs/fat/inode.c linux-2.6.13-scan2/fs/fat/inode.c
@@ -342,6 +342,8 @@ static void fat_delete_inode(struct inod
 	clear_inode(inode);
 }
 
+extern void fat_lkup_hint_inval(struct inode*);
+
 static void fat_clear_inode(struct inode *inode)
 {
 	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
@@ -349,6 +351,7 @@ static void fat_clear_inode(struct inode
 	if (is_bad_inode(inode))
 		return;
 	lock_kernel();
+	fat_lkup_hint_inval(inode);
 	spin_lock(&sbi->inode_hash_lock);
 	fat_cache_inval_inode(inode);
 	hlist_del_init(&MSDOS_I(inode)->i_fat_hash);
@@ -1042,6 +1045,8 @@ static int fat_read_root(struct inode *i
 	return 0;
 }
 
+extern void fat_lkup_hint_init_sb(struct super_block *sb);
+
 /*
  * Read the super block of an MS-DOS FS.
  */
@@ -1302,6 +1307,8 @@ int fat_fill_super(struct super_block *s
 		goto out_fail;
 	}
 
+	fat_lkup_hint_init_sb(sb);
+
 	return 0;
 
 out_invalid:
include/linux/msdos_fs.h +19 -0
diff -r -up linux-2.6.13/include/linux/msdos_fs.h linux-2.6.13-scan2/include/linux/msdos_fs.h
@@ -185,6 +185,19 @@ struct fat_slot_info {
 #include <linux/nls.h>
 #include <linux/fs.h>
 
+#define FAT_LKUP_HINT_MAX     16
+/* assume FAT_LKUP_HINT_MAX <= sizeof(long)*8 */
+
+struct fat_lookup_hint {
+	struct list_head lru_list;
+	pid_t pid;
+	struct inode *dir;
+	loff_t last_pos;
+#define FAT_LKUP_ST_RANDOM       1
+#define FAT_LKUP_ST_LFN_RANDOM       2
+	unsigned int state;
+};
+
 struct fat_mount_options {
 	uid_t fs_uid;
 	gid_t fs_gid;
@@ -241,6 +254,12 @@ struct msdos_sb_info {
 
 	spinlock_t inode_hash_lock;
 	struct hlist_head inode_hashtable[FAT_HASH_SIZE];
+
+	/* dirent lookup hints */
+	spinlock_t lkup_hint_lock;
+	struct list_head lkup_hint_head;
+	struct fat_lookup_hint lkup_hint[FAT_LKUP_HINT_MAX];
+	unsigned lkup_hint_freemap:FAT_LKUP_HINT_MAX;
 };
 
 #define FAT_CACHE_VALID	0	/* special case for valid cache */