Skip to content

Commit a43f9b3

Browse files
committed
Modified lfs_dir_compact to avoid redundant erases during split
The commit machine in littlefs has three stages: commit, compact, and then split. First we try to append our commit to the metadata log, if that fails we try to compact the metadata log to remove duplicates and make room for the commit, if that still fails we split the metadata into two metadata-pairs and try again. Each stage is less efficient but also less frequent. However, in the case that we're filling up a directory with new files, such as the bootstrap process in setting up a new system, we must pass through all three stages rather quickly in order to get enough metadata-pairs to hold all of our files. This means we'll compact, split, and then need to compact again. This creates more erases than is needed in the optimal case, which can be a big cost on disks with an expensive erase operation. In theory, we can actually avoid this redundant erase by reusing the data we wrote out in the first attempt to compact. In practice, this trick is very complicated to pull off. 1. We may need to cache a half-completed program while we write out the new metadata-pair. We need to write out the second pair first in order to get our new tail before we complete our first metadata-pair. This requires two pcaches, which we don't have The solution here is to just drop our cache and reconstruct what if would have been. This needs to be perfect down to the byte level because we don't have knowledge of where our cache lines are. 2. We may have written out entries that are then moved to the new metadata-pair. The solution here isn't pretty but it works, we just add a delete tag for any entry that was moved over. In the end the solution ends up a bit hacky, with different layers poked through the commit logic in order to manage writes at the byte level from where we manage splits. But it works fairly well and saves erases.
1 parent 478dcdd commit a43f9b3

File tree

1 file changed

+122
-90
lines changed

1 file changed

+122
-90
lines changed

lfs.c

Lines changed: 122 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ static int lfs_bd_read(lfs_t *lfs,
3939
void *buffer, lfs_size_t size) {
4040
uint8_t *data = buffer;
4141
LFS_ASSERT(block != 0xffffffff);
42+
if (off+size > lfs->cfg->block_size) {
43+
return LFS_ERR_CORRUPT;
44+
}
4245

4346
while (size > 0) {
4447
if (pcache && block == pcache->block &&
@@ -452,6 +455,7 @@ struct lfs_commit {
452455

453456
lfs_off_t begin;
454457
lfs_off_t end;
458+
lfs_off_t ack;
455459
};
456460

457461
struct lfs_diskoff {
@@ -503,6 +507,24 @@ static int32_t lfs_commit_get(lfs_t *lfs, lfs_block_t block, lfs_off_t off,
503507
return LFS_ERR_NOENT;
504508
}
505509

510+
static int lfs_commit_prog(lfs_t *lfs, struct lfs_commit *commit,
511+
const void *buffer, lfs_size_t size) {
512+
lfs_off_t skip = lfs_min(lfs_max(commit->ack, commit->off)
513+
- commit->off, size);
514+
int err = lfs_bd_prog(lfs,
515+
&lfs->pcache, &lfs->rcache, false,
516+
commit->block, commit->off + skip,
517+
(const uint8_t*)buffer + skip, size - skip);
518+
if (err) {
519+
return err;
520+
}
521+
522+
commit->crc = lfs_crc(commit->crc, buffer, size);
523+
commit->off += size;
524+
commit->ack = lfs_max(commit->off, commit->ack);
525+
return 0;
526+
}
527+
506528
static int lfs_commit_attrs(lfs_t *lfs, struct lfs_commit *commit,
507529
uint16_t id, const struct lfs_attr *attrs);
508530

@@ -532,21 +554,14 @@ static int lfs_commit_attr(lfs_t *lfs, struct lfs_commit *commit,
532554

533555
// write out tag
534556
uint32_t ntag = lfs_tole32((tag & 0x7fffffff) ^ commit->ptag);
535-
commit->crc = lfs_crc(commit->crc, &ntag, sizeof(ntag));
536-
int err = lfs_bd_prog(lfs,
537-
&lfs->pcache, &lfs->rcache, false,
538-
commit->block, commit->off, &ntag, sizeof(ntag));
557+
int err = lfs_commit_prog(lfs, commit, &ntag, sizeof(ntag));
539558
if (err) {
540559
return err;
541560
}
542-
commit->off += sizeof(ntag);
543561

544562
if (!(tag & 0x80000000)) {
545563
// from memory
546-
commit->crc = lfs_crc(commit->crc, buffer, size);
547-
err = lfs_bd_prog(lfs,
548-
&lfs->pcache, &lfs->rcache, false,
549-
commit->block, commit->off, buffer, size);
564+
err = lfs_commit_prog(lfs, commit, buffer, size);
550565
if (err) {
551566
return err;
552567
}
@@ -563,17 +578,13 @@ static int lfs_commit_attr(lfs_t *lfs, struct lfs_commit *commit,
563578
return err;
564579
}
565580

566-
commit->crc = lfs_crc(commit->crc, &dat, 1);
567-
err = lfs_bd_prog(lfs,
568-
&lfs->pcache, &lfs->rcache, false,
569-
commit->block, commit->off+i, &dat, 1);
581+
err = lfs_commit_prog(lfs, commit, &dat, 1);
570582
if (err) {
571583
return err;
572584
}
573585
}
574586
}
575587

576-
commit->off += size;
577588
commit->ptag = tag & 0x7fffffff;
578589
return 0;
579590
}
@@ -677,13 +688,11 @@ static int lfs_commit_crc(lfs_t *lfs, struct lfs_commit *commit) {
677688

678689
// read erased state from next program unit
679690
uint32_t tag = 0;
680-
if (off < lfs->cfg->block_size) {
681-
int err = lfs_bd_read(lfs,
682-
&lfs->pcache, &lfs->rcache, lfs->cfg->block_size,
683-
commit->block, off, &tag, sizeof(tag));
684-
if (err) {
685-
return err;
686-
}
691+
int err = lfs_bd_read(lfs,
692+
&lfs->pcache, &lfs->rcache, sizeof(tag),
693+
commit->block, off, &tag, sizeof(tag));
694+
if (err && err != LFS_ERR_CORRUPT) {
695+
return err;
687696
}
688697

689698
// build crc tag
@@ -697,7 +706,7 @@ static int lfs_commit_crc(lfs_t *lfs, struct lfs_commit *commit) {
697706
footer[0] = lfs_tole32(tag ^ commit->ptag);
698707
commit->crc = lfs_crc(commit->crc, &footer[0], sizeof(footer[0]));
699708
footer[1] = lfs_tole32(commit->crc);
700-
int err = lfs_bd_prog(lfs,
709+
err = lfs_bd_prog(lfs,
701710
&lfs->pcache, &lfs->rcache, false,
702711
commit->block, commit->off, &footer, sizeof(footer));
703712
if (err) {
@@ -824,12 +833,6 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs,
824833
lfs_global_zero(&templocals);
825834

826835
while (true) {
827-
// reached end of block
828-
if (off+sizeof(uint32_t) >= lfs->cfg->block_size) {
829-
dir->erased = false;
830-
break;
831-
}
832-
833836
// extract next tag
834837
uint32_t tag;
835838
int err = lfs_bd_read(lfs,
@@ -1076,79 +1079,88 @@ static int lfs_dir_compact(lfs_t *lfs,
10761079
lfs_global_zero(&dir->locals);
10771080

10781081
while (true) {
1079-
// last complete id
1080-
dir->count = end - begin;
1081-
int16_t ack = -1;
1082+
// setup compaction
1083+
bool splitted = false;
10821084
bool exhausted = false;
10831085

1084-
// increment revision count
1085-
dir->rev += 1;
1086-
if (lfs->cfg->block_cycles && dir->rev % lfs->cfg->block_cycles == 0) {
1087-
if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
1088-
// we're writing too much to the superblock, should we expand?
1089-
lfs_ssize_t res = lfs_fs_size(lfs);
1090-
if (res < 0) {
1091-
return res;
1092-
}
1086+
struct lfs_commit commit;
1087+
commit.block = dir->pair[1];
1088+
commit.ack = 0;
1089+
1090+
commit:
1091+
// setup erase state
1092+
exhausted = false;
1093+
dir->count = end - begin;
1094+
int16_t ackid = -1;
1095+
1096+
// setup commit state
1097+
commit.off = 0;
1098+
commit.crc = 0xffffffff;
1099+
commit.ptag = 0;
1100+
1101+
// space is complicated, we need room for tail, crc, globals,
1102+
// cleanup delete, and we cap at half a block to give room
1103+
// for metadata updates
1104+
commit.begin = 0;
1105+
commit.end = lfs_min(
1106+
lfs_alignup(lfs->cfg->block_size/2, lfs->cfg->prog_size),
1107+
lfs->cfg->block_size - 38);
1108+
1109+
if (!splitted) {
1110+
// increment revision count
1111+
dir->rev += 1;
1112+
if (lfs->cfg->block_cycles &&
1113+
dir->rev % lfs->cfg->block_cycles == 0) {
1114+
if (lfs_pair_cmp(dir->pair,
1115+
(const lfs_block_t[2]){0, 1}) == 0) {
1116+
// we're writing too much to the superblock,
1117+
// should we expand?
1118+
lfs_ssize_t res = lfs_fs_size(lfs);
1119+
if (res < 0) {
1120+
return res;
1121+
}
10931122

1094-
// do we have enough space to expand?
1095-
if (res < lfs->cfg->block_count/2) {
1096-
LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev);
1123+
// do we have enough space to expand?
1124+
if (res < lfs->cfg->block_count/2) {
1125+
LFS_DEBUG("Expanding superblock at rev %"PRIu32,
1126+
dir->rev);
1127+
exhausted = true;
1128+
goto split;
1129+
}
1130+
} else {
1131+
// we're writing too much, time to relocate
10971132
exhausted = true;
1098-
goto split;
1133+
goto relocate;
10991134
}
1100-
} else {
1101-
// we're writing too much, time to relocate
1102-
exhausted = true;
1103-
goto relocate;
11041135
}
1105-
}
11061136

1107-
// erase block to write to
1108-
int err = lfs_bd_erase(lfs, dir->pair[1]);
1109-
if (err) {
1110-
if (err == LFS_ERR_CORRUPT) {
1111-
goto relocate;
1137+
// erase block to write to
1138+
int err = lfs_bd_erase(lfs, dir->pair[1]);
1139+
if (err) {
1140+
if (err == LFS_ERR_CORRUPT) {
1141+
goto relocate;
1142+
}
1143+
return err;
11121144
}
1113-
return err;
11141145
}
11151146

11161147
// write out header
1117-
uint32_t crc = 0xffffffff;
11181148
uint32_t rev = lfs_tole32(dir->rev);
1119-
crc = lfs_crc(crc, &rev, sizeof(rev));
1120-
err = lfs_bd_prog(lfs,
1121-
&lfs->pcache, &lfs->rcache, false,
1122-
dir->pair[1], 0, &rev, sizeof(rev));
1149+
int err = lfs_commit_prog(lfs, &commit, &rev, sizeof(rev));
11231150
if (err) {
11241151
if (err == LFS_ERR_CORRUPT) {
11251152
goto relocate;
11261153
}
11271154
return err;
11281155
}
11291156

1130-
// setup compaction
1131-
struct lfs_commit commit = {
1132-
.block = dir->pair[1],
1133-
.off = sizeof(dir->rev),
1134-
.crc = crc,
1135-
.ptag = 0,
1136-
1137-
// space is complicated, we need room for tail, crc, globals,
1138-
// and we cap at half a block to give room for metadata updates
1139-
.begin = 0,
1140-
.end = lfs_min(
1141-
lfs_alignup(lfs->cfg->block_size/2, lfs->cfg->prog_size),
1142-
lfs->cfg->block_size - 34),
1143-
};
1144-
11451157
// commit with a move
1146-
for (uint16_t id = begin; id < end; id++) {
1158+
for (uint16_t id = begin; id < end || commit.off < commit.ack; id++) {
11471159
err = lfs_commit_move(lfs, &commit,
11481160
0x003ff000, LFS_MKTAG(0, id, 0),
11491161
0x003ff000, LFS_MKTAG(0, id - begin, 0),
11501162
source, attrs);
1151-
if (err) {
1163+
if (err && !(splitted && err == LFS_ERR_NOSPC)) {
11521164
if (err == LFS_ERR_NOSPC) {
11531165
goto split;
11541166
} else if (err == LFS_ERR_CORRUPT) {
@@ -1157,12 +1169,25 @@ static int lfs_dir_compact(lfs_t *lfs,
11571169
return err;
11581170
}
11591171

1160-
ack = id;
1172+
ackid = id;
11611173
}
11621174

11631175
// reopen reserved space at the end
11641176
commit.end = lfs->cfg->block_size - 8;
11651177

1178+
if (ackid >= end) {
1179+
// extra garbage attributes were written out during split,
1180+
// need to clean up
1181+
err = lfs_commit_attr(lfs, &commit,
1182+
LFS_MKTAG(LFS_TYPE_DELETE, ackid, 0), NULL);
1183+
if (err) {
1184+
if (err == LFS_ERR_CORRUPT) {
1185+
goto relocate;
1186+
}
1187+
return err;
1188+
}
1189+
}
1190+
11661191
if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
11671192
// move over (duplicate) superblock if we are root
11681193
err = lfs_commit_move(lfs, &commit,
@@ -1178,8 +1203,8 @@ static int lfs_dir_compact(lfs_t *lfs,
11781203
}
11791204

11801205
if (!relocated) {
1181-
// commit any globals, unless we're relocating, in which case our
1182-
// parent will steal our globals
1206+
// commit any globals, unless we're relocating,
1207+
// in which case our parent will steal our globals
11831208
err = lfs_commit_globals(lfs, &commit, &dir->locals);
11841209
if (err) {
11851210
if (err == LFS_ERR_CORRUPT) {
@@ -1222,8 +1247,13 @@ static int lfs_dir_compact(lfs_t *lfs,
12221247
split:
12231248
// commit no longer fits, need to split dir,
12241249
// drop caches and create tail
1225-
lfs_cache_drop(lfs, &lfs->pcache);
1226-
if (!exhausted && ack < 0) {
1250+
splitted = !exhausted;
1251+
if (lfs->pcache.block != 0xffffffff) {
1252+
commit.ack -= lfs->pcache.size;
1253+
lfs_cache_drop(lfs, &lfs->pcache);
1254+
}
1255+
1256+
if (!exhausted && ackid < 0) {
12271257
// If we can't fit in this block, we won't fit in next block
12281258
return LFS_ERR_NOSPC;
12291259
}
@@ -1234,25 +1264,26 @@ static int lfs_dir_compact(lfs_t *lfs,
12341264
return err;
12351265
}
12361266

1237-
if (exhausted) {
1238-
lfs->root[0] = tail.pair[0];
1239-
lfs->root[1] = tail.pair[1];
1240-
}
1241-
12421267
tail.split = dir->split;
12431268
tail.tail[0] = dir->tail[0];
12441269
tail.tail[1] = dir->tail[1];
12451270

1246-
err = lfs_dir_compact(lfs, &tail, attrs, source, ack+1, end);
1271+
err = lfs_dir_compact(lfs, &tail, attrs, source, ackid+1, end);
12471272
if (err) {
12481273
return err;
12491274
}
12501275

1251-
end = ack+1;
1276+
end = ackid+1;
12521277
dir->tail[0] = tail.pair[0];
12531278
dir->tail[1] = tail.pair[1];
12541279
dir->split = true;
1255-
continue;
1280+
1281+
if (exhausted) {
1282+
lfs->root[0] = tail.pair[0];
1283+
lfs->root[1] = tail.pair[1];
1284+
}
1285+
1286+
goto commit;
12561287

12571288
relocate:
12581289
// commit was corrupted, drop caches and prepare to relocate block
@@ -1363,6 +1394,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
13631394

13641395
.begin = dir->off,
13651396
.end = lfs->cfg->block_size - 8,
1397+
.ack = 0,
13661398
};
13671399

13681400
for (const lfs_mattr_t *a = attrs; a; a = a->next) {

0 commit comments

Comments
 (0)