ext4fs: Add ext4 extent tree cache
In ext4, the file inode can store up to 4 extents. If a file requires more (due to size or fragmentation), an extent index tree is used. Currently, u-boot reads a node from each level of the extent tree for every block read, which is very inefficient when extent tree depth is > 0. This patch adds a cache for the extent tree. We cache the 1 most-recently-seen node at each extent tree level. The typical workload is sequential block access, so once we leave a given tree node, it will not be revisited. Therefore, it makes sense to just cache one node per tree level. Cached blocks are lazily allocated. The typical case is extent tree depth = 0, in which case no caching is needed and no allocations will occur. For files with extent tree depth = 1, this patch produces a ~10x improvement in read speed. For deeper extent trees, the improvement is larger. On my test device, a 3MB file which previously took 9s to read now takes 150ms. Cache size is configurable with CONFIG_EXT4_EXTENT_CACHE_SIZE. However the default of 5 (the maximum depth of well-formed extent trees) is recommended. Signed-off-by: Evan Thompson <evan.thompson@flukenetworks.com> Reviewed-by: Tom Rini <trini@konsulko.com> Cherry-picked-by: Alexandre Bard <alexandre.bard@netmodule.com> BugzID: 57904
This commit is contained in:
parent
c603e3265f
commit
631ba313ea
|
|
@ -33,6 +33,13 @@ In addition, to get the write access command "ext4write", enable:
|
||||||
which automatically selects CONFIG_EXT4_WRITE if it wasn't defined
|
which automatically selects CONFIG_EXT4_WRITE if it wasn't defined
|
||||||
already.
|
already.
|
||||||
|
|
||||||
|
For files with extents, an ext4 extent tree cache improves performance:
|
||||||
|
|
||||||
|
CONFIG_EXT4_EXTENT_CACHE_SIZE <#>
|
||||||
|
|
||||||
|
The above cache size defaults to 5 if not defined. This default is
|
||||||
|
strongly recommended. 0 will turn off extent caching.
|
||||||
|
|
||||||
Also relevant are the generic filesystem commands, selected by:
|
Also relevant are the generic filesystem commands, selected by:
|
||||||
|
|
||||||
CONFIG_CMD_FS_GENERIC
|
CONFIG_CMD_FS_GENERIC
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,11 @@ config EXT4_WRITE
|
||||||
help
|
help
|
||||||
This provides support for creating and writing new files to an
|
This provides support for creating and writing new files to an
|
||||||
existing ext4 filesystem partition.
|
existing ext4 filesystem partition.
|
||||||
|
|
||||||
|
config EXT4_EXTENT_CACHE_SIZE
|
||||||
|
int "Set ext4 extent tree cache depth"
|
||||||
|
default 5
|
||||||
|
depends on FS_EXT4
|
||||||
|
help
|
||||||
|
This sets the depth of extent nodes cached. Sane ext4 files
|
||||||
|
go no deeper than 5. Set to 0 to disable caching.
|
||||||
|
|
|
||||||
|
|
@ -1523,6 +1523,43 @@ void ext4fs_allocate_blocks(struct ext2_inode *file_inode,
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Extent tree cache caches one entry per tree level
|
||||||
|
* eg, ext_block->eh_depth is used as the index into the cache
|
||||||
|
*
|
||||||
|
* If the tree is deeper than CONFIG_EXT4_EXTENT_CACHE_SIZE (very unlikely),
|
||||||
|
* file read performance will be impacted by repeated re-reads
|
||||||
|
* of those index nodes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct extent_cache_entry {
|
||||||
|
unsigned long long block;
|
||||||
|
struct ext4_extent_header *ext_block;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct extent_cache_entry
|
||||||
|
extent_cache[CONFIG_EXT4_EXTENT_CACHE_SIZE];
|
||||||
|
|
||||||
|
static void ext4fs_init_extent_block_cache(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < CONFIG_EXT4_EXTENT_CACHE_SIZE; i++) {
|
||||||
|
extent_cache[i].block = 0;
|
||||||
|
extent_cache[i].ext_block = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ext4fs_free_extent_block_cache(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < CONFIG_EXT4_EXTENT_CACHE_SIZE; i++) {
|
||||||
|
extent_cache[i].block = 0;
|
||||||
|
free(extent_cache[i].ext_block);
|
||||||
|
extent_cache[i].ext_block = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static struct ext4_extent_header *ext4fs_get_extent_block
|
static struct ext4_extent_header *ext4fs_get_extent_block
|
||||||
(struct ext2_data *data, char *buf,
|
(struct ext2_data *data, char *buf,
|
||||||
struct ext4_extent_header *ext_block,
|
struct ext4_extent_header *ext_block,
|
||||||
|
|
@ -1532,6 +1569,7 @@ static struct ext4_extent_header *ext4fs_get_extent_block
|
||||||
unsigned long long block;
|
unsigned long long block;
|
||||||
int blksz = EXT2_BLOCK_SIZE(data);
|
int blksz = EXT2_BLOCK_SIZE(data);
|
||||||
int i;
|
int i;
|
||||||
|
unsigned int cache_item;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
index = (struct ext4_extent_idx *)(ext_block + 1);
|
index = (struct ext4_extent_idx *)(ext_block + 1);
|
||||||
|
|
@ -1554,11 +1592,31 @@ static struct ext4_extent_header *ext4fs_get_extent_block
|
||||||
block = le16_to_cpu(index[i].ei_leaf_hi);
|
block = le16_to_cpu(index[i].ei_leaf_hi);
|
||||||
block = (block << 32) + le32_to_cpu(index[i].ei_leaf_lo);
|
block = (block << 32) + le32_to_cpu(index[i].ei_leaf_lo);
|
||||||
|
|
||||||
if (ext4fs_devread((lbaint_t)block << log2_blksz, 0, blksz,
|
// check cache, read block from device if not found
|
||||||
buf))
|
cache_item = le16_to_cpu(ext_block->eh_depth) - 1;
|
||||||
|
if (cache_item < CONFIG_EXT4_EXTENT_CACHE_SIZE &&
|
||||||
|
extent_cache[cache_item].block == block) {
|
||||||
|
ext_block = extent_cache[cache_item].ext_block;
|
||||||
|
} else {
|
||||||
|
if (ext4fs_devread((lbaint_t)block << log2_blksz, 0,
|
||||||
|
blksz, buf))
|
||||||
ext_block = (struct ext4_extent_header *)buf;
|
ext_block = (struct ext4_extent_header *)buf;
|
||||||
else
|
else
|
||||||
return NULL;
|
return NULL;
|
||||||
|
// put in cache
|
||||||
|
if (cache_item < CONFIG_EXT4_EXTENT_CACHE_SIZE) {
|
||||||
|
struct extent_cache_entry *cache_entry =
|
||||||
|
&extent_cache[cache_item];
|
||||||
|
if (!cache_entry->ext_block)
|
||||||
|
cache_entry->ext_block = zalloc(blksz);
|
||||||
|
if (!cache_entry->ext_block) {
|
||||||
|
printf("ext4 cache alloc failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(cache_entry->ext_block, buf, blksz);
|
||||||
|
cache_entry->block = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2000,6 +2058,7 @@ void ext4fs_close(void)
|
||||||
if (ext4fs_root != NULL) {
|
if (ext4fs_root != NULL) {
|
||||||
free(ext4fs_root);
|
free(ext4fs_root);
|
||||||
ext4fs_root = NULL;
|
ext4fs_root = NULL;
|
||||||
|
ext4fs_free_extent_block_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
ext4fs_reinit_global();
|
ext4fs_reinit_global();
|
||||||
|
|
@ -2376,6 +2435,7 @@ int ext4fs_mount(unsigned part_length)
|
||||||
|
|
||||||
ext4fs_root = data;
|
ext4fs_root = data;
|
||||||
|
|
||||||
|
ext4fs_init_extent_block_cache();
|
||||||
return 1;
|
return 1;
|
||||||
fail:
|
fail:
|
||||||
printf("Failed to mount ext2 filesystem...\n");
|
printf("Failed to mount ext2 filesystem...\n");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue