Add cache for block devices
Subsystems (2)
v2 cover Add cache for block devices
Here's a more full-featured implementation of a cache for block devices that uses a small linked list of cache blocks.
Experimentation loading a 4.5 MiB kernel from the root directory of a FAT filesystem shows that a single cache entry of a single block is the only
Loading the same from the /boot directory of an ext4 filesystem shows a benefit with 4 cache entries, though the single biggest benefit is also with the first cache entry:
=> for n in 0 1 2 4 8 ; do
Quoted 3 lines
blkc 1 $n ; blkc c ; blkc i ; load mmc 0:2 10008000 /boot/zImage ;done
changed to max of 0 entries of 1 blocks each
4955304 bytes read in 503 ms (9.4 MiB/s)
changed to max of 1 entries of 1 blocks each
4955304 bytes read in 284 ms (16.6 MiB/s)
changed to max of 2 entries of 1 blocks each
4955304 bytes read in 284 ms (16.6 MiB/s)
changed to max of 4 entries of 1 blocks each
4955304 bytes read in 255 ms (18.5 MiB/s)
changed to max of 8 entries of 1 blocks each
4955304 bytes read in 255 ms (18.5 MiB/s)
As mentioned earlier in this thread, the modification to the mmc layer should probably be simpler and easier to apply to other block subsystems.
Eric Nelson (3):
drivers: block: add block device cache
block: add Kconfig options for BLOCK_CACHE, CMD_BLOCK_CACHE
mmc: add support for block device cache
drivers/block/Kconfig | 19 ++++ drivers/block/Makefile | 1 + drivers/block/cache_block.c | 240 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/mmc.c | 10 +- drivers/mmc/mmc_write.c | 7 ++ include/part.h | 69 +++++++++++++ 6 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 drivers/block/cache_block.c
Signature · 1 line
2.6.2
RFC PATCH v2 1/3 drivers: block: add block device cache
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.
The maximum number of entries in the cache defaults to 4 and the maximum number of blocks per cache entry has a default of 1, which matches the current implementation of at least FAT and ext4 that only read a single block at a time.
The 'blkcache' command (enabled through CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.
--- drivers/block/Makefile | 1 + drivers/block/cache_block.c | 240 ++++++++++++++++++++++++++++++++++++++++++++ include/part.h | 69 +++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 drivers/block/cache_block.c
drivers/block/Makefile +1 -0
@@ -24,3 +24,4 @@ obj-$(CONFIG_IDE_SIL680) += sil680.o obj-$(CONFIG_SANDBOX) += sandbox.o obj-$(CONFIG_SCSI_SYM53C8XX) += sym53c8xx.o obj-$(CONFIG_SYSTEMACE) += systemace.o +obj-$(CONFIG_BLOCK_CACHE) += cache_block.o
drivers/block/cache_block.c new file +240 -0
240 lines not on this page · raw message
include/part.h +69 -0
73 lines not on this page · raw message
Signature · 1 line
2.6.2
RFC PATCH v2 1/3 drivers: block: add block device cache
Quoted 3 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
...
Quoted 9 lines
Signed-off-by Eric Nelson <[email protected]>--- drivers/block/Makefile | 1 + drivers/block/cache_block.c | 240 ++++++++++++++++++++++++++++++++++++++++++++ include/part.h | 69 +++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 drivers/block/cache_block.c
...
include/part.h int gpt_verify_partitions(struct blk_desc *dev_desc, · line 378
Quoted 8 lines
include/part.h +0
-0@@ -376,4 +376,73 @@ int gpt_verify_partitions(struct blk_desc *dev_desc, gpt_header *gpt_head, gpt_entry **gpt_pte);#endif
I think this stuff now belongs in blk.h instead of part.h:
Quoted 5 lines
include/part.h +5
-0+#ifdef CONFIG_BLOCK_CACHE +/** +* cache_block_read() - attempt to read a set of blocks from cache +* +* @param iftype - IF_TYPE_x for type of device
RFC PATCH v2 1/3 drivers: block: add block device cache
In reply to Eric NelsonQuoted 17 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (
block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.
Quoted 4 lines
The maximum number of entries in the cache defaults to 4 and the maximum number of blocks per cache entry has a default of 1, which matches the current implementation of at least FAT and ext4 that only read a single block at a time.
If the maximum size of the cache is fixed and small (which judging by the description it is), why not use an array rather than a linked list. That would be far simpler to manage.
Quoted 3 lines
The 'blkcache' command (enabled through
CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.
Even with this dynamic adjustment, using an array still feels simpler, although I have't looked at the code yet.
drivers/block/cache_block.c line 41
Quoted 8 lines
drivers/block/cache_block.c +6
-0+/* +* search for a set of blocks in the cache +* +* remove and return the node if found so it can be +* added back at the start of the list and maintain MRU order) +*/
Splitting the remove/add feels a bit dangerous, since forgetting to re-do the add (or e.g. some error causing that to be skipped) could cause cache_count to become inconsistent.
The function name "find" is a bit misleading. cache_find_and_remove() would make its semantics more obvious. Or, simply put the list_del() somewhere else; it doesn't feel like part of a "find" operation to me. Or, put the entire move-to-front operation inside this function so it isn't split up - if so, cache_find_and_move_to_head().
drivers/block/cache_block.c line 43
Quoted 2 lines
drivers/block/cache_block.c +2
-0+static struct block_cache_node *cache_find + (int iftype, int devnum,
Nit: the ( and first n arguments shouldn't be wrapped.
drivers/block/cache_block.c line 70
Quoted 5 lines
drivers/block/cache_block.c +4
-0+int cache_block_read(int iftype, int devnum, + lbaint_t start, lbaint_t blkcnt, + unsigned long blksz, void *buffer) + memcpy(buffer, src, blksz*blkcnt);
Nit: Space around operators. checkpatch should catch this.
drivers/block/cache_block.c line 73
Quoted 3 lines
drivers/block/cache_block.c +3
-0+if (trace) +printf ("hit: start " LBAF ", count " LBAFU "\n" , + start, blkcnt);
I guess I'll find that trace can be adjusted at run-time somewhere later in this patch, but it's more typical to use debug() without the if statement for this. It would be awesome if debug() could be adjusted at run-time...
Quoted 1 line
+void cache_block_fill(int iftype, int devnum,
...
drivers/block/cache_block.c line 118
Quoted 3 lines
drivers/block/cache_block.c +3
-0+if (node->cache ==0 ) { + node->cache =malloc (bytes); +if (!node->cache) {
Nit: may as well be consistent with those NULL checks.
Quoted 1 line
+void
cache_block_invalidate(int iftype, int devnum)
...
Quoted 1 line
+void
cache_block_invalidate_all(void)
There's no invalidate_blocks(); I imagine that writing-to/erasing (some blocks of) a device must call cache_block_invalidate() which will wipe out even data that wasn't written.
Quoted 1 line
+static void
dump_block_cache(void)
...
drivers/block/cache_block.c
Quoted 1 line
drivers/block/cache_block.c +1
-0+list_for_each_safe (entry, n, &block_cache) {
Nit: This doesn't need to be _safe since the list is not being modified.
Quoted 4 lines
drivers/block/cache_block.c +4
-0+static int do_blkcache (cmd_tbl_t *cmdtp,int flag, +int argc,char *const argv[]) +{ +if ((argc ==1 ) || !strcmp ("show" , argv[1 ])) {
drivers/block/cache_block.c line 202
Quoted 10 lines
drivers/block/cache_block.c +10
-0+printf ("block cache:\n" +"%u\thits\n" +"%u\tmisses\n" +"%u\tentries in cache\n" +"trace %s\n" +"max blocks/entry %lu\n" +"max entries %lu\n" , + block_cache_hits, block_cache_misses, cache_count, + trace ?"on" :"off" , + max_blocks_per_entry, max_cache_entries);
Nit: Let's print the value and "label" in a consistent order. I would suggest everything being formatted as:
" description: %u"
so that it's indented, has a delimiter between description and value, and so that irrespective of the length of the converted value, the indentation of the other parts of the string don't change (\t doesn't guarantee this after a certain number length).
I would rather have expected "show" to dump the entries too, but I suppose it's fine that they're separate.
drivers/block/cache_block.c line 203
Quoted 1 line
drivers/block/cache_block.c +1
-0+ }else if ((argc ==2 ) && ('c' == argv[1 ][0 ])) {
Nit: the value of 'c' is always 'c', so it's pointless to test whether it's equal to something. The value being compared is arv[1][0], so the parameters to == should be swapped. I'm aware that == is mathematically commutative, but typical English reading of the operator is "is equal to" which has non-commutative implications re: what is being tested. I'm also aware that this construct is used to trigger compiler warnings if someone types = instead of ==. However, (a) you didn't and (b) compilers warn about this these days, so there's no need to write unusual code to get that feature anymore.
I worry that being imprecise in command-line parsing (i.e. treating both "crud" and "clear" as the same thing) will lead to problems expanding the command-line format in the future; we won't be able to ever add another option starting with "c" and maintain the same abbreviation semantics. I'd suggest requiring the full option name always.
drivers/block/cache_block.c line 215
Quoted 1 line
drivers/block/cache_block.c +1
-0+ }else if ((argc ==3 ) &&isdigit (argv[1 ][0 ]) &&isdigit (argv[2 ][0 ])) {
Let's make this "blkcache set" or "blkcache configure" so that other sub-commands can take numeric parameters in the future, and have consistent command-line syntax.
drivers/block/cache_block.c line 238
Quoted 11 lines
drivers/block/cache_block.c +11
-0+U_BOOT_CMD ( + blkcache,3 ,0 , do_blkcache, +"block cache control/statistics" , +"[show] - show statistics\n" +"blkcache c[lear] - clear statistics\n" +"blkcache d[ump] - dump cache entries\n" +"blkcache i[nvalidate] - invalidate cache\n" +"blkcache #maxblocks #maxentries\n" +"\tset maximum blocks per cache entry, maximum entries\n" +"blkcache t[race] [off] - enable (disable) tracing" +);
BTW, isn't there some support in U-Boot for sub-commands already, so you can write a separate function per sub-command and skip writing all the strcmp logic to select between them? I'm pretty sure I saw that somewhere.
include/part.h
Quoted 3 lines
include/part.h +1
-0+static inline void cache_block_invalidate_all (void ){}
Is that useful outside of the debug commands?
RFC PATCH v2 1/3 drivers: block: add block device cache
Hi Stephen,
Thanks again for the detailed review.
Quoted 18 lines · Stephen Warren
Quoted 17 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (
block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.
Quoted 9 lines
Quoted 4 lines
The maximum number of entries in the cache defaults to 4 and the maximum number of blocks per cache entry has a default of 1, which matches the current implementation of at least FAT and ext4 that only read a single block at a time.
If the maximum size of the cache is fixed and small (which judging by the description it is), why not use an array rather than a linked list. That would be far simpler to manage.
You seem to agree with Marek, and I must be dain-bramaged because I just don't see it.
Quoted 20 lines
Quoted 3 lines
The 'blkcache' command (enabled through
CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.Even with this dynamic adjustment, using an array still feels simpler, although I have't looked at the code yet.
drivers/block/cache_block.c line 41
Quoted 8 lines
drivers/block/cache_block.c +6
-0+/* +* search for a set of blocks in the cache +* +* remove and return the node if found so it can be +* added back at the start of the list and maintain MRU order) +*/ Splitting the remove/add feels a bit dangerous, since forgetting to re-do the add (or e.g. some error causing that to be skipped) could cause
cache_countto become inconsistent.
Quoted 6 lines
The function name "find" is a bit misleading.
cache_find_and_remove()would make its semantics more obvious. Or, simply put thelist_del()somewhere else; it doesn't feel like part of a "find" operation to me. Or, put the entire move-to-front operation inside this function so it isn't split up - if so,cache_find_and_move_to_head().
You're right. I'll look at that for a V3 (hopefully non-RFC) patch set.
Quoted 5 lines
drivers/block/cache_block.c line 43
Quoted 2 lines
drivers/block/cache_block.c +2
-0+static struct block_cache_node *cache_find + (int iftype, int devnum,Nit: the ( and first n arguments shouldn't be wrapped.
Quoted 8 lines
drivers/block/cache_block.c line 70
Quoted 5 lines
drivers/block/cache_block.c +4
-0+int cache_block_read(int iftype, int devnum, + lbaint_t start, lbaint_t blkcnt, + unsigned long blksz, void *buffer) + memcpy(buffer, src, blksz*blkcnt);Nit: Space around operators. checkpatch should catch this.
Thanks.
Quoted 9 lines
drivers/block/cache_block.c line 73
Quoted 3 lines
drivers/block/cache_block.c +3
-0+if (trace) +printf ("hit: start " LBAF ", count " LBAFU "\n" , + start, blkcnt);I guess I'll find that trace can be adjusted at run-time somewhere later in this patch, but it's more typical to use
debug()without the if statement for this. It would be awesome ifdebug()could be adjusted at run-time...
I wanted to keep this as a run-time thing because it's useful in tuning things and I'm not yet certain if/when users might need to update the sizes.
I could wrap these with some #ifdef stuff but I'm not certain which is the right thing to do.
Quoted 8 lines
Quoted 1 line
+void cache_block_fill(int iftype, int devnum,
...
drivers/block/cache_block.c line 118
Quoted 3 lines
drivers/block/cache_block.c +3
-0+if (node->cache ==0 ) { + node->cache =malloc (bytes); +if (!node->cache) {Nit: may as well be consistent with those NULL checks.
Yep.
Quoted 8 lines
Quoted 1 line
+void
cache_block_invalidate(int iftype, int devnum)...
Quoted 1 line
+void
cache_block_invalidate_all(void)There's no
invalidate_blocks(); I imagine that writing-to/erasing (some blocks of) a device must callcache_block_invalidate()which will wipe out even data that wasn't written.
Right. I figured that this wasn't worth extra code.
The 99.99% use case for U-Boot is read-only, so optimizing this just seems like bloat.
Quoted 6 lines
Quoted 1 line
+static void
dump_block_cache(void)...
Quoted 1 line
+
list_for_each_safe(entry, n, &block_cache){Nit: This doesn't need to be
_safesince the list is not being modified.
Good catch.
Quoted 18 lines
Quoted 4 lines
drivers/block/cache_block.c +4
-0+static int do_blkcache (cmd_tbl_t *cmdtp,int flag, +int argc,char *const argv[]) +{ +if ((argc ==1 ) || !strcmp ("show" , argv[1 ])) {drivers/block/cache_block.c line 202
Quoted 10 lines
drivers/block/cache_block.c +10
-0+printf ("block cache:\n" +"%u\thits\n" +"%u\tmisses\n" +"%u\tentries in cache\n" +"trace %s\n" +"max blocks/entry %lu\n" +"max entries %lu\n" , + block_cache_hits, block_cache_misses, cache_count, + trace ?"on" :"off" , + max_blocks_per_entry, max_cache_entries);Nit: Let's print the value and "label" in a consistent order. I would suggest everything being formatted as:
Quoted 7 lines
" description: %u"
so that it's indented, has a delimiter between description and value, and so that irrespective of the length of the converted value, the indentation of the other parts of the string don't change (\t doesn't guarantee this after a certain number length).
Thanks.
Quoted 3 lines
I would rather have expected "show" to dump the entries too, but I suppose it's fine that they're separate.
While testing, I found myself mostly using 'show' to see results.
I added dump as a debug tool and almost removed it, but figured this was still an RFC, so I left it in.
Quoted 12 lines
Quoted 1 line
+ } else if ((argc == 2) && ('c' == argv[1][0])) {
Nit: the value of 'c' is always 'c', so it's pointless to test whether it's equal to something. The value being compared is arv[1][0], so the parameters to == should be swapped. I'm aware that == is mathematically commutative, but typical English reading of the operator is "is equal to" which has non-commutative implications re: what is being tested. I'm also aware that this construct is used to trigger compiler warnings if someone types = instead of ==. However, (a) you didn't and (b) compilers warn about this these days, so there's no need to write unusual code to get that feature anymore.
:) muscle-memory again.
It's usually Marek that catches my yoda-expressions, which are usually tests against zero.
Quoted 6 lines
I worry that being imprecise in command-line parsing (i.e. treating both "crud" and "clear" as the same thing) will lead to problems expanding the command-line format in the future; we won't be able to ever add another option starting with "c" and maintain the same abbreviation semantics. I'd suggest requiring the full option name always.
Quoted 7 lines
Quoted 2 lines
+ } else if ((argc == 3) &&
isdigit(argv[1][0])&&isdigit(argv[2][0])) {Let's make this "blkcache set" or "blkcache configure" so that other sub-commands can take numeric parameters in the future, and have consistent command-line syntax.
Sounds good to me.
Quoted 7 lines
Quoted 7 lines
drivers/block/cache_block.c +7
-0+U_BOOT_CMD ( + blkcache,3 ,0 , do_blkcache, +"block cache control/statistics" , +"[show] - show statistics\n" +"blkcache c[lear] - clear statistics\n" +"blkcache d[ump] - dump cache entries\n" + "blkcache i[nvalidate] - invalidate cache\n"
Quoted 9 lines
drivers/block/cache_block.c line 238
Quoted 4 lines
drivers/block/cache_block.c +4
-0+"blkcache #maxblocks #maxentries\n" +"\tset maximum blocks per cache entry, maximum entries\n" +"blkcache t[race] [off] - enable (disable) tracing" +);BTW, isn't there some support in U-Boot for sub-commands already, so you can write a separate function per sub-command and skip writing all the strcmp logic to select between them? I'm pretty sure I saw that somewhere.
There is, but I haven't (yet) used it. I'll take a look at this in V3.
Quoted 6 lines
include/part.h
Quoted 3 lines
include/part.h +1
-0+static inline void cache_block_invalidate_all (void ){}Is that useful outside of the debug commands?
Not at the moment, and it probably won't be needed when I figure out how this glues to the block layer and/or drivers in a cleaner way.
Regards,
Eric
RFC PATCH v2 2/3 block: add Kconfig options for [CMD_]BLOCK_CACHE
In reply to Eric NelsonAllow the selection of CONFIG_BLOCK_CACHE, CONFIG_CMD_BLOCK_CACHE using menuconfig.
--- drivers/block/Kconfig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
drivers/block/Kconfig +19 -0
@@ -18,3 +18,22 @@ config DISK types can use this, such as AHCI/SATA. It does not provide any standard operations at present. The block device interface has not been converted to driver model. + +config BLOCK_CACHE + bool "Use block device cache" + default n + help + This option enables a disk-block cache for all block devices. + This is most useful when accessing filesystems under U-Boot since + it will prevent repeated reads from directory structures. + +config CMD_BLOCK_CACHE + bool "Include block device cache control command (blkcache)" + depends on BLOCK_CACHE + default y if BLOCK_CACHE + help + Enable the blkcache command, which can be used to control the + operation of the cache functions. + This is most useful when fine-tuning the operation of the cache + during development, but also allows the cache to be disabled when + it might hurt performance (e.g. when using the ums command).
Signature · 1 line
2.6.2
RFC PATCH v2 2/3 block: add Kconfig options for [CMD_]BLOCK_CACHE
Quoted 2 lines · Eric Nelson
Allow the selection of
CONFIG_BLOCK_CACHE,CONFIG_CMD_BLOCK_CACHEusing menuconfig.
I think this should be part of patch 1.
RFC PATCH v2 2/3 block: add Kconfig options for [CMD_]BLOCK_CACHE
Quoted 5 lines · Stephen Warren
Quoted 2 lines · Eric Nelson
Allow the selection of
CONFIG_BLOCK_CACHE,CONFIG_CMD_BLOCK_CACHEusing menuconfig.I think this should be part of patch 1.
Works for me, especially as it adds some documentation in the form of help text.
RFC PATCH v2 3/3 mmc: add support for block device cache
In reply to Eric Nelson--- drivers/mmc/mmc.c | 10 +++++++++- drivers/mmc/mmc_write.c | 7 +++++++ 2 files changed, 16 insertions(+), 1 deletion(-)
drivers/mmc/mmc.c +9 -1
@@ -6,7 +6,6 @@* * SPDX-License-Identifier: GPL-2.0+ */ -#include <config.h> #include <common.h> #include <command.h>
@@ -240,6 +239,8 @@ static ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start,int dev_num = block_dev->devnum;int err;lbaint_t cur, blocks_todo = blkcnt; +void *outbuf = dst; +lbaint_t outblk = start;if (blkcnt ==0 )return 0 ;
@@ -260,6 +261,10 @@ static ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start,return 0 ; } +if (cache_block_read (IF_TYPE_MMC , dev_num, start, blkcnt, + mmc->read_bl_len, dst)) +return blkcnt; +if (mmc_set_blocklen (mmc, mmc->read_bl_len)) {debug ("%s: Failed to set blocklen\n" , __func__);return 0 ;
@@ -277,6 +282,9 @@ static ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start, dst += cur * mmc->read_bl_len; }while (blocks_todo >0 ); +cache_block_fill (IF_TYPE_MMC , dev_num, outblk, blkcnt, + mmc->read_bl_len, outbuf); +return blkcnt; }
drivers/mmc/mmc_write.c +7 -0
@@ -12,6 +12,7 @@#include <part.h> #include <div64.h> #include <linux/math64.h> +#include <part.h> #include "mmc_private.h" static ulong mmc_erase_t (struct mmc *mmc,ulong start,lbaint_t blkcnt)
@@ -20,6 +21,8 @@ static ulong mmc_erase_t(struct mmc *mmc, ulong start, lbaint_t blkcnt)ulong end;int err, start_cmd, end_cmd; +cache_block_invalidate (IF_TYPE_MMC , mmc->block_dev.devnum); +if (mmc->high_capacity) { end = start + blkcnt -1 ; }else {
@@ -82,6 +85,8 @@ unsigned long mmc_berase(struct blk_desc *block_dev, lbaint_t start,if (err <0 )return -1 ; +cache_block_invalidate (IF_TYPE_MMC , dev_num); +/* * We want to see if the requested start or total block count are * unaligned. We discard the whole numbers and only care about the
@@ -186,6 +191,8 @@ ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,if (err <0 )return 0 ; +cache_block_invalidate (IF_TYPE_MMC , dev_num); +if (mmc_set_blocklen (mmc, mmc->write_bl_len))return 0 ;
Signature · 1 line
2.6.2
RFC PATCH v2 3/3 mmc: add support for block device cache
drivers/mmc/mmc.c line 9
Quoted 8 lines · Eric Nelson
drivers/mmc/mmc.c +0
-1@@ -6,7 +6,6 @@* * SPDX-License-Identifier: GPL-2.0+ */ -#include <config.h>
Unrelated change?
I don't see any cache invalidation call when the SD device is re-initialized.
I think I mentioned these two points last time.
RFC PATCH v2 3/3 mmc: add support for block device cache
Hi Stephen,
Quoted 4 lines · Stephen Warren
Quoted 1 line · Eric Nelson
drivers/mmc/mmc.c +0
-0
Quoted 9 lines
drivers/mmc/mmc.c line 9
Quoted 6 lines
drivers/mmc/mmc.c +0
-1@@ -6,7 +6,6 @@* * SPDX-License-Identifier: GPL-2.0+ */ -#include <config.h> Unrelated change?
Yes. Sorry about that.
Quoted 4 lines
I don't see any cache invalidation call when the SD device is re-initialized.
I think I mentioned these two points last time.
You did and I missed it.
Regards,
Eric
v2 cover Add cache for block devices
In reply to Eric NelsonQuoted 2 lines · Eric Nelson
Here's a more full-featured implementation of a cache for block devices that uses a small linked list of cache blocks.
Why do you use linked list ? You have four entries, you can as well use fixed array. Maybe you should implement an adaptive cache would would use the unpopulated malloc area and hash the sector number(s) into that area ?
Quoted 3 lines
Experimentation loading a 4.5 MiB kernel from the root directory of a FAT filesystem shows that a single cache entry of a single block is the only
only ... what ? This is where things started to be interesting, but you leave us hanging :)
Quoted 37 lines
Loading the same from the /boot directory of an ext4 filesystem shows a benefit with 4 cache entries, though the single biggest benefit is also with the first cache entry:
=> for n in 0 1 2 4 8 ; do
Quoted 3 lines
blkc 1 $n ; blkc c ; blkc i ; load mmc 0:2 10008000 /boot/zImage ;done
changed to max of 0 entries of 1 blocks each
4955304 bytes read in 503 ms (9.4 MiB/s)
changed to max of 1 entries of 1 blocks each
4955304 bytes read in 284 ms (16.6 MiB/s)
changed to max of 2 entries of 1 blocks each
4955304 bytes read in 284 ms (16.6 MiB/s)
changed to max of 4 entries of 1 blocks each
4955304 bytes read in 255 ms (18.5 MiB/s)
changed to max of 8 entries of 1 blocks each
4955304 bytes read in 255 ms (18.5 MiB/s)As mentioned earlier in this thread, the modification to the mmc layer should probably be simpler and easier to apply to other block subsystems.
Eric Nelson (3):
drivers: block: add block device cache
block: addKconfigoptions forBLOCK_CACHE,CMD_BLOCK_CACHE
mmc: add support for block device cachedrivers/block/Kconfig | 19 ++++ drivers/block/Makefile | 1 + drivers/block/cache_block.c | 240 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/mmc.c | 10 +- drivers/mmc/mmc_write.c | 7 ++ include/part.h | 69 +++++++++++++ 6 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 drivers/block/cache_block.c
Signature · 2 lines
Best regards, Marek Vasut
v2 cover Add cache for block devices
Hi Marek,
Quoted 9 lines · Marek Vasut
Quoted 2 lines · Eric Nelson
Here's a more full-featured implementation of a cache for block devices that uses a small linked list of cache blocks.
Why do you use linked list ? You have four entries, you can as well use fixed array. Maybe you should implement an adaptive cache would would use the unpopulated malloc area and hash the sector number(s) into that area ?
I was looking for a simple implementation that would allow tweaking of the max entries/size per entry.
We could get higher performance through hashing, but with such a small cache, it's probably not worth extra code.
Using an array and re-allocating on changes to the max entries variable is feasible, but I think it would be slightly more code.
Quoted 7 lines
Quoted 3 lines
Experimentation loading a 4.5 MiB kernel from the root directory of
a FAT filesystem shows that a single cache entry of a single
block is the onlyonly ... what ? This is where things started to be interesting, but you leave us hanging :)
Oops.
... I was planning on re-wording that.
My testing showed no gain in performance (additional cache hits) past a single entry of a single block. This was done on a small (32MiB) partition with a small number of files (~10) and only a single read is skipped.
=> blkc c ; blkc i ; blkc 0 0 ;
changed to max of 0 entries of 0 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 247 ms (19.1 MiB/s)
=> blkc
block cache:
0 hits
7 misses
0 entries in cache
trace off
max blocks/entry 0
max entries 0
=> blkc c ; blkc i ; blkc 1 1 ;
changed to max of 1 entries of 1 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 243 ms (19.4 MiB/s)
=> blkc
block cache:
1 hits
6 misses
1 entries in cache
trace off
max blocks/entry 1
max entries 1
I don't believe that enabling the cache is worth the extra code for this use case.
By comparison, a load of 150 MiB compressed disk image from ext4 showed a 30x speedup with the V1 patch (single block, single entry) from ~150s to 5s.
Without some form of cache, the 150s was long enough to make a user (me) think something is broken.
Regards,
Eric
v2 cover Add cache for block devices
Quoted 13 lines · Eric Nelson
Hi Marek,
Quoted 9 lines · Marek Vasut
Quoted 2 lines · Eric Nelson
Here's a more full-featured implementation of a cache for block devices that uses a small linked list of cache blocks.
Why do you use linked list ? You have four entries, you can as well use fixed array. Maybe you should implement an adaptive cache would would use the unpopulated malloc area and hash the sector number(s) into that area ?
Quoted 5 lines
I was looking for a simple implementation that would allow tweaking of the max entries/size per entry.
We could get higher performance through hashing, but with such a small cache, it's probably not worth extra code.
The hashing function can be a simple modulo on sector number ;-) That'd be less code than linked lists.
Quoted 2 lines
Using an array and re-allocating on changes to the max entries variable is feasible, but I think it would be slightly more code.
That would indeed be more code.
Quoted 8 lines
Quoted 7 lines
Quoted 3 lines
Experimentation loading a 4.5 MiB kernel from the root directory of
a FAT filesystem shows that a single cache entry of a single
block is the onlyonly ... what ? This is where things started to be interesting, but you leave us hanging :)
Quoted 8 lines
Oops.
... I was planning on re-wording that.
My testing showed no gain in performance (additional cache hits) past a single entry of a single block. This was done on a small (32MiB) partition with a small number of files (~10) and only a single read is skipped.
I'd kinda expect that indeed.
Quoted 30 lines
=> blkc c ; blkc i ; blkc 0 0 ;
changed to max of 0 entries of 0 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 247 ms (19.1 MiB/s)
=> blkc
block cache:
0 hits
7 misses
0 entries in cache
trace off
max blocks/entry 0
max entries 0
=> blkc c ; blkc i ; blkc 1 1 ;
changed to max of 1 entries of 1 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 243 ms (19.4 MiB/s)
=> blkc
block cache:
1 hits
6 misses
1 entries in cache
trace off
max blocks/entry 1
max entries 1I don't believe that enabling the cache is worth the extra code for this use case.
Quoted 6 lines
By comparison, a load of 150 MiB compressed disk image from ext4 showed a 30x speedup with the V1 patch (single block, single entry) from ~150s to 5s.
Without some form of cache, the 150s was long enough to make a user (me) think something is broken.
I'm obviously loving this improvement.
Best regards,
Marek Vasut
v2 cover Add cache for block devices
Hi Marek,
Quoted 12 lines · Marek Vasut
Quoted 11 lines · Eric Nelson
Quoted 9 lines · Marek Vasut
Quoted 2 lines · Eric Nelson
Here's a more full-featured implementation of a cache for block devices that uses a small linked list of cache blocks.
Why do you use linked list ? You have four entries, you can as well use fixed array. Maybe you should implement an adaptive cache would would use the unpopulated malloc area and hash the sector number(s) into that area ?
Quoted 9 lines
Quoted 5 lines
I was looking for a simple implementation that would allow tweaking of the max entries/size per entry.
We could get higher performance through hashing, but with such a small cache, it's probably not worth extra code.
The hashing function can be a simple modulo on sector number ;-) That'd be less code than linked lists.
I'm not seeing how.
I'm going look first at a better way to integrate than the approach taken in patch 3.
Quoted 15 lines
Quoted 2 lines
Using an array and re-allocating on changes to the max entries variable is feasible, but I think it would be slightly more code.
That would indeed be more code.
Quoted 10 lines
Quoted 7 lines
Quoted 3 lines
Experimentation loading a 4.5 MiB kernel from the root directory of
a FAT filesystem shows that a single cache entry of a single
block is the onlyonly ... what ? This is where things started to be interesting, but you leave us hanging :)
Oops.
Quoted 9 lines
Quoted 6 lines
... I was planning on re-wording that.
My testing showed no gain in performance (additional cache hits) past a single entry of a single block. This was done on a small (32MiB) partition with a small number of files (~10) and only a single read is skipped.
I'd kinda expect that indeed.
Yeah, and the single-digit-ms improvement isn't worth much.
Quoted 30 lines
Quoted 30 lines
=> blkc c ; blkc i ; blkc 0 0 ;
changed to max of 0 entries of 0 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 247 ms (19.1 MiB/s)
=> blkc
block cache:
0 hits
7 misses
0 entries in cache
trace off
max blocks/entry 0
max entries 0
=> blkc c ; blkc i ; blkc 1 1 ;
changed to max of 1 entries of 1 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 243 ms (19.4 MiB/s)
=> blkc
block cache:
1 hits
6 misses
1 entries in cache
trace off
max blocks/entry 1
max entries 1I don't believe that enabling the cache is worth the extra code for this use case.
Quoted 9 lines
Quoted 6 lines
By comparison, a load of 150 MiB compressed disk image from ext4 showed a 30x speedup with the V1 patch (single block, single entry) from ~150s to 5s.
Without some form of cache, the 150s was long enough to make a user (me) think something is broken.
I'm obviously loving this improvement.
Glad to hear it.
v2 cover Add cache for block devices
Quoted 1 line · Eric Nelson
Hi Marek,
Hi!
Quoted 19 lines · Marek Vasut
Quoted 18 lines · Eric Nelson
Quoted 16 lines · Marek Vasut
Quoted 9 lines · Eric Nelson
Quoted 2 lines
Here's a more full-featured implementation of a cache for block devices that uses a small linked list of cache blocks.
Why do you use linked list ? You have four entries, you can as well use fixed array. Maybe you should implement an adaptive cache would would use the unpopulated malloc area and hash the sector number(s) into that area ?
I was looking for a simple implementation that would allow tweaking of the max entries/size per entry.
We could get higher performance through hashing, but with such a small cache, it's probably not worth extra code.
Quoted 8 lines
Quoted 3 lines
The hashing function can be a simple modulo on sector number ;-) That'd be less code than linked lists.
I'm not seeing how.
I'm going look first at a better way to integrate than the approach taken in patch 3.
I will dive in the code itself and comment if applicable.
Quoted 61 lines
Quoted 15 lines
Quoted 2 lines
Using an array and re-allocating on changes to the max entries variable is feasible, but I think it would be slightly more code.
That would indeed be more code.
Quoted 10 lines
Quoted 7 lines
Quoted 3 lines
Experimentation loading a 4.5 MiB kernel from the root directory of
a FAT filesystem shows that a single cache entry of a single
block is the onlyonly ... what ? This is where things started to be interesting, but you leave us hanging :)
Oops.
Quoted 9 lines
Quoted 6 lines
... I was planning on re-wording that.
My testing showed no gain in performance (additional cache hits) past a single entry of a single block. This was done on a small (32MiB) partition with a small number of files (~10) and only a single read is skipped.
I'd kinda expect that indeed.
Yeah, and the single-digit-ms improvement isn't worth much.
Quoted 34 lines
Quoted 34 lines
=> blkc c ; blkc i ; blkc 0 0 ;
changed to max of 0 entries of 0 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 247 ms (19.1 MiB/s)
=> blkc
block cache:
0 hits
7 misses
0 entries in cache
trace off
max blocks/entry 0
max entries 0
=> blkc c ; blkc i ; blkc 1 1 ;
changed to max of 1 entries of 1 blocks each
=> load mmc 0 10008000 /zImage
reading /zImage
4955304 bytes read in 243 ms (19.4 MiB/s)
=> blkc
block cache:
1 hits
6 misses
1 entries in cache
trace off
max blocks/entry 1
max entries 1I don't believe that enabling the cache is worth the extra code for this use case.
By comparison, a load of 150 MiB compressed disk image from ext4 showed a 30x speedup with the V1 patch (single block, single entry) from ~150s to 5s.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
Without some form of cache, the 150s was long enough to make a user (me) think something is broken.
I'm obviously loving this improvement.
Glad to hear it.
:)
Signature · 2 lines
Best regards, Marek Vasut
cover Add cache for block devices
In reply to Eric NelsonPatch 1 is the block cache implementation.
Patches 2 and 3 update the mmc and sata commands to use the new blk_dread/dwrite/derase routines and can be applied separately.
Notable changes since the V2 RFC patch set include:
- renaming of files and routines to use "blkcache" rather than "cache_block" to make things consistent with the command name.
- implemented through the blk_dread/dwrite/derase routines in blk.h instead of drivers/mmc.
- added detection of device (re)initialization in disk/part.c
- changed the default max blocks/entry and entries to 2 and 32 respectively based on further testing.
- changed to sub-command implementation as suggested by Stephen Warren.
- changed interface to cache_find per Stephen's suggestion
I tested with and without DM and compile-tested with CONFIG_BLK enabled, but only on MMC and USB devices.
As a teaser, here are some performance numbers loading a 5MiB file from SD card.
With cache:
=> blkc max 2 32 changed to max of 32 entries of 2 blocks each => load mmc 0:2 10008000 /usr/lib/libGAL.so 5042083 bytes read in 251 ms (19.2 MiB/s)
Without cache:
=> blkc max 0 0 changed to max of 0 entries of 0 blocks each => load mmc 0:2 10008000 /usr/lib/libGAL.so 5042083 bytes read in 4638 ms (1 MiB/s)
Eric Nelson (3):
drivers: block: add block device cache
mmc: force mmc command to go through block layer
sata: force sata reads and writes to go through block layer
cmd/mmc.c | 7 +- cmd/sata.c | 6 +- disk/part.c | 2 + drivers/block/Kconfig | 20 ++++ drivers/block/Makefile | 1 + drivers/block/blk-uclass.c | 13 +- drivers/block/blkcache.c | 293 +++++++++++++++++++++++++++++++++++++++++++++ include/blk.h | 79 +++++++++++- 8 files changed, 414 insertions(+), 7 deletions(-) create mode 100644 drivers/block/blkcache.c
Signature · 1 line
2.6.2
PATCH v2 1/3 drivers: block: add block device cache
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.
The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
The 'blkcache' command (enabled through CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.
--- disk/part.c | 2 + drivers/block/Kconfig | 20 ++++ drivers/block/Makefile | 1 + drivers/block/blk-uclass.c | 13 +- drivers/block/blkcache.c | 293 +++++++++++++++++++++++++++++++++++++++++++++ include/blk.h | 79 +++++++++++- 6 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 drivers/block/blkcache.c
disk/part.c +2 -0
@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry; +blkcache_invalidate (dev_desc->if_type, dev_desc->devnum); + dev_desc->part_type =PART_TYPE_UNKNOWN ;for (entry = drv; entry != drv + n_ents; entry++) {int ret;
drivers/block/Kconfig +20 -0
@@ -18,3 +18,23 @@ config DISK types can use this, such as AHCI/SATA. It does not provide any standard operations at present. The block device interface has not been converted to driver model. + +config BLOCK_CACHE + bool "Use block device cache" + default n + help + This option enables a disk-block cache for all block devices. + This is most useful when accessing filesystems under U-Boot since + it will prevent repeated reads from directory structures and other + filesystem data structures. + +config CMD_BLOCK_CACHE + bool "Include block device cache control command (blkcache)" + depends on BLOCK_CACHE + default y if BLOCK_CACHE + help + Enable the blkcache command, which can be used to control the + operation of the cache functions. + This is most useful when fine-tuning the operation of the cache + during development, but also allows the cache to be disabled when + it might hurt performance (e.g. when using the ums command).
drivers/block/Makefile +1 -0
@@ -24,3 +24,4 @@ obj-$(CONFIG_IDE_SIL680) += sil680.o obj-$(CONFIG_SANDBOX) += sandbox.o obj-$(CONFIG_SCSI_SYM53C8XX) += sym53c8xx.o obj-$(CONFIG_SYSTEMACE) += systemace.o +obj-$(CONFIG_BLOCK_CACHE) += blkcache.o
drivers/block/blk-uclass.c +12 -1
@@ -80,11 +80,20 @@ unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start, {struct udevice *dev = block_dev->bdev;const struct blk_ops *ops =blk_get_ops (dev); +ulong blks_read;if (!ops->read)return -ENOSYS ; -return ops->read (dev, start, blkcnt, buffer); +if (blkcache_read (block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer)) +return blkcnt; + blks_read = ops->read (dev, start, blkcnt, buffer); +if (blks_read == blkcnt) +blkcache_fill (block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer); + +return blks_read; } unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
@@ -96,6 +105,7 @@ unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,if (!ops->write)return -ENOSYS ; +blkcache_invalidate (block_dev->if_type, block_dev->devnum);return ops->write (dev, start, blkcnt, buffer); }
@@ -108,6 +118,7 @@ unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start,if (!ops->erase)return -ENOSYS ; +blkcache_invalidate (block_dev->if_type, block_dev->devnum);return ops->erase (dev, start, blkcnt); }
drivers/block/blkcache.c new file +293 -0
293 lines not on this page · raw message
include/blk.h +78 -1
107 lines not on this page · raw message
Signature · 1 line
2.6.2
PATCH 1/3 drivers: block: add block device cache
Quoted 1 line · Eric Nelson
+++
b/drivers/block/blkcache.c
[snip]
Quoted 1 line
+static int trace;
I see where you can toggle this at run-time. But do we really think that this will be useful outside of debug? My first reaction is that we should move the trace stuff into debug() statements instead.
[snip]
Quoted 1 line
+#ifdef
CONFIG_CMD_BLOCK_CACHE
Please split the command code into cmd/blkcache.c. And yes, this might require thinking harder about what to expose or making an API for some of it. I'm also not sure that's a bad thing as tuning the cache seems useful long term but dumping the stats seems more like debug work.
[snip]
drivers/block/blkcache.c line 270
Quoted 3 lines
drivers/block/blkcache.c new file +3
-0+/* Strip off leading 'i2c' command argument */ + argc--; + argv++;
I see you borrowed from i2c here :)
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160328/5d374cce/attachment.sig>
PATCH v2 1/3 drivers: block: add block device cache
Thanks Tom,
Quoted 10 lines · Tom Rini
Quoted 1 line · Eric Nelson
+++
b/drivers/block/blkcache.c[snip]
Quoted 1 line
+static int trace;
I see where you can toggle this at run-time. But do we really think that this will be useful outside of debug? My first reaction is that we should move the trace stuff into
debug()statements instead.
Will do.
Stephen had the same comment.
Quoted 8 lines
[snip]
Quoted 1 line
+#ifdef
CONFIG_CMD_BLOCK_CACHEPlease split the command code into
cmd/blkcache.c. And yes, this might require thinking harder about what to expose or making an API for some of it. I'm also not sure that's a bad thing as tuning the cache seems useful long term but dumping the stats seems more like debug work.
Okay. I started to do that but stopped when I looked at the number of implementation details needed by the commands themselves.
Quoted 7 lines
[snip]
drivers/block/blkcache.c line 270
Quoted 3 lines
drivers/block/blkcache.c new file +3
-0+/* Strip off leading 'i2c' command argument */ + argc--; + argv++;I see you borrowed from i2c here :)
Yep. Oops.
Regards,
Eric
PATCH v2 1/3 drivers: block: add block device cache
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.
The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
The 'blkcache' command (enabled through CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.
---
Changes in V2:
- moved blkcache command into cmd/blkcache (Misc commands)
- removed invalidate sub-command - done in max command
- removed clear sub-command - done in show command
- removed dump command (was only for debug)
- removed run-time trace control in favor of debug() messages
- use struct block_cache_stats instead of separate static vars to streamline block_cache_
- rename max sub-command to configure to match API
- change show sub-command to auto-clear statistics
cmd/Kconfig | 11 +++ cmd/Makefile | 1 + cmd/blkcache.c | 90 +++++++++++++++++++++++ disk/part.c | 2 + drivers/block/Kconfig | 9 +++ drivers/block/Makefile | 1 + drivers/block/blk-uclass.c | 13 +++- drivers/block/blkcache.c | 173 +++++++++++++++++++++++++++++++++++++++++++++ include/blk.h | 105 ++++++++++++++++++++++++++- 9 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 cmd/blkcache.c create mode 100644 drivers/block/blkcache.c
cmd/Kconfig +11 -0
@@ -485,6 +485,17 @@ config SYS_AMBAPP_PRINT_ON_STARTUP help Show AMBA Plug-n-Play information on startup. +config CMD_BLOCK_CACHE + bool "blkcache - control and stats for block cache" + depends on BLOCK_CACHE + default y if BLOCK_CACHE + help + Enable the blkcache command, which can be used to control the + operation of the cache functions. + This is most useful when fine-tuning the operation of the cache + during development, but also allows the cache to be disabled when + it might hurt performance (e.g. when using the ums command). + config CMD_TIME bool "time" help
cmd/Makefile +1 -0
@@ -20,6 +20,7 @@ obj-$(CONFIG_SOURCE) += source.o obj-$(CONFIG_CMD_SOURCE) += source.o obj-$(CONFIG_CMD_BDI) += bdinfo.o obj-$(CONFIG_CMD_BEDBUG) += bedbug.o +obj-$(CONFIG_CMD_BLOCK_CACHE) += blkcache.o obj-$(CONFIG_CMD_BMP) += bmp.o obj-$(CONFIG_CMD_BOOTEFI) += bootefi.o obj-$(CONFIG_CMD_BOOTMENU) += bootmenu.o
cmd/blkcache.c new file +90 -0
90 lines not on this page · raw message
disk/part.c +2 -0
@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry; +blkcache_invalidate (dev_desc->if_type, dev_desc->devnum); + dev_desc->part_type =PART_TYPE_UNKNOWN ;for (entry = drv; entry != drv + n_ents; entry++) {int ret;
drivers/block/Kconfig +9 -0
@@ -18,3 +18,12 @@ config DISK types can use this, such as AHCI/SATA. It does not provide any standard operations at present. The block device interface has not been converted to driver model. + +config BLOCK_CACHE + bool "Use block device cache" + default n + help + This option enables a disk-block cache for all block devices. + This is most useful when accessing filesystems under U-Boot since + it will prevent repeated reads from directory structures and other + filesystem data structures.
drivers/block/Makefile +1 -0
@@ -24,3 +24,4 @@ obj-$(CONFIG_IDE_SIL680) += sil680.o obj-$(CONFIG_SANDBOX) += sandbox.o obj-$(CONFIG_SCSI_SYM53C8XX) += sym53c8xx.o obj-$(CONFIG_SYSTEMACE) += systemace.o +obj-$(CONFIG_BLOCK_CACHE) += blkcache.o
drivers/block/blk-uclass.c +12 -1
@@ -80,11 +80,20 @@ unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start, {struct udevice *dev = block_dev->bdev;const struct blk_ops *ops =blk_get_ops (dev); +ulong blks_read;if (!ops->read)return -ENOSYS ; -return ops->read (dev, start, blkcnt, buffer); +if (blkcache_read (block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer)) +return blkcnt; + blks_read = ops->read (dev, start, blkcnt, buffer); +if (blks_read == blkcnt) +blkcache_fill (block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer); + +return blks_read; } unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
@@ -96,6 +105,7 @@ unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,if (!ops->write)return -ENOSYS ; +blkcache_invalidate (block_dev->if_type, block_dev->devnum);return ops->write (dev, start, blkcnt, buffer); }
@@ -108,6 +118,7 @@ unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start,if (!ops->erase)return -ENOSYS ; +blkcache_invalidate (block_dev->if_type, block_dev->devnum);return ops->erase (dev, start, blkcnt); }
drivers/block/blkcache.c new file +173 -0
173 lines not on this page · raw message
include/blk.h +104 -1
133 lines not on this page · raw message
Signature · 1 line
2.6.2
PATCH v3 1/3 drivers: block: add block device cache
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.
The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
The 'blkcache' command (enabled through CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.
---
Changes in V3:
- replace remnant of "blkcache max" from sub-command name change
Changes in V2:
- moved blkcache command into cmd/blkcache (Misc commands)
- removed invalidate sub-command - done in max command
- removed clear sub-command - done in show command
- removed dump command (was only for debug)
- removed run-time trace control in favor of debug() messages
- use struct block_cache_stats instead of separate static vars to streamline block_cache_
- rename max sub-command to configure to match API
- change show sub-command to auto-clear statistics
cmd/Kconfig | 11 +++ cmd/Makefile | 1 + cmd/blkcache.c | 90 +++++++++++++++++++++++ disk/part.c | 2 + drivers/block/Kconfig | 9 +++ drivers/block/Makefile | 1 + drivers/block/blk-uclass.c | 13 +++- drivers/block/blkcache.c | 173 +++++++++++++++++++++++++++++++++++++++++++++ include/blk.h | 105 ++++++++++++++++++++++++++- 9 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 cmd/blkcache.c create mode 100644 drivers/block/blkcache.c
cmd/Kconfig +11 -0
@@ -485,6 +485,17 @@ config SYS_AMBAPP_PRINT_ON_STARTUP help Show AMBA Plug-n-Play information on startup. +config CMD_BLOCK_CACHE + bool "blkcache - control and stats for block cache" + depends on BLOCK_CACHE + default y if BLOCK_CACHE + help + Enable the blkcache command, which can be used to control the + operation of the cache functions. + This is most useful when fine-tuning the operation of the cache + during development, but also allows the cache to be disabled when + it might hurt performance (e.g. when using the ums command). + config CMD_TIME bool "time" help
cmd/Makefile +1 -0
@@ -20,6 +20,7 @@ obj-$(CONFIG_SOURCE) += source.o obj-$(CONFIG_CMD_SOURCE) += source.o obj-$(CONFIG_CMD_BDI) += bdinfo.o obj-$(CONFIG_CMD_BEDBUG) += bedbug.o +obj-$(CONFIG_CMD_BLOCK_CACHE) += blkcache.o obj-$(CONFIG_CMD_BMP) += bmp.o obj-$(CONFIG_CMD_BOOTEFI) += bootefi.o obj-$(CONFIG_CMD_BOOTMENU) += bootmenu.o
cmd/blkcache.c new file +90 -0
90 lines not on this page · raw message
disk/part.c +2 -0
@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry; +blkcache_invalidate (dev_desc->if_type, dev_desc->devnum); + dev_desc->part_type =PART_TYPE_UNKNOWN ;for (entry = drv; entry != drv + n_ents; entry++) {int ret;
drivers/block/Kconfig +9 -0
@@ -18,3 +18,12 @@ config DISK types can use this, such as AHCI/SATA. It does not provide any standard operations at present. The block device interface has not been converted to driver model. + +config BLOCK_CACHE + bool "Use block device cache" + default n + help + This option enables a disk-block cache for all block devices. + This is most useful when accessing filesystems under U-Boot since + it will prevent repeated reads from directory structures and other + filesystem data structures.
drivers/block/Makefile +1 -0
@@ -24,3 +24,4 @@ obj-$(CONFIG_IDE_SIL680) += sil680.o obj-$(CONFIG_SANDBOX) += sandbox.o obj-$(CONFIG_SCSI_SYM53C8XX) += sym53c8xx.o obj-$(CONFIG_SYSTEMACE) += systemace.o +obj-$(CONFIG_BLOCK_CACHE) += blkcache.o
drivers/block/blk-uclass.c +12 -1
@@ -80,11 +80,20 @@ unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start, {struct udevice *dev = block_dev->bdev;const struct blk_ops *ops =blk_get_ops (dev); +ulong blks_read;if (!ops->read)return -ENOSYS ; -return ops->read (dev, start, blkcnt, buffer); +if (blkcache_read (block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer)) +return blkcnt; + blks_read = ops->read (dev, start, blkcnt, buffer); +if (blks_read == blkcnt) +blkcache_fill (block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer); + +return blks_read; } unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,
@@ -96,6 +105,7 @@ unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start,if (!ops->write)return -ENOSYS ; +blkcache_invalidate (block_dev->if_type, block_dev->devnum);return ops->write (dev, start, blkcnt, buffer); }
@@ -108,6 +118,7 @@ unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start,if (!ops->erase)return -ENOSYS ; +blkcache_invalidate (block_dev->if_type, block_dev->devnum);return ops->erase (dev, start, blkcnt); }
drivers/block/blkcache.c new file +173 -0
173 lines not on this page · raw message
include/blk.h +104 -1
133 lines not on this page · raw message
Signature · 1 line
2.6.2
PATCH v3 1/3 drivers: block: add block device cache
Quoted 28 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (
block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
The 'blkcache' command (enabled through
CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.cmd/blkcache.c +1
-0+static int blkc_show(cmd_tbl_t *cmdtp, int flag,
cmd/blkcache.c line 23
Quoted 10 lines
cmd/blkcache.c +10
-0+ int argc, char * const argv[]) +{ + struct block_cache_stats stats; +blkcache_stats (&stats); + + printf(" hits: %u\n" + " misses: %u\n" + " entries: %u\n" + " max blocks/entry: %u\n" + " max cache entries: %u\n",
Nit: I'm not sure why all that command output is indented. Perhaps it made sense before if some kind of title was printed before that text, but I don't think it is any more.
cmd/blkcache.c line 81
Quoted 10 lines
cmd/blkcache.c +9
-0+static int do_blkcache (cmd_tbl_t *cmdtp,int flag, + c = find_cmd_tbl(argv[0], &cmd_blkc_sub[0], ARRAY_SIZE(cmd_blkc_sub)); + +if (c) +return c->cmd (cmdtp, flag, argc, argv); +else +return CMD_RET_USAGE ; + +return 0 ;
Nit: I'd prefer to see the error handling immediately follow the function call whose result is being tested, and the non-failure-case code be not indented, i.e.:
c = find_cmd_tbl(argv[0], &cmd_blkc_sub[0], ARRAY_SIZE(cmd_blkc_sub)); if (!c)
return CMD_RET_USAGE;
return c->cmd(cmdtp, flag, argc, argv);
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 7 lines
disk/part.c +1
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry; +blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);
Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;
drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};
Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a situation where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
include/blk.h struct blk_desc { · line 102
Quoted 6 lines
include/blk.h +4
-0+int blkcache_read + (int iftype,int dev, +lbaint_t start,lbaint_t blkcnt, +unsigned long blksz,void *buffer);
Nit: The opening ( and first n arguments should be wrapped onto the same line as the function name.
PATCH v3 1/3 drivers: block: add block device cache
Quoted 25 lines · Stephen Warren
Quoted 24 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (
block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
The 'blkcache' command (enabled through
CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.
[snip]
Quoted 6 lines
Quoted 6 lines
disk/part.c +0
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry;
Quoted 8 lines
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 1 line
disk/part.c +1
-0+blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
My initial reaction here is that we should stay conservative and invalidate the cache more often rather than too infrequent. I mean, what's the worst case here, an extra read? A few extra reads? We want to make sure we keep the complexity to functionality ratio right here, if we make the recovery/flashing/factory cases a whole lot better but are leaving 1 second of wall clock time on the table when we've just gained a minute, we're OK.
Quoted 22 lines
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a
Quoted 8 lines
situation where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
That is a good point. But how would you hit this? The problem in ums/dfu was that it was several megabytes, yes? My quick read over the code right now has me thinking this is something measured in kilobytes.
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160330/b6b02cb6/attachment.sig>
PATCH v3 1/3 drivers: block: add block device cache
Quoted 81 lines · Tom Rini
Quoted 25 lines · Stephen Warren
Quoted 24 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (
block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
The 'blkcache' command (enabled through
CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.[snip]
Quoted 6 lines
Quoted 6 lines
disk/part.c +0
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry;Quoted 8 lines
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 1 line
disk/part.c +1
-0+blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
My initial reaction here is that we should stay conservative and invalidate the cache more often rather than too infrequent. I mean, what's the worst case here, an extra read? A few extra reads? We want to make sure we keep the complexity to functionality ratio right here, if we make the recovery/flashing/factory cases a whole lot better but are leaving 1 second of wall clock time on the table when we've just gained a minute, we're OK.
Quoted 22 lines
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a
Quoted 8 lines
situation where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
Quoted 3 lines
That is a good point. But how would you hit this? The problem in ums/dfu was that it was several megabytes, yes? My quick read over the code right now has me thinking this is something measured in kilobytes.
The allocation that failed was a large multi-megabyte buffer. However, the allocations that caused the fragmentation/failure were small (probably tens/hundreds of bytes, but I didn't check).
PATCH v3 1/3 drivers: block: add block device cache
In reply to Tom RiniHi Tom,
Quoted 5 lines · Tom Rini
Quoted 4 lines · Stephen Warren
Quoted 3 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
[snip]
Quoted 13 lines
Quoted 4 lines
Quoted 4 lines
disk/part.c +0
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry;Quoted 8 lines
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 1 line
disk/part.c +1
-0+blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
Quoted 8 lines
My initial reaction here is that we should stay conservative and invalidate the cache more often rather than too infrequent. I mean, what's the worst case here, an extra read? A few extra reads? We want to make sure we keep the complexity to functionality ratio right here, if we make the recovery/flashing/factory cases a whole lot better but are leaving 1 second of wall clock time on the table when we've just gained a minute, we're OK.
I don't think this is called during every command, at least not with mmc.
Quoted 31 lines
Quoted 22 lines
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a
Quoted 8 lines
situation where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
Quoted 4 lines
That is a good point. But how would you hit this? The problem in ums/dfu was that it was several megabytes, yes? My quick read over the code right now has me thinking this is something measured in kilobytes.
36 bytes for the node, plus 512 bytes or 1k for the block data unless tuned through the blkcache command.
And the block data is going to be allocated at the same time.
Regards,
Eric
PATCH v3 1/3 drivers: block: add block device cache
In reply to Stephen WarrenThanks again for the detailed review, Stephen.
Quoted 4 lines · Stephen Warren
Quoted 3 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
<snip>
Quoted 11 lines
cmd/blkcache.c line 23
Quoted 6 lines
cmd/blkcache.c new file +6
-0+ + printf(" hits: %u\n" + " misses: %u\n" + " entries: %u\n" + " max blocks/entry: %u\n" + " max cache entries: %u\n",Nit: I'm not sure why all that command output is indented. Perhaps it made sense before if some kind of title was printed before that text, but I don't think it is any more.
Okay.
Quoted 12 lines
cmd/blkcache.c line 81
Quoted 6 lines
cmd/blkcache.c new file +6
-0+if (c) +return c->cmd (cmdtp, flag, argc, argv); +else +return CMD_RET_USAGE ; + +return 0 ;Nit: I'd prefer to see the error handling immediately follow the function call whose result is being tested, and the non-failure-case code be not indented, i.e.:
Quoted 6 lines
c =
find_cmd_tbl(argv[0], &cmd_blkc_sub[0], ARRAY_SIZE(cmd_blkc_sub)); if (!c)return CMD_RET_USAGE;return
c->cmd(cmdtp, flag, argc, argv);
Okay. As Tom pointed out, I copied this verbatim from i2c.c.
Quoted 6 lines
Quoted 6 lines
disk/part.c +0
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry;
Quoted 9 lines
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 1 line
disk/part.c +1
-0+blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
I'm not sure it does. I traced through the mmc initialization and it's only called when the card itself is initialized.
IOW, at the point where we'd recognize a card swap.
Quoted 22 lines
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a situation
Quoted 9 lines
where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
We're going to allocate a block or set of blocks every time we allocate a new node for the list, so having the list in an array doesn't fix the problem.
While re-working the code, I also thought more about using an array and still don't see how the implementation doesn't get more complex.
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Doing this in an array would require keeping the array sorted (a bad idea) or keeping an access sequence in each node of the array and searching for oldest when invalidation is needed (when the max entries limit is hit).
... unless I'm still missing something and you or Marek have something else in mind.
I could also allocate an array of "free" nodes once the first time around (as a buffer pool), but I don't think this is warranted because of the block allocations I mentioned above.
Quoted 9 lines
include/blk.h struct blk_desc { · line 102
Quoted 6 lines
include/blk.h +4
-0+int blkcache_read + (int iftype,int dev, +lbaint_t start,lbaint_t blkcnt, +unsigned long blksz,void *buffer);Nit: The opening ( and first n arguments should be wrapped onto the same line as the function name.
Aargh. You've now pointed this out several times and I keep missing it.
Will fix in V4.
Regards,
Eric
PATCH v3 1/3 drivers: block: add block device cache
Quoted 22 lines · Eric Nelson
Thanks again for the detailed review, Stephen.
Quoted 10 lines · Stephen Warren
Quoted 9 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
disk/part.c +0
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry;Quoted 8 lines
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 1 line
disk/part.c +1
-0+blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
Quoted 2 lines
I'm not sure it does. I traced through the mmc initialization and it's only called when the card itself is initialized.
I don't believe U-Boot caches the partition structure across user commands. Doesn't each user command (e.g. part list, ls, load, save) first look up the block device, then scan the partition table, then "mount" the filesystem, then perform its action, then throw all that state away? Conversely, "mmc rescan" only happens under explicit user control. Still as I said, the current implementation is probably fine to start with, and at least is safe.
Quoted 31 lines
Quoted 22 lines
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a situation
Quoted 8 lines
where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
Quoted 3 lines
We're going to allocate a block or set of blocks every time we allocate a new node for the list, so having the list in an array doesn't fix the problem.
We could allocate the data storage for the block cache at the top of RAM before relocation, like many other things are allocated, and hence not use malloc() for that.
Quoted 5 lines
While re-working the code, I also thought more about using an array and still don't see how the implementation doesn't get more complex.
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Yes, the MRU logic would make it more complex. Is that particularly useful, i.e. is it an intrinsic part of the speedup?
PATCH v3 1/3 drivers: block: add block device cache
Hi Stephen,
Quoted 26 lines · Stephen Warren
Quoted 22 lines · Eric Nelson
Thanks again for the detailed review, Stephen.
Quoted 10 lines · Stephen Warren
Quoted 9 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
disk/part.c +0
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry;Quoted 8 lines
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 1 line
disk/part.c +1
-0+blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
Quoted 2 lines
I'm not sure it does. I traced through the mmc initialization and it's only called when the card itself is initialized.
Quoted 8 lines
I don't believe U-Boot caches the partition structure across user commands. Doesn't each user command (e.g. part list, ls, load, save) first look up the block device, then scan the partition table, then "mount" the filesystem, then perform its action, then throw all that state away? Conversely, "mmc rescan" only happens under explicit user control. Still as I said, the current implementation is probably fine to start with, and at least is safe.
At least for MMC, this isn't the case. Various filesystem commands operate without calling part_init.
Quoted 31 lines
Quoted 31 lines
Quoted 30 lines
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a situation where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
Quoted 8 lines
Quoted 3 lines
We're going to allocate a block or set of blocks every time we allocate a new node for the list, so having the list in an array doesn't fix the problem.
We could allocate the data storage for the block cache at the top of RAM before relocation, like many other things are allocated, and hence not use
malloc()for that.
Hmmm. We seem to have gone from a discussion about data structures to type of allocation.
I'm interested in seeing how that works. Can you provide hints about what's doing this now?
Quoted 8 lines
Quoted 5 lines
While re-working the code, I also thought more about using an array and still don't see how the implementation doesn't get more complex.
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Yes, the MRU logic would make it more complex. Is that particularly useful, i.e. is it an intrinsic part of the speedup?
It's not a question of speed with small numbers of entries. The code to handle eviction would just be more complex.
Given that the command "blkcache configure 0 0" will discard all cache and since both dfu and ums should properly have the cache disabled, I'd like to proceed as-is with the list and heap approach.
A follow-up change to use another form of allocation is unlikely to change the primary interfaces, though I can't be sure until I understand how these allocation(s) would occur.
I have a V3 prepped that addresses your other comments.
To reiterate the impact of this code, I have use cases where file loading takes minutes when it should take seconds and suspect that others have been seeing the same for quite some time.
Let me know your thoughts.
Regards,
Eric
PATCH v3 1/3 drivers: block: add block device cache
Quoted 38 lines · Eric Nelson
Hi Stephen,
Quoted 26 lines · Stephen Warren
Quoted 22 lines · Eric Nelson
Thanks again for the detailed review, Stephen.
Quoted 10 lines · Stephen Warren
Quoted 9 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
disk/part.c +0
-0@@ -268,6 +268,8 @@ void part_init(struct blk_desc *dev_desc) const int n_ents = ll_entry_count(struct part_driver, part_driver);struct part_driver *entry;Quoted 8 lines
disk/part.c void part_init(struct blk_desc *dev_desc) · line 271
Quoted 1 line
disk/part.c +1
-0+blkcache_invalidate (dev_desc->if_type, dev_desc->devnum);Doesn't this invalidate the cache far too often? I expect that function is called for command the user executes from the command-line, whereas it'd be nice if the cache persisted across commands. I suppose this is a reasonable (and very safe) first implementation though, and saves having to go through each storage provider type and find out the right place to detect media changes.
Quoted 2 lines
I'm not sure it does. I traced through the mmc initialization and it's only called when the card itself is initialized.
Quoted 8 lines
I don't believe U-Boot caches the partition structure across user commands. Doesn't each user command (e.g. part list, ls, load, save) first look up the block device, then scan the partition table, then "mount" the filesystem, then perform its action, then throw all that state away? Conversely, "mmc rescan" only happens under explicit user control. Still as I said, the current implementation is probably fine to start with, and at least is safe.
Quoted 2 lines
At least for MMC, this isn't the case. Various filesystem commands operate without calling
part_init.
Interesting; that step is indeed only performed when the device is first probed for MMC and USB.
Quoted 39 lines
Quoted 31 lines
Quoted 31 lines
Quoted 30 lines
Quoted 8 lines
drivers/block/blkcache.c +6
-0+struct block_cache_node { +struct list_head lh; +int iftype; +int devnum; +lbaint_t start; +lbaint_t blkcnt;drivers/block/blkcache.c line 30
Quoted 10 lines
drivers/block/blkcache.c +10
-0+unsigned long blksz; +char *cache; +}; + +static LIST_HEAD (block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry =2 , + .max_entries =32 +};Now is a good time to mention another reason why I don't like using a dynamically allocated linked list for this: Memory fragmentation. By dynamically allocating the cache, we could easily run into a situation where the user runs a command that allocates memory and also adds to the block cache, then most of that memory gets freed when U-Boot returns to the command prompt, then the user runs the command again but it fails since it can't allocate the memory due to fragmentation of the heap. This is a real problem I've seen e.g. with the "ums" and "dfu" commands, since they might initialize the USB controller the first time they're run, which allocates some new memory. Statically allocation would avoid this.
Quoted 7 lines
Quoted 3 lines
We're going to allocate a block or set of blocks every time we allocate a new node for the list, so having the list in an array doesn't fix the problem.
We could allocate the data storage for the block cache at the top of RAM before relocation, like many other things are allocated, and hence not use
malloc()for that.
Quoted 5 lines
Hmmm. We seem to have gone from a discussion about data structures to type of allocation.
I'm interested in seeing how that works. Can you provide hints about what's doing this now?
Something like common/board_f.c:reserve_mmu() and many other functions there. relocaddr starts at approximately the top of RAM, continually gets adjusted down as many static allocations are reserved, and eventually becomes the address that U-Boot is relocated to. Simply adding another entry into init_sequence_f[] for the disk cache might work.
Quoted 11 lines
Quoted 8 lines
Quoted 5 lines
While re-working the code, I also thought more about using an array and still don't see how the implementation doesn't get more complex.
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Yes, the MRU logic would make it more complex. Is that particularly useful, i.e. is it an intrinsic part of the speedup?
It's not a question of speed with small numbers of entries. The code to handle eviction would just be more complex.
My thought was that if the eviction algorithm wasn't important (i.e. most of the speedup comes from have some (any) kind of cache, but the eviction algorithm makes little difference to the gain from having the cache), we could just drop MRU completely. If that's not possible, then indeed a list would make implementing MRU easier.
You could still do a list with a statically allocated set of list nodes, especially since the length of the list is bounded.
Quoted 3 lines
Given that the command "blkcache configure 0 0" will discard all cache and since both dfu and ums should properly have the cache disabled, I'd like to proceed as-is with the list and heap approach.
I don't understand "since both dfu and ums should properly have the cache disabled"; I didn't see anything that did that. Perhaps you're referring to the fact that writes invalidate the cache?
Eventually it seems better to keep the cache enabled for at least DFU to a filesystem (rather than raw block device) since presumably parsing the directory structure to write to a file for DFU would benefit from the cache just like anything else.
PATCH v3 1/3 drivers: block: add block device cache
Hi Stephen,
Quoted 5 lines · Stephen Warren
Quoted 4 lines · Eric Nelson
Quoted 3 lines · Stephen Warren
Quoted 2 lines · Eric Nelson
Quoted 1 line · Stephen Warren
On 03/28/2016 11:05 AM, Eric Nelson wrote:
<snip>
Quoted 8 lines
Quoted 8 lines
Quoted 4 lines
We could allocate the data storage for the block cache at the top of RAM before relocation, like many other things are allocated, and hence not use
malloc()for that.Hmmm. We seem to have gone from a discussion about data structures to type of allocation.
Quoted 9 lines
Quoted 2 lines
I'm interested in seeing how that works. Can you provide hints about what's doing this now?
Something like common/board_f.c:reserve_mmu() and many other functions there. relocaddr starts at approximately the top of RAM, continually gets adjusted down as many static allocations are reserved, and eventually becomes the address that U-Boot is relocated to. Simply adding another entry into init_sequence_f[] for the disk cache might work.
Thanks for the pointer. I'll review that when time permits.
This would remove the opportunity to re-configure the cache though, right?
I'm not sure whether how important this feature is, and I think only time and use will tell.
I'd prefer to keep that ability at least for a cycle or two so that I and others can test.
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
Quoted 5 lines
While re-working the code, I also thought more about using an array and still don't see how the implementation doesn't get more complex.
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Yes, the MRU logic would make it more complex. Is that particularly useful, i.e. is it an intrinsic part of the speedup?
Quoted 9 lines
Quoted 2 lines
It's not a question of speed with small numbers of entries. The code to handle eviction would just be more complex.
My thought was that if the eviction algorithm wasn't important (i.e. most of the speedup comes from have some (any) kind of cache, but the eviction algorithm makes little difference to the gain from having the cache), we could just drop MRU completely. If that's not possible, then indeed a list would make implementing MRU easier.
How would we decide which block to discard? I haven't traced enough to know what algorithm(s) might be best, but I can say that there's a preponderance of repeated accesses to the last-accessed block, especially in ext4.
Quoted 3 lines
You could still do a list with a statically allocated set of list nodes, especially since the length of the list is bounded.
Sure. A pooled allocator (pool of free nodes) works well with array-based allocation.
Having a fixed upper limit on the number of blocks would require additional checking unless we just sized it for (max entries * max blocks/entry).
Quoted 8 lines
Quoted 3 lines
Given that the command "blkcache configure 0 0" will discard all cache and since both dfu and ums should properly have the cache disabled, I'd like to proceed as-is with the list and heap approach.
I don't understand "since both dfu and ums should properly have the cache disabled"; I didn't see anything that did that. Perhaps you're referring to the fact that writes invalidate the cache?
Yes, but also that the host will cache blocks in the ums case, so having the cache enabled will only slow things down slightly by lots of memcpy's to cached blocks that won't be helpful.
I think I was a bit flippant by including dfu in this statement, since I haven't used it to access anything except SPI-NOR.
Quoted 4 lines
Eventually it seems better to keep the cache enabled for at least DFU to a filesystem (rather than raw block device) since presumably parsing the directory structure to write to a file for DFU would benefit from the cache just like anything else.
I'm not in a position to comment about dfu.
Regards,
Eric
PATCH v3 1/3 drivers: block: add block device cache
Quoted 90 lines · Eric Nelson
Hi Stephen,
Quoted 5 lines · Stephen Warren
Quoted 4 lines · Eric Nelson
Quoted 3 lines · Stephen Warren
Quoted 2 lines · Eric Nelson
Quoted 1 line · Stephen Warren
On 03/28/2016 11:05 AM, Eric Nelson wrote:
<snip>
Quoted 8 lines
Quoted 8 lines
Quoted 4 lines
We could allocate the data storage for the block cache at the top of RAM before relocation, like many other things are allocated, and hence not use
malloc()for that.Hmmm. We seem to have gone from a discussion about data structures to type of allocation.
Quoted 9 lines
Quoted 2 lines
I'm interested in seeing how that works. Can you provide hints about what's doing this now?
Something like common/board_f.c:reserve_mmu() and many other functions there. relocaddr starts at approximately the top of RAM, continually gets adjusted down as many static allocations are reserved, and eventually becomes the address that U-Boot is relocated to. Simply adding another entry into init_sequence_f[] for the disk cache might work.
Thanks for the pointer. I'll review that when time permits.
This would remove the opportunity to re-configure the cache though, right?
I'm not sure whether how important this feature is, and I think only time and use will tell.
I'd prefer to keep that ability at least for a cycle or two so that I and others can test.
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
Quoted 5 lines
While re-working the code, I also thought more about using an array and still don't see how the implementation doesn't get more complex.
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Yes, the MRU logic would make it more complex. Is that particularly useful, i.e. is it an intrinsic part of the speedup?
Quoted 9 lines
Quoted 2 lines
It's not a question of speed with small numbers of entries. The code to handle eviction would just be more complex.
My thought was that if the eviction algorithm wasn't important (i.e. most of the speedup comes from have some (any) kind of cache, but the eviction algorithm makes little difference to the gain from having the cache), we could just drop MRU completely. If that's not possible, then indeed a list would make implementing MRU easier.
How would we decide which block to discard? I haven't traced enough to know what algorithm(s) might be best, but I can say that there's a preponderance of repeated accesses to the last-accessed block, especially in ext4.
Quoted 3 lines
You could still do a list with a statically allocated set of list nodes, especially since the length of the list is bounded.
Sure. A pooled allocator (pool of free nodes) works well with array-based allocation.
Having a fixed upper limit on the number of blocks would require additional checking unless we just sized it for (max entries * max blocks/entry).
Quoted 8 lines
Quoted 3 lines
Given that the command "blkcache configure 0 0" will discard all cache and since both dfu and ums should properly have the cache disabled, I'd like to proceed as-is with the list and heap approach.
I don't understand "since both dfu and ums should properly have the cache disabled"; I didn't see anything that did that. Perhaps you're referring to the fact that writes invalidate the cache?
Yes, but also that the host will cache blocks in the ums case, so having the cache enabled will only slow things down slightly by lots of memcpy's to cached blocks that won't be helpful.
I think I was a bit flippant by including dfu in this statement, since I haven't used it to access anything except SPI-NOR.
Quoted 6 lines
Quoted 4 lines
Eventually it seems better to keep the cache enabled for at least DFU to a filesystem (rather than raw block device) since presumably parsing the directory structure to write to a file for DFU would benefit from the cache just like anything else.
I'm not in a position to comment about dfu.
For the record, I think this discussion is good and important, but not a blocker for getting the initial functionality in.
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160401/ee5052f1/attachment.sig>
PATCH v3 1/3 drivers: block: add block device cache
Quoted 9 lines · Tom Rini
Quoted 8 lines · Eric Nelson
Hi Stephen,
Quoted 5 lines · Stephen Warren
Quoted 4 lines · Eric Nelson
Quoted 3 lines · Stephen Warren
Quoted 2 lines · Eric Nelson
Quoted 1 line · Stephen Warren
On 03/28/2016 11:05 AM, Eric Nelson wrote:
<snip>
Quoted 12 lines
Quoted 12 lines
Yes, but also that the host will cache blocks in the ums case, so having the cache enabled will only slow things down slightly by lots of memcpy's to cached blocks that won't be helpful.
I think I was a bit flippant by including dfu in this statement, since I haven't used it to access anything except SPI-NOR.
Quoted 4 lines
Eventually it seems better to keep the cache enabled for at least DFU to a filesystem (rather than raw block device) since presumably parsing the directory structure to write to a file for DFU would benefit from the cache just like anything else.
Quoted 5 lines
Quoted 1 line
I'm not in a position to comment about dfu.
For the record, I think this discussion is good and important, but not a blocker for getting the initial functionality in.
I wholeheartedly agree.
I'm learning a lot and the code's better because of Stephen's feedback.
Regards,
Eric
PATCH v3 1/3 drivers: block: add block device cache
In reply to Eric NelsonQuoted 29 lines · Eric Nelson
Hi Stephen,
Quoted 5 lines · Stephen Warren
Quoted 4 lines · Eric Nelson
Quoted 3 lines · Stephen Warren
Quoted 2 lines · Eric Nelson
Quoted 1 line · Stephen Warren
On 03/28/2016 11:05 AM, Eric Nelson wrote:
<snip>
Quoted 8 lines
Quoted 8 lines
Quoted 4 lines
We could allocate the data storage for the block cache at the top of RAM before relocation, like many other things are allocated, and hence not use
malloc()for that.Hmmm. We seem to have gone from a discussion about data structures to type of allocation.
Quoted 9 lines
Quoted 2 lines
I'm interested in seeing how that works. Can you provide hints about what's doing this now?
Something like common/board_f.c:reserve_mmu() and many other functions there. relocaddr starts at approximately the top of RAM, continually gets adjusted down as many static allocations are reserved, and eventually becomes the address that U-Boot is relocated to. Simply adding another entry into init_sequence_f[] for the disk cache might work.
Quoted 3 lines
Thanks for the pointer. I'll review that when time permits.
This would remove the opportunity to re-configure the cache though, right?
Well, it would make it impossible to use less RAM. One could use more by having a mix of the initial static allocation plus some additional dynamic allocation, but that might get a bit painful to manage.
It might be interesting to use the MMU more and allow de-fragmentation of VA space. That is, assuming there's much more VA space than RAM, such as is true on current 64-bit architectures. Then I wouldn't dislike dynamic allocation so much:-)
Quoted 25 lines
I'm not sure whether how important this feature is, and I think only time and use will tell.
I'd prefer to keep that ability at least for a cycle or two so that I and others can test.
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
Quoted 5 lines
While re-working the code, I also thought more about using an array and still don't see how the implementation doesn't get more complex.
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Yes, the MRU logic would make it more complex. Is that particularly useful, i.e. is it an intrinsic part of the speedup?
Quoted 9 lines
Quoted 2 lines
It's not a question of speed with small numbers of entries. The code to handle eviction would just be more complex.
My thought was that if the eviction algorithm wasn't important (i.e. most of the speedup comes from have some (any) kind of cache, but the eviction algorithm makes little difference to the gain from having the cache), we could just drop MRU completely. If that's not possible, then indeed a list would make implementing MRU easier.
Quoted 4 lines
How would we decide which block to discard? I haven't traced enough to know what algorithm(s) might be best, but I can say that there's a preponderance of repeated accesses to the last-accessed block, especially in ext4.
Perhaps just keep an index into the array, use that index any time something is written to the cache, and then increment it each time. Probably not anywhere near as optimal as MRU/LRU though.
PATCH v3 1/3 drivers: block: add block device cache
Hi Stephen,
Quoted 30 lines · Stephen Warren
Quoted 29 lines · Eric Nelson
Quoted 5 lines · Stephen Warren
Quoted 4 lines · Eric Nelson
Quoted 3 lines · Stephen Warren
Quoted 2 lines · Eric Nelson
Quoted 1 line · Stephen Warren
On 03/28/2016 11:05 AM, Eric Nelson wrote:
<snip>
Quoted 19 lines
Quoted 11 lines
Quoted 5 lines
We could allocate the data storage for the block cache at the top of RAM
before relocation, like many other things are allocated, and hence not usemalloc()for that.Hmmm. We seem to have gone from a discussion about data structures to type of allocation.
I'm interested in seeing how that works. Can you provide hints about what's doing this now?
Something like common/board_f.c:reserve_mmu() and many other functions there. relocaddr starts at approximately the top of RAM, continually gets adjusted down as many static allocations are reserved, and eventually becomes the address that U-Boot is relocated to. Simply adding another entry into init_sequence_f[] for the disk cache might work.
Quoted 9 lines
Quoted 4 lines
Thanks for the pointer. I'll review that when time permits.
This would remove the opportunity to re-configure the cache though, right?
Well, it would make it impossible to use less RAM. One could use more by having a mix of the initial static allocation plus some additional dynamic allocation, but that might get a bit painful to manage.
This might not be too bad though. Even if we allocated 4x the current defaults, we're only at ~64k.
Quoted 5 lines
It might be interesting to use the MMU more and allow de-fragmentation of VA space. That is, assuming there's much more VA space than RAM, such as is true on current 64-bit architectures. Then I wouldn't dislike dynamic allocation so much:-)
That's interesting, but probably more invasive than this patch set.
Quoted 26 lines
Quoted 26 lines
I'm not sure whether how important this feature is, and I think only time and use will tell.
I'd prefer to keep that ability at least for a cycle or two so that I and others can test.
Quoted 19 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
While re-working the code, I also thought more about using an array and
still don't see how the implementation doesn't get more complex.Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
The key bit is that the list is implemented in MRU order so invalidating the oldest is trivial.
Yes, the MRU logic would make it more complex. Is that particularly useful, i.e. is it an intrinsic part of the speedup?
It's not a question of speed with small numbers of entries. The code to handle eviction would just be more complex.
My thought was that if the eviction algorithm wasn't important (i.e. most of the speedup comes from have some (any) kind of cache, but the eviction algorithm makes little difference to the gain from having the cache), we could just drop MRU completely. If that's not possible, then indeed a list would make implementing MRU easier.
Quoted 8 lines
Quoted 4 lines
How would we decide which block to discard? I haven't traced enough to know what algorithm(s) might be best, but I can say that there's a preponderance of repeated accesses to the last-accessed block, especially in ext4.
Perhaps just keep an index into the array, use that index any time something is written to the cache, and then increment it each time. Probably not anywhere near as optimal as MRU/LRU though.
I see that Tom just applied V3, so I'd be interested in seeing patches on top of that.
Regards,
Eric
PATCH v3 1/3 drivers: block: add block device cache
In reply to Eric NelsonQuoted 21 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (
block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
Quoted 5 lines
The 'blkcache' command (enabled through
CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.Signed-off-by Eric Nelson <[email protected]>
Applied to u-boot/master, thanks!
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160401/58b5808e/attachment.sig>
PATCH v3 1/3 drivers: block: add block device cache
Hi Tom,
Quoted 23 lines · Tom Rini
Quoted 21 lines · Eric Nelson
Add a block device cache to speed up repeated reads of block devices by various filesystems.
This small amount of cache can dramatically speed up filesystem operations by skipping repeated reads of common areas of a block device (typically directory structures).
This has shown to have some benefit on FAT filesystem operations of loading a kernel and RAM disk, but more dramatic benefits on ext4 filesystems when the kernel and/or RAM disk are spread across multiple extent header structures as described in commit fc0fc50.
The cache is implemented through a minimal list (
block_cache) maintained in most-recently-used order and count of the current number of entries (cache_count). It uses a maximum block count setting to prevent copies of large block reads and an upper bound on the number of cached areas.The maximum number of entries in the cache defaults to 32 and the maximum number of blocks per cache entry has a default of 2, which has shown to produce the best results on testing of ext4 and FAT filesystems.
Quoted 8 lines
Quoted 5 lines
The 'blkcache' command (enabled through
CONFIG_CMD_BLOCK_CACHE) allows changing these values and can be used to tune for a particular filesystem layout.Signed-off-by Eric Nelson <[email protected]>Applied to u-boot/master, thanks!
Whoops. I have a couple of minor updates from Stephen's last review that I'll forward shortly.
cover minor blkcache updates
This set of updates addresses a number of small coding style issues caught by Stephen Warren's review of the block cache patch set.
Eric Nelson (3):
cmd: blkcache: remove indentation from output of 'show'
cmd: blkcache: simplify sub-command handling
drivers: block: formatting: fix placement of function parameters
cmd/blkcache.c | 16 +++++++--------- include/blk.h | 34 ++++++++++++++-------------------- 2 files changed, 21 insertions(+), 29 deletions(-)
Signature · 1 line
2.6.2
PATCH v2 1/3 cmd: blkcache: remove indentation from output of 'show'
--- cmd/blkcache.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
cmd/blkcache.c +5 -5
@@ -16,11 +16,11 @@ static int blkc_show(cmd_tbl_t *cmdtp, int flag,struct block_cache_stats stats;blkcache_stats (&stats); -printf (" hits: %u\n" -" misses: %u\n" -" entries: %u\n" -" max blocks/entry: %u\n" -" max cache entries: %u\n" , +printf ("hits: %u\n" +"misses: %u\n" +"entries: %u\n" +"max blocks/entry: %u\n" +"max cache entries: %u\n" , stats.hits, stats.misses, stats.entries, stats.max_blocks_per_entry, stats.max_entries);return 0 ;
Signature · 1 line
2.6.2
PATCH 1/3 cmd: blkcache: remove indentation from output of 'show'
Quoted 1 line · Eric Nelson
Signed-off-by Eric Nelson <[email protected]>
Applied to u-boot/master, thanks!
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 819 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160411/8ce2c13a/attachment.sig>
PATCH v2 2/3 cmd: blkcache: simplify sub-command handling
In reply to Eric Nelson--- cmd/blkcache.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
cmd/blkcache.c +2 -4
@@ -73,12 +73,10 @@ static int do_blkcache(cmd_tbl_t *cmdtp, int flag, c =find_cmd_tbl (argv[0 ], &cmd_blkc_sub[0 ],ARRAY_SIZE (cmd_blkc_sub)); -if (c) -return c->cmd (cmdtp, flag, argc, argv); -else +if (!c)return CMD_RET_USAGE ; -return 0 ; +return c->cmd (cmdtp, flag, argc, argv); } U_BOOT_CMD(
Signature · 1 line
2.6.2
PATCH 2/3 cmd: blkcache: simplify sub-command handling
On 04/02/2016 08:37 AM, Eric Nelson wrote:
The series,
One nit below:
cmd/blkcache.c static int do_blkcache(cmd_tbl_t *cmdtp, int flag, · line 76
Quoted 7 lines
cmd/blkcache.c +0
-1@@ -73,12 +73,10 @@ static int do_blkcache(cmd_tbl_t *cmdtp, int flag, c =find_cmd_tbl (argv[0 ], &cmd_blkc_sub[0 ],ARRAY_SIZE (cmd_blkc_sub)); -if (c)
I'd suggest removing that blank line too.
PATCH 2/3 cmd: blkcache: simplify sub-command handling
In reply to Eric NelsonQuoted 2 lines · Eric Nelson
Signed-off-by Eric Nelson <[email protected]>Acked-by Stephen Warren <[email protected]>
Applied to u-boot/master, thanks!
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 819 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160411/2b4000a9/attachment.sig>
PATCH v2 3/3 drivers: block: fix placement of parameters
In reply to Eric Nelson--- include/blk.h | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-)
include/blk.h +14 -20
@@ -96,10 +96,9 @@ struct blk_desc {* * @return - '1' if block returned from cache, '0' otherwise. */ -int blkcache_read - (int iftype,int dev, -lbaint_t start,lbaint_t blkcnt, -unsigned long blksz,void *buffer); +int blkcache_read (int iftype,int dev, +lbaint_t start,lbaint_t blkcnt, +unsigned long blksz,void *buffer);/** * blkcache_fill() - make data read from a block device available
@@ -113,10 +112,9 @@ int blkcache_read* @param buf - buffer containing data to cache * */ -void blkcache_fill - (int iftype,int dev, -lbaint_t start,lbaint_t blkcnt, -unsigned long blksz,void const *buffer); +void blkcache_fill (int iftype,int dev, +lbaint_t start,lbaint_t blkcnt, +unsigned long blksz,void const *buffer);/** * blkcache_invalidate() - discard the cache for a set of blocks
@@ -125,8 +123,7 @@ void blkcache_fill* @param iftype - IF_TYPE_x for type of device * @param dev - device index of particular type */ -void blkcache_invalidate - (int iftype,int dev); +void blkcache_invalidate (int iftype,int dev);/** * blkcache_configure() - configure block cache
@@ -156,21 +153,18 @@ void blkcache_stats(struct block_cache_stats *stats);#else -static inline int blkcache_read - (int iftype,int dev, -lbaint_t start,lbaint_t blkcnt, -unsigned long blksz,void *buffer) +static inline int blkcache_read (int iftype,int dev, +lbaint_t start,lbaint_t blkcnt, +unsigned long blksz,void *buffer) {return 0 ; } -static inline void blkcache_fill - (int iftype,int dev, -lbaint_t start,lbaint_t blkcnt, -unsigned long blksz,void const *buffer) {} +static inline void blkcache_fill (int iftype,int dev, +lbaint_t start,lbaint_t blkcnt, +unsigned long blksz,void const *buffer) {} -static inline void blkcache_invalidate - (int iftype,int dev) {} +static inline void blkcache_invalidate (int iftype,int dev) {}#endif
Signature · 1 line
2.6.2
PATCH 3/3 drivers: block: fix placement of parameters
Quoted 1 line · Eric Nelson
Signed-off-by Eric Nelson <[email protected]>
Applied to u-boot/master, thanks!
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 819 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160411/0bf17497/attachment.sig>
PATCH v2 2/3 mmc: use block layer in mmc command
In reply to Eric NelsonCall blk_dread, blk_dwrite, blk_derase to ensure that the block cache is used if enabled and to remove build breakage when CONFIG_BLK is enabled.
--- cmd/mmc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
cmd/mmc.c +4 -3
@@ -150,6 +150,7 @@ static struct mmc *init_mmc_device(int dev, bool force_init)printf ("no mmc device at slot %x\n" , dev);return NULL ; } +if (force_init) mmc->has_init =0 ;if (mmc_init (mmc))
@@ -345,7 +346,7 @@ static int do_mmc_read(cmd_tbl_t *cmdtp, int flag,printf ("\nMMC read: dev # %d, block # %d, count %d ... " , curr_device, blk, cnt); - n = mmc->block_dev.block_read (&mmc->block_dev, blk, cnt, addr); + n =blk_dread (&mmc->block_dev, blk, cnt, addr);/* flush cache after read */ flush_cache ((ulong )addr, cnt *512 );/* FIXME */ printf ("%d blocks read: %s\n" , n, (n == cnt) ?"OK" :"ERROR" );
@@ -377,7 +378,7 @@ static int do_mmc_write(cmd_tbl_t *cmdtp, int flag,printf ("Error: card is write protected!\n" );return CMD_RET_FAILURE ; } - n = mmc->block_dev.block_write (&mmc->block_dev, blk, cnt, addr); + n =blk_dwrite (&mmc->block_dev, blk, cnt, addr);printf ("%d blocks written: %s\n" , n, (n == cnt) ?"OK" :"ERROR" );return (n == cnt) ?CMD_RET_SUCCESS :CMD_RET_FAILURE ;
@@ -405,7 +406,7 @@ static int do_mmc_erase(cmd_tbl_t *cmdtp, int flag,printf ("Error: card is write protected!\n" );return CMD_RET_FAILURE ; } - n = mmc->block_dev.block_erase (&mmc->block_dev, blk, cnt); + n =blk_derase (&mmc->block_dev, blk, cnt);printf ("%d blocks erased: %s\n" , n, (n == cnt) ?"OK" :"ERROR" );return (n == cnt) ?CMD_RET_SUCCESS :CMD_RET_FAILURE ;
Signature · 1 line
2.6.2
PATCH 2/3 mmc: use block layer in mmc command
Quoted 4 lines · Eric Nelson
Call
blk_dread,blk_dwrite,blk_deraseto ensure that the block cache is used if enabled and to remove build breakage whenCONFIG_BLKis enabled.Signed-off-by Eric Nelson <[email protected]>
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160328/a54e74a2/attachment.sig>
PATCH 2/3 mmc: use block layer in mmc command
In reply to Eric NelsonQuoted 5 lines · Eric Nelson
Call
blk_dread,blk_dwrite,blk_deraseto ensure that the block cache is used if enabled and to remove build breakage whenCONFIG_BLKis enabled.Signed-off-by Eric Nelson <[email protected]>Reviewed-by Tom Rini <[email protected]>
Applied to u-boot/master, thanks!
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160401/fafb03bf/attachment.sig>
PATCH v2 3/3 sata: use block layer for sata command
In reply to Eric NelsonCall blk_dread, blk_dwrite, blk_derase to ensure that the block cache is used if enabled and to remove build breakage when CONFIG_BLK is enabled.
--- cmd/sata.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
cmd/sata.c +4 -2
@@ -183,7 +183,8 @@ static int do_sata(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])printf ("\nSATA read: device %d block # %ld, count %ld ... " , sata_curr_device, blk, cnt); - n =sata_read (sata_curr_device, blk, cnt, (u32 *)addr); + n =blk_dread (&sata_dev_desc[sata_curr_device], + blk, cnt, (u32 *)addr);/* flush cache after read */ flush_cache (addr, cnt * sata_dev_desc[sata_curr_device].blksz);
@@ -201,7 +202,8 @@ static int do_sata(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])printf ("\nSATA write: device %d block # %ld, count %ld ... " , sata_curr_device, blk, cnt); - n =sata_write (sata_curr_device, blk, cnt, (u32 *)addr); + n =blk_dwrite (&sata_dev_desc[sata_curr_device], + blk, cnt, (u32 *)addr);printf ("%ld blocks written: %s\n" , n, (n == cnt) ?"OK" :"ERROR" );
Signature · 1 line
2.6.2
PATCH 3/3 sata: use block layer for sata command
Quoted 4 lines · Eric Nelson
Call
blk_dread,blk_dwrite,blk_deraseto ensure that the block cache is used if enabled and to remove build breakage whenCONFIG_BLKis enabled.Signed-off-by Eric Nelson <[email protected]>
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160328/a14cc420/attachment.sig>
PATCH 3/3 sata: use block layer for sata command
In reply to Eric NelsonQuoted 5 lines · Eric Nelson
Call
blk_dread,blk_dwrite,blk_deraseto ensure that the block cache is used if enabled and to remove build breakage whenCONFIG_BLKis enabled.Signed-off-by Eric Nelson <[email protected]>Reviewed-by Tom Rini <[email protected]>
Applied to u-boot/master, thanks!
Signature · 8 lines
Tom -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 836 bytes Desc: Digital signature URL: <http://lists.denx.de/pipermail/u-boot/attachments/20160401/15e564f7/attachment.sig>