Found a better heuristic for did truncation, dropped mlimit, found bugs
The idea here is to combine the current mtree size with the theoretical upper bound on the number of directories in a single mdir, assuming our block size, to give us a heuristic for did truncation that does not require any extra state. - Each directory needs 1 name tag, 1 did tag, and 1 dstart - Each tag needs ~2 alts with our current compaction strategy - Each tag/alt encodes to a minimum of 4 bytes - We can also assume ~1/2 block utilization due to our split threshold This gives us ~3*3*4*2 or ~72 bytes per directory at minimum, or rounding down, ~block_size/32 directories per mdir. This is a nice number because for common NOR flash geometry, 4096/32 = 128, so a filesystem with a single mdir encodes dids in a single byte. The biggest benefit though is being able to drop the mlimit state from the lfs_t struct. --- Unfortunately, this change revealed several bugs. It turns out __builtin_clz in GCC is undefined at 0, which caused our lfs_nlog2 function to return incorrect values at 1. This was causing our dids to all collide when the mtree was inlined, which was resolved by the linear scanning that resolves dids, but was severely limiting what exactly our tests covered. Now that this is fixed (with a simple if statement in lfs_nlog2, lfs_nlog2 now always has defined behavior, even at 0), several bugs needed fixing: - We update the rid based on attrs in lfsr_mdir_commit before updating the mdir. If we have multiple attrs this causes the assert on rid-in-bounds to trigger incorrectly. Just removed that assert for now. - We needed to adjust second grms if they are affected by the fixing of the first grm. - Directory position updates are incorrectly updated if an unrelated weight change occurs before an opened directory, but is not a part of that opened directory. This is NOT fixed yet, the current implementation is just broken enough that I've just ripped it out for now (it was causing the read_with_rms test to fail because pos backed up into the "."/".." entries). This needs some thinking to fix. Because of that last, unfixed bug, tests are not all passing at the moment. To pass testing -DSEEK=0 is needed to disable the failing tests.
This commit is contained in:
@@ -3354,7 +3354,7 @@ static int lfsr_rbyd_estimate(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
||||
// fortunately the self-balancing nature of rybds give us a tight
|
||||
// bound on the number of alt pointers
|
||||
real_dsize
|
||||
+= (2*lfs_nlog2(count+1)+1) * LFSR_TAG_DSIZE
|
||||
+= (2*lfs_nlog2(count)+1) * LFSR_TAG_DSIZE
|
||||
+ LFSR_TAG_DSIZE
|
||||
+ lfsr_data_size(data);
|
||||
|
||||
@@ -6242,7 +6242,6 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
|
||||
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||
if (opened->mdir.mid == mdir->mid
|
||||
&& opened->rid >= attrs[i].id) {
|
||||
LFS_ASSERT(opened->rid <= (lfs_ssize_t)mdir->rbyd.weight);
|
||||
// removed?
|
||||
if (opened->rid + attrs[i].delta < attrs[i].id) {
|
||||
// note we have different behavior for files and dirs
|
||||
@@ -6256,16 +6255,6 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
|
||||
}
|
||||
} else {
|
||||
opened->rid += attrs[i].delta;
|
||||
// adjust dir position
|
||||
if (type == LFS_TYPE_DIR) {
|
||||
((lfsr_dir_t*)opened)->pos += attrs[i].delta;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (opened->mdir.mid > mdir->mid) {
|
||||
// adjust dir position
|
||||
if (type == LFS_TYPE_DIR) {
|
||||
((lfsr_dir_t*)opened)->pos += attrs[i].delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6307,9 +6296,6 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir, lfs_ssize_t *rid,
|
||||
// update our mroot and mtree
|
||||
lfs->mroot = mroot_;
|
||||
lfs->mtree = mtree_;
|
||||
lfs->mlimit = lfs_max32(lfs->mlimit, lfs_max32(
|
||||
mdir_.rbyd.weight,
|
||||
msibling_.rbyd.weight));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -6564,10 +6550,6 @@ static int lfsr_mtree_traversal_next(lfs_t *lfs,
|
||||
return err;
|
||||
}
|
||||
|
||||
// keep track of the largest mdir we've seen, reset here
|
||||
// since this is the first mdir we see
|
||||
lfs->mlimit = traversal->mdir.rbyd.weight;
|
||||
|
||||
if (mid_) {
|
||||
*mid_ = -1;
|
||||
}
|
||||
@@ -6605,9 +6587,6 @@ static int lfsr_mtree_traversal_next(lfs_t *lfs,
|
||||
return err;
|
||||
}
|
||||
|
||||
// keep track of the largest mdir we've seen
|
||||
lfs->mlimit = lfs_max32(lfs->mlimit, traversal->mdir.rbyd.weight);
|
||||
|
||||
if (mid_) {
|
||||
*mid_ = -1;
|
||||
}
|
||||
@@ -6726,9 +6705,6 @@ static int lfsr_mtree_traversal_next(lfs_t *lfs,
|
||||
return err;
|
||||
}
|
||||
|
||||
// keep track of the largest mdir we've seen
|
||||
lfs->mlimit = lfs_max32(lfs->mlimit, traversal->mdir.rbyd.weight);
|
||||
|
||||
if (mid_) {
|
||||
*mid_ = mid;
|
||||
}
|
||||
@@ -7404,22 +7380,34 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) {
|
||||
}
|
||||
|
||||
// Our directory needs an arbitrary directory-id. To find one with
|
||||
// hopefully few collisions, we use a hash of the full path. Since
|
||||
// we have a CRC handy, we can use that.
|
||||
// hopefully few collisions, we use a hash of the full path using our CRC,
|
||||
// since we have it handy.
|
||||
//
|
||||
// We also truncate to make better use of our leb128 encoding. This is
|
||||
// pretty arbitrary, but we don't want collisions, so we truncate to
|
||||
// our best >= estimate of the number of metadata entries. Worst case
|
||||
// this is >= ~2x the number of dids in the system, since each dir needs
|
||||
// a dir entry and dstart entry.
|
||||
// relatively arbitrary, but if we truncate too much we risk increasing
|
||||
// the number of collisions, so we want to aim for ~2x the number dids
|
||||
// in the system. We don't actually know the number of dids in the system,
|
||||
// but we can use a heuristic based on the maximum possible number of
|
||||
// directories in the current mtree assuming our block size.
|
||||
//
|
||||
// - Each directory needs 1 name tag, 1 did tag, and 1 dstart
|
||||
// - Each tag needs ~2 alts with our current compaction strategy
|
||||
// - Each tag/alt encodes to a minimum of 4 bytes
|
||||
// - We can also assume ~1/2 block utilization due to our split threshold
|
||||
//
|
||||
// This gives us ~3*3*4*2 or ~72 bytes per directory at minimum.
|
||||
// Multiplying by 2 and rounding down to the nearest power of 2 for cheaper
|
||||
// division gives us a heuristic of ~block_size/32 directories per mdir.
|
||||
//
|
||||
// This is a nice number because for common NOR flash geometry,
|
||||
// 4096/32 = 128, so a filesystem with a single mdir encodes dids in a
|
||||
// single byte.
|
||||
//
|
||||
lfs_size_t did = lfs_crc32c(0, path, strlen(path))
|
||||
// This complicated bit of logic determines the upper limit of
|
||||
// metadata entries, limited to 2^32. This is done post-log to
|
||||
// try to avoid issues with integer overflow.
|
||||
// note we need to be careful to catch integer overflow
|
||||
& ((1 << lfs_min32(
|
||||
lfs_nlog2(lfsr_mtree_weight(lfs))
|
||||
+ lfs_nlog2(lfs->mlimit),
|
||||
+ lfs_nlog2(lfs->cfg->block_size/32),
|
||||
32)) - 1);
|
||||
|
||||
// Check if we have a collision. If we do, search for the next
|
||||
@@ -7957,6 +7945,14 @@ static int lfsr_fs_fixgrm(lfs_t *lfs) {
|
||||
lfsr_grm_t grm_ = lfs->grm_;
|
||||
lfsr_grm_poprm(&grm_);
|
||||
|
||||
// TODO should this just be implicit in lfsr_mdir_commit? compare cost?
|
||||
// make sure to adjust any remaining grms
|
||||
if (grm_.rms[0].mid == lfs->grm_.rms[0].mid
|
||||
&& grm_.rms[0].rid >= lfs->grm_.rms[0].rid) {
|
||||
LFS_ASSERT(grm_.rms[0].rid != lfs->grm_.rms[0].rid);
|
||||
grm_.rms[0].rid -= 1;
|
||||
}
|
||||
|
||||
uint8_t buf[LFSR_GRM_DSIZE];
|
||||
lfs_ssize_t d = lfsr_grm_todisk(lfs, &grm_, buf);
|
||||
if (d < 0) {
|
||||
|
||||
Reference in New Issue
Block a user