Buffered writeback to a block device submits one bio per dirty buffer_head. For contiguous dirty data this creates many small bios that the block layer then has to merge again, or does not merge at all. Large folios make this worse, since a 2 MB folio with 4 KiB blocks is up to 512 bios, although the buffers of a folio, and consecutive folios, are often contiguous on disk.
To aggregate data into a bio, keep one bio open while writing back and append each dirty buffer to it as long as it directly continues the data already in the bio and can share its properties. Once a buffer does not fit, submit the bio and start a new one. The open bio is kept across the buffers of a folio and across consecutive folios, so contiguous dirty data ends up in one large bio, which is then also completed in one pass instead of buffer by buffer. A similar approach was posted before [1]. Since then folios, and large folios in particular, make it much more attractive: a single folio can now carry many buffers, and a run of folios can fill a bio with hundreds of megabytes. With 2 MB folios I see bios of up to 512 MB (256 segments). [1] https://lore.kernel.org/all/[email protected]/ Lukas Sichert (4): buffer: writeback: extract shared bio build/complete helpers buffer: writeback: batch contiguous async-write buffers per folio block: coalesce bios across folios in blkdev_writepages selftests: filesystems: add blkdev writeback coalescing test block/fops.c | 9 +- fs/buffer.c | 236 +++++++++++++--- include/linux/buffer_head.h | 3 + .../testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- .../filesystems/blkdev_writeback_test.c | 259 ++++++++++++++++++ 6 files changed, 463 insertions(+), 47 deletions(-) create mode 100644 tools/testing/selftests/filesystems/blkdev_writeback_test.c base-commit: 0c2669a9f4a1d607e7591ae50ccf3c432a0aff08

