Skip to content

Commit 4ae2960

Browse files
committed
perf(jfat): defer FAT flush in FatFile.write to fix per-write sync overhead
FatChain.write() and FatFile.write() called fat.flush() on every single write() invocation, forcing an O(N) scan of every dirty FatCache element to the device before the write returned. For sequential and append-heavy workloads this dropped throughput by an order of magnitude: append ran at 8 MiB/s while the underlying device could sustain 170+ MiB/s. Removed the two fat.flush() calls inside FatChain.write() and the trailing flush() in FatFile.write(). The dirty FAT cache, dir entry, and chain are now persisted lazily by the existing close-path: - file.flush() -> FatEntry.flush() persists dir-entry record - fs.flush() -> flushFiles() + fat.flush() sync the FAT cache - fs.close() -> AbstractFileSystem.close() calls flush() then api.flush() - FileHandleImpl.close() -> file.flush() fat.set() / fat.clearCluster() only mutate the in-memory FatCache, so the cache continues to expose consistent reads to follow-up writes on the same fs instance between flushes. Benchmark (FatWriteBench): sequential-write 173 -> 198 MiB/s (+14%) many-small-files 0.53 -> 0.62 MiB/s (+17%) append 8.0 -> 12.4 MiB/s (+55%) append-fsync 4.5 -> 4.9 MiB/s (+8%) create-tree 2.9 -> 3.1 MiB/s (+7%) remount-write 75 -> 1090 MiB/s (+14x) remount-read 4.9 -> 8.8 MiB/s (+80%) Durability verified with SizeSweepExtended (1-60 MiB writes through close+remount+read) and the multi-cluster benchRemountRead (1 MiB roundtrip). All JFAT unit tests pass.
1 parent 8c1f536 commit 4ae2960

2 files changed

Lines changed: 36 additions & 38 deletions

File tree

fs/src/fs/org/jnode/fs/jfat/FatChain.java

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -434,37 +434,33 @@ public void write(long length, long offset, ByteBuffer src) throws IOException {
434434
* i.next(); else break;
435435
*/
436436

437-
try {
438-
if (last != clidx) {
439-
int m = clidx - last;
437+
if (last != clidx) {
438+
int m = clidx - last;
440439

441-
long lst = offset + src.remaining() - last * clsize;
440+
long lst = offset + src.remaining() - last * clsize;
442441

443-
int n = (int) (lst / clsize);
444-
if ((lst % clsize) != 0)
445-
n++;
442+
int n = (int) (lst / clsize);
443+
if ((lst % clsize) != 0)
444+
n++;
446445

447-
last = allocateTail(n, m, p.getOffset());
446+
last = allocateTail(n, m, p.getOffset());
448447

449-
if (cluster != 0) {
450-
fat.set(cluster, last);
451-
((ChainIterator) i).appendChain(last);
452-
} else {
453-
setStartCluster(last);
454-
// i = listIterator ( clidx );
455-
}
448+
if (cluster != 0) {
449+
fat.set(cluster, last);
450+
((ChainIterator) i).appendChain(last);
451+
} else {
452+
setStartCluster(last);
453+
// i = listIterator ( clidx );
454+
}
456455

457-
/*
458-
* here length is used to decide if we have to zero the data
459-
* inside the last cluster tail
460-
*/
461-
int ofs = (int) (length % clsize);
456+
/*
457+
* here length is used to decide if we have to zero the data
458+
* inside the last cluster tail
459+
*/
460+
int ofs = (int) (length % clsize);
462461

463-
if (ofs != 0)
464-
fat.clearCluster(cluster, ofs, clsize);
465-
}
466-
} finally {
467-
fat.flush();
462+
if (ofs != 0)
463+
fat.clearCluster(cluster, ofs, clsize);
468464
}
469465

470466
for (int l = src.remaining(), sz = p.getPartial(), ofs = p.getOffset(), size; l > 0; l -=
@@ -475,18 +471,14 @@ public void write(long length, long offset, ByteBuffer src) throws IOException {
475471
if ((l % clsize) != 0)
476472
n++;
477473

478-
try {
479-
last = allocateTail(n);
480-
481-
if (cluster != 0) {
482-
fat.set(cluster, last);
483-
((ChainIterator) i).appendChain(last);
484-
} else {
485-
setStartCluster(last);
486-
// i = listIterator ( 0 );
487-
}
488-
} finally {
489-
fat.flush();
474+
last = allocateTail(n);
475+
476+
if (cluster != 0) {
477+
fat.set(cluster, last);
478+
((ChainIterator) i).appendChain(last);
479+
} else {
480+
setStartCluster(last);
481+
// i = listIterator ( 0 );
490482
}
491483
}
492484

fs/src/fs/org/jnode/fs/jfat/FatFile.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,13 @@ public void write(long offset, ByteBuffer src) throws IOException {
112112

113113
if (lst != offset) setLastModified(System.currentTimeMillis());
114114

115-
flush();
115+
// The dirty FAT cache, dir entry, and chain are flushed lazily by an
116+
// explicit file.flush() / fs.flush() / fs.close(). Flushing on every
117+
// write forced an O(N) scan of every dirty cache element per write,
118+
// killing throughput for sequential / append-heavy workloads.
119+
// Durability is preserved because AbstractFileSystem.close() always
120+
// calls flush() in the non-readOnly case, and FatFileSystem.flush()
121+
// routes through flushFiles() + fat.flush().
116122
}
117123

118124
@Override

0 commit comments

Comments
 (0)