mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 00:32:38 +01:00 
			
		
		
		
	Removed recursion from lfs_dir_traverse
lfs_dir_traverse is a bit unpleasant in that it is inherently a recursive function, but without a strict bound of 4 calls (commit -> filter -> move -> filter), and efforts to unroll the recursion comes at a signification code cost. It turns out the best solution I've found so far is to simple create an explicit stack with an explicit bound of 4 calls (or more accurately, 3 pushed frames). --- This actually highlights one of the bigger flaws in littlefs right now, which is that this function, lfs_dir_traverse, takes O(n^2) disk reads to traverse. Note that LFS_FROM_MOVE can only occur once per commit, which is why this code is O(n^2) and not O(n^4).
This commit is contained in:
		
							
								
								
									
										176
									
								
								lfs.c
									
									
									
									
									
								
							
							
						
						
									
										176
									
								
								lfs.c
									
									
									
									
									
								
							| @@ -737,6 +737,7 @@ static int lfs_dir_traverse_filter(void *p, | |||||||
|             (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( |             (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( | ||||||
|                 LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | |                 LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | | ||||||
|                     (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { |                     (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { | ||||||
|  |         *filtertag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0); | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -751,17 +752,54 @@ static int lfs_dir_traverse_filter(void *p, | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifndef LFS_READONLY | #ifndef LFS_READONLY | ||||||
|  | // maximum recursive depth of lfs_dir_traverse, the deepest call: | ||||||
|  | // | ||||||
|  | // traverse with commit | ||||||
|  | // '-> traverse with filter | ||||||
|  | //     '-> traverse with move | ||||||
|  | //         ' traverse with filter | ||||||
|  | // | ||||||
|  | #define LFS_DIR_TRAVERSE_DEPTH 4 | ||||||
|  |  | ||||||
|  | struct lfs_dir_traverse { | ||||||
|  |     const lfs_mdir_t *dir; | ||||||
|  |     lfs_off_t off; | ||||||
|  |     lfs_tag_t ptag; | ||||||
|  |     const struct lfs_mattr *attrs; | ||||||
|  |     int attrcount; | ||||||
|  |  | ||||||
|  |     lfs_tag_t tmask; | ||||||
|  |     lfs_tag_t ttag; | ||||||
|  |     uint16_t begin; | ||||||
|  |     uint16_t end; | ||||||
|  |     int16_t diff; | ||||||
|  |  | ||||||
|  |     int (*cb)(void *data, lfs_tag_t tag, const void *buffer); | ||||||
|  |     void *data; | ||||||
|  |  | ||||||
|  |     lfs_tag_t tag; | ||||||
|  |     const void *buffer; | ||||||
|  |     struct lfs_diskoff disk; | ||||||
|  | }; | ||||||
|  |  | ||||||
| static int lfs_dir_traverse(lfs_t *lfs, | static int lfs_dir_traverse(lfs_t *lfs, | ||||||
|         const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, |         const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, | ||||||
|         const struct lfs_mattr *attrs, int attrcount, |         const struct lfs_mattr *attrs, int attrcount, | ||||||
|         lfs_tag_t tmask, lfs_tag_t ttag, |         lfs_tag_t tmask, lfs_tag_t ttag, | ||||||
|         uint16_t begin, uint16_t end, int16_t diff, |         uint16_t begin, uint16_t end, int16_t diff, | ||||||
|         int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { |         int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { | ||||||
|  |     // This function in inherently recursive, but bounded. To allow tool-based | ||||||
|  |     // analysis without unnecessary code-cost we use an explicit stack | ||||||
|  |     struct lfs_dir_traverse stack[LFS_DIR_TRAVERSE_DEPTH-1]; | ||||||
|  |     unsigned sp = 0; | ||||||
|  |     int res; | ||||||
|  |  | ||||||
|     // iterate over directory and attrs |     // iterate over directory and attrs | ||||||
|     while (true) { |  | ||||||
|     lfs_tag_t tag; |     lfs_tag_t tag; | ||||||
|     const void *buffer; |     const void *buffer; | ||||||
|     struct lfs_diskoff disk; |     struct lfs_diskoff disk; | ||||||
|  |     while (true) { | ||||||
|  |         { | ||||||
|             if (off+lfs_tag_dsize(ptag) < dir->off) { |             if (off+lfs_tag_dsize(ptag) < dir->off) { | ||||||
|                 off += lfs_tag_dsize(ptag); |                 off += lfs_tag_dsize(ptag); | ||||||
|                 int err = lfs_bd_read(lfs, |                 int err = lfs_bd_read(lfs, | ||||||
| @@ -782,67 +820,145 @@ static int lfs_dir_traverse(lfs_t *lfs, | |||||||
|                 attrs += 1; |                 attrs += 1; | ||||||
|                 attrcount -= 1; |                 attrcount -= 1; | ||||||
|             } else { |             } else { | ||||||
|             return 0; |                 // finished traversal, pop from stack? | ||||||
|  |                 res = 0; | ||||||
|  |                 break; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // do we need to filter? | ||||||
|             lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); |             lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); | ||||||
|             if ((mask & tmask & tag) != (mask & tmask & ttag)) { |             if ((mask & tmask & tag) != (mask & tmask & ttag)) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         // do we need to filter? inlining the filtering logic here allows |  | ||||||
|         // for some minor optimizations |  | ||||||
|             if (lfs_tag_id(tmask) != 0) { |             if (lfs_tag_id(tmask) != 0) { | ||||||
|             // scan for duplicates and update tag based on creates/deletes |                 LFS_ASSERT(sp < LFS_DIR_TRAVERSE_DEPTH); | ||||||
|             int filter = lfs_dir_traverse(lfs, |                 // recurse, scan for duplicates, and update tag based on | ||||||
|                     dir, off, ptag, attrs, attrcount, |                 // creates/deletes | ||||||
|                     0, 0, 0, 0, 0, |                 stack[sp] = (struct lfs_dir_traverse){ | ||||||
|                     lfs_dir_traverse_filter, &tag); |                     .dir        = dir, | ||||||
|             if (filter < 0) { |                     .off        = off, | ||||||
|                 return filter; |                     .ptag       = ptag, | ||||||
|             } |                     .attrs      = attrs, | ||||||
|  |                     .attrcount  = attrcount, | ||||||
|  |                     .tmask      = tmask, | ||||||
|  |                     .ttag       = ttag, | ||||||
|  |                     .begin      = begin, | ||||||
|  |                     .end        = end, | ||||||
|  |                     .diff       = diff, | ||||||
|  |                     .cb         = cb, | ||||||
|  |                     .data       = data, | ||||||
|  |                     .tag        = tag, | ||||||
|  |                     .buffer     = buffer, | ||||||
|  |                     .disk       = disk, | ||||||
|  |                 }; | ||||||
|  |                 sp += 1; | ||||||
|  |  | ||||||
|             if (filter) { |                 dir = dir; | ||||||
|  |                 off = off; | ||||||
|  |                 ptag = ptag; | ||||||
|  |                 attrs = attrs; | ||||||
|  |                 attrcount = attrcount; | ||||||
|  |                 tmask = 0; | ||||||
|  |                 ttag = 0; | ||||||
|  |                 begin = 0; | ||||||
|  |                 end = 0; | ||||||
|  |                 diff = 0; | ||||||
|  |                 cb = lfs_dir_traverse_filter; | ||||||
|  |                 data = &stack[sp-1].tag; | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | popped: | ||||||
|         // in filter range? |         // in filter range? | ||||||
|             if (!(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { |         if (lfs_tag_id(tmask) != 0 && | ||||||
|  |                 !(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // handle special cases for mcu-side operations |         // handle special cases for mcu-side operations | ||||||
|         if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { |         if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { | ||||||
|             // do nothing |             // do nothing | ||||||
|         } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { |         } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { | ||||||
|  |             // recurse into move | ||||||
|  |             stack[sp] = (struct lfs_dir_traverse){ | ||||||
|  |                 .dir        = dir, | ||||||
|  |                 .off        = off, | ||||||
|  |                 .ptag       = ptag, | ||||||
|  |                 .attrs      = attrs, | ||||||
|  |                 .attrcount  = attrcount, | ||||||
|  |                 .tmask      = tmask, | ||||||
|  |                 .ttag       = ttag, | ||||||
|  |                 .begin      = begin, | ||||||
|  |                 .end        = end, | ||||||
|  |                 .diff       = diff, | ||||||
|  |                 .cb         = cb, | ||||||
|  |                 .data       = data, | ||||||
|  |                 .tag        = LFS_MKTAG(LFS_FROM_NOOP, 0, 0), | ||||||
|  |             }; | ||||||
|  |             sp += 1; | ||||||
|  |  | ||||||
|             uint16_t fromid = lfs_tag_size(tag); |             uint16_t fromid = lfs_tag_size(tag); | ||||||
|             uint16_t toid = lfs_tag_id(tag); |             uint16_t toid = lfs_tag_id(tag); | ||||||
|             int err = lfs_dir_traverse(lfs, |             dir = buffer; | ||||||
|                     buffer, 0, 0xffffffff, NULL, 0, |             off = 0; | ||||||
|                     LFS_MKTAG(0x600, 0x3ff, 0), |             ptag = 0xffffffff; | ||||||
|                     LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0), |             attrs = NULL; | ||||||
|                     fromid, fromid+1, toid-fromid+diff, |             attrcount = 0; | ||||||
|                     cb, data); |             tmask = LFS_MKTAG(0x600, 0x3ff, 0); | ||||||
|             if (err) { |             ttag = LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0); | ||||||
|                 return err; |             begin = fromid; | ||||||
|             } |             end = fromid+1; | ||||||
|  |             diff = toid-fromid+diff; | ||||||
|         } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { |         } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { | ||||||
|             for (unsigned i = 0; i < lfs_tag_size(tag); i++) { |             for (unsigned i = 0; i < lfs_tag_size(tag); i++) { | ||||||
|                 const struct lfs_attr *a = buffer; |                 const struct lfs_attr *a = buffer; | ||||||
|                 int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, |                 res = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, | ||||||
|                         lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); |                         lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); | ||||||
|                 if (err) { |                 if (res < 0) { | ||||||
|                     return err; |                     return res; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (res) { | ||||||
|  |                     break; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             int err = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); |             res = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); | ||||||
|             if (err) { |             if (res < 0) { | ||||||
|                 return err; |                 return res; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (res) { | ||||||
|  |                 break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (sp > 0) { | ||||||
|  |         // pop from the stack and return, fortunately all pops share | ||||||
|  |         // a destination | ||||||
|  |         dir         = stack[sp-1].dir; | ||||||
|  |         off         = stack[sp-1].off; | ||||||
|  |         ptag        = stack[sp-1].ptag; | ||||||
|  |         attrs       = stack[sp-1].attrs; | ||||||
|  |         attrcount   = stack[sp-1].attrcount; | ||||||
|  |         tmask       = stack[sp-1].tmask; | ||||||
|  |         ttag        = stack[sp-1].ttag; | ||||||
|  |         begin       = stack[sp-1].begin; | ||||||
|  |         end         = stack[sp-1].end; | ||||||
|  |         diff        = stack[sp-1].diff; | ||||||
|  |         cb          = stack[sp-1].cb; | ||||||
|  |         data        = stack[sp-1].data; | ||||||
|  |         tag         = stack[sp-1].tag; | ||||||
|  |         buffer      = stack[sp-1].buffer; | ||||||
|  |         disk        = stack[sp-1].disk; | ||||||
|  |         sp -= 1; | ||||||
|  |         goto popped; | ||||||
|  |     } else { | ||||||
|  |         return res; | ||||||
|  |     } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user