* [PATCH] btrfs: skip hole detection during full fsync for files without holes
@ 2026-07-30 16:41 fdmanana
2026-07-30 22:32 ` Qu Wenruo
2026-07-31 9:19 ` [PATCH v2] " fdmanana
0 siblings, 2 replies; 4+ messages in thread
From: fdmanana @ 2026-07-30 16:41 UTC (permalink / raw)
To: linux-btrfs
From: Filipe Manana <fdmanana@suse.com>
If we the no-holes feature is enabled (a default since btrfs-progs 5.15),
when doing a full fsync we always iterate of all leaves in the subvolume
root that contain file extent items in order to detect holes between them.
This can take a lot of time for files with a large number of extents.
But if we know there are no prealloc extents and the amount of space
(uncompressed space) matches the i_size of the inode, then we cannot
have holes and therefore avoid searching for them. So skip the search
if those conditions are met.
The following test script was used:
$ cat test.sh
#!/bin/bash
MNT=/mnt/nullb0
DEV=/dev/nullb0
MOUNT_OPTIONS="-o ssd"
MKFS_OPTIONS=""
umount $MNT &> /dev/null
mkfs.btrfs -f $MKFS_OPTIONS $DEV
mount $MOUNT_OPTIONS $DEV $MNT
# 256M gives 64K extents of 4K each.
FILE_SIZE=$((256 * 1024 * 1024))
touch $MNT/foobar
for ((i = 0; i < $FILE_SIZE; i += 8192)); do
xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
done
xfs_io -c "fsync" $MNT/foobar
for ((i = 4096; i < $FILE_SIZE; i += 8192)); do
xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
done
# unmount and mount, clear caches and ensure the next fsync is a
# full sync.
umount $MNT
mount $MOUNT_OPTIONS $DEV $MNT
# Do some change to the file in order to fsync.
xfs_io -c "pwrite -S 0xcd 0 4K" $MNT/foobar > /dev/null
T0=$(date +%s%N)
xfs_io -c "fsync" $MNT/foobar
T1=$(date +%s%N)
echo
echo "Took $(( (T1 - T0) / 1000 ))us"
umount $MNT
Before this change:
Took 28721us
After this change:
Took 5453us
That's about 5.3x times faster.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
fs/btrfs/tree-log.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 47046dd14997..226dd3053564 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5591,6 +5591,14 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans,
if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0)
return 0;
+ /*
+ * If there are no prealloc extents (which can be located past i_size),
+ * and disk space used matches the i_size, then there are no holes.
+ */
+ if (!(inode->flags & BTRFS_INODE_PREALLOC) &&
+ i_size == inode_get_bytes(&inode->vfs_inode))
+ return 0;
+
key.objectid = ino;
key.type = BTRFS_EXTENT_DATA_KEY;
key.offset = 0;
--
2.47.2
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH] btrfs: skip hole detection during full fsync for files without holes
2026-07-30 16:41 [PATCH] btrfs: skip hole detection during full fsync for files without holes fdmanana
@ 2026-07-30 22:32 ` Qu Wenruo
2026-07-31 9:19 ` [PATCH v2] " fdmanana
1 sibling, 0 replies; 4+ messages in thread
From: Qu Wenruo @ 2026-07-30 22:32 UTC (permalink / raw)
To: fdmanana, linux-btrfs
在 2026/7/31 02:11, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
>
> If we the no-holes feature is enabled (a default since btrfs-progs 5.15),
> when doing a full fsync we always iterate of all leaves in the subvolume
> root that contain file extent items in order to detect holes between them.
>
> This can take a lot of time for files with a large number of extents.
> But if we know there are no prealloc extents and the amount of space
> (uncompressed space) matches the i_size of the inode, then we cannot
> have holes and therefore avoid searching for them. So skip the search
> if those conditions are met.
>
> The following test script was used:
>
> $ cat test.sh
> #!/bin/bash
>
> MNT=/mnt/nullb0
> DEV=/dev/nullb0
>
> MOUNT_OPTIONS="-o ssd"
> MKFS_OPTIONS=""
>
> umount $MNT &> /dev/null
> mkfs.btrfs -f $MKFS_OPTIONS $DEV
> mount $MOUNT_OPTIONS $DEV $MNT
>
> # 256M gives 64K extents of 4K each.
> FILE_SIZE=$((256 * 1024 * 1024))
> touch $MNT/foobar
>
> for ((i = 0; i < $FILE_SIZE; i += 8192)); do
> xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
> done
>
> xfs_io -c "fsync" $MNT/foobar
>
> for ((i = 4096; i < $FILE_SIZE; i += 8192)); do
> xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
> done
>
> # unmount and mount, clear caches and ensure the next fsync is a
> # full sync.
> umount $MNT
> mount $MOUNT_OPTIONS $DEV $MNT
>
> # Do some change to the file in order to fsync.
> xfs_io -c "pwrite -S 0xcd 0 4K" $MNT/foobar > /dev/null
>
> T0=$(date +%s%N)
> xfs_io -c "fsync" $MNT/foobar
> T1=$(date +%s%N)
>
> echo
> echo "Took $(( (T1 - T0) / 1000 ))us"
>
> umount $MNT
>
> Before this change:
>
> Took 28721us
>
> After this change:
>
> Took 5453us
>
> That's about 5.3x times faster.
>
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
> ---
> fs/btrfs/tree-log.c | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
> index 47046dd14997..226dd3053564 100644
> --- a/fs/btrfs/tree-log.c
> +++ b/fs/btrfs/tree-log.c
> @@ -5591,6 +5591,14 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans,
> if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0)
> return 0;
>
> + /*
> + * If there are no prealloc extents (which can be located past i_size),
> + * and disk space used matches the i_size, then there are no holes.
> + */
I'm wondering if i_size < nbytes, can we still skip the hole scan?
Thanks,
Qu
> + if (!(inode->flags & BTRFS_INODE_PREALLOC) &&
> + i_size == inode_get_bytes(&inode->vfs_inode))
> + return 0;
> +
> key.objectid = ino;
> key.type = BTRFS_EXTENT_DATA_KEY;
> key.offset = 0;
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2] btrfs: skip hole detection during full fsync for files without holes
2026-07-30 16:41 [PATCH] btrfs: skip hole detection during full fsync for files without holes fdmanana
2026-07-30 22:32 ` Qu Wenruo
@ 2026-07-31 9:19 ` fdmanana
2026-07-31 9:27 ` Qu Wenruo
1 sibling, 1 reply; 4+ messages in thread
From: fdmanana @ 2026-07-31 9:19 UTC (permalink / raw)
To: linux-btrfs
From: Filipe Manana <fdmanana@suse.com>
If we the no-holes feature is enabled (a default since btrfs-progs 5.15),
when doing a full fsync we always iterate of all leaves in the subvolume
root that contain file extent items in order to detect holes between them.
This can take a lot of time for files with a large number of extents.
But if we know there are no prealloc extents and the amount of space
(uncompressed space) is greater than or equals to the i_size of the
inode, then we cannot have holes and therefore avoid searching for
them. So skip the search if those conditions are met.
The following test script was used:
$ cat test.sh
#!/bin/bash
MNT=/mnt/nullb0
DEV=/dev/nullb0
umount $MNT &> /dev/null
mkfs.btrfs -f $DEV
mount $DEV $MNT
# 256M gives 64K extents of 4K each.
FILE_SIZE=$((256 * 1024 * 1024))
touch $MNT/foobar
for ((i = 0; i < $FILE_SIZE; i += 8192)); do
xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
done
xfs_io -c "fsync" $MNT/foobar
for ((i = 4096; i < $FILE_SIZE; i += 8192)); do
xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
done
# unmount and mount, clear caches and ensure the next fsync is a
# full sync.
umount $MNT
mount $DEV $MNT
# Do some change to the file in order to fsync.
xfs_io -c "pwrite -S 0xcd 0 4K" $MNT/foobar > /dev/null
T0=$(date +%s%N)
xfs_io -c "fsync" $MNT/foobar
T1=$(date +%s%N)
echo
echo "Took $(( (T1 - T0) / 1000 ))us"
umount $MNT
Before this change:
Took 28721us
After this change:
Took 5453us
That's about 5.3x times faster.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
V2: Support the case where i_size is not sector size aligned and so
i_size <= nbytes.
fs/btrfs/tree-log.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 47046dd14997..88b6258f2e3f 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -5591,6 +5591,15 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans,
if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0)
return 0;
+ /*
+ * If there are no prealloc extents (which can be located past i_size),
+ * and disk space used is greater than or equals to i_size, then there
+ * are no holes.
+ */
+ if (!(inode->flags & BTRFS_INODE_PREALLOC) &&
+ i_size <= inode_get_bytes(&inode->vfs_inode))
+ return 0;
+
key.objectid = ino;
key.type = BTRFS_EXTENT_DATA_KEY;
key.offset = 0;
--
2.47.2
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH v2] btrfs: skip hole detection during full fsync for files without holes
2026-07-31 9:19 ` [PATCH v2] " fdmanana
@ 2026-07-31 9:27 ` Qu Wenruo
0 siblings, 0 replies; 4+ messages in thread
From: Qu Wenruo @ 2026-07-31 9:27 UTC (permalink / raw)
To: fdmanana, linux-btrfs
在 2026/7/31 18:49, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
>
> If we the no-holes feature is enabled (a default since btrfs-progs 5.15),
> when doing a full fsync we always iterate of all leaves in the subvolume
> root that contain file extent items in order to detect holes between them.
>
> This can take a lot of time for files with a large number of extents.
> But if we know there are no prealloc extents and the amount of space
> (uncompressed space) is greater than or equals to the i_size of the
> inode, then we cannot have holes and therefore avoid searching for
> them. So skip the search if those conditions are met.
>
> The following test script was used:
>
> $ cat test.sh
> #!/bin/bash
>
> MNT=/mnt/nullb0
> DEV=/dev/nullb0
>
> umount $MNT &> /dev/null
> mkfs.btrfs -f $DEV
> mount $DEV $MNT
>
> # 256M gives 64K extents of 4K each.
> FILE_SIZE=$((256 * 1024 * 1024))
> touch $MNT/foobar
>
> for ((i = 0; i < $FILE_SIZE; i += 8192)); do
> xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
> done
>
> xfs_io -c "fsync" $MNT/foobar
>
> for ((i = 4096; i < $FILE_SIZE; i += 8192)); do
> xfs_io -c "pwrite -S 0xab $i 4K" $MNT/foobar > /dev/null
> done
>
> # unmount and mount, clear caches and ensure the next fsync is a
> # full sync.
> umount $MNT
> mount $DEV $MNT
>
> # Do some change to the file in order to fsync.
> xfs_io -c "pwrite -S 0xcd 0 4K" $MNT/foobar > /dev/null
>
> T0=$(date +%s%N)
> xfs_io -c "fsync" $MNT/foobar
> T1=$(date +%s%N)
>
> echo
> echo "Took $(( (T1 - T0) / 1000 ))us"
>
> umount $MNT
>
> Before this change:
>
> Took 28721us
>
> After this change:
>
> Took 5453us
>
> That's about 5.3x times faster.
>
> Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Thanks,
Qu
> ---
>
> V2: Support the case where i_size is not sector size aligned and so
> i_size <= nbytes.
>
> fs/btrfs/tree-log.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
> index 47046dd14997..88b6258f2e3f 100644
> --- a/fs/btrfs/tree-log.c
> +++ b/fs/btrfs/tree-log.c
> @@ -5591,6 +5591,15 @@ static int btrfs_log_holes(struct btrfs_trans_handle *trans,
> if (!btrfs_fs_incompat(fs_info, NO_HOLES) || i_size == 0)
> return 0;
>
> + /*
> + * If there are no prealloc extents (which can be located past i_size),
> + * and disk space used is greater than or equals to i_size, then there
> + * are no holes.
> + */
> + if (!(inode->flags & BTRFS_INODE_PREALLOC) &&
> + i_size <= inode_get_bytes(&inode->vfs_inode))
> + return 0;
> +
> key.objectid = ino;
> key.type = BTRFS_EXTENT_DATA_KEY;
> key.offset = 0;
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-31 9:27 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 16:41 [PATCH] btrfs: skip hole detection during full fsync for files without holes fdmanana
2026-07-30 22:32 ` Qu Wenruo
2026-07-31 9:19 ` [PATCH v2] " fdmanana
2026-07-31 9:27 ` Qu Wenruo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox