Skip to content

Commit ffa7c42

Browse files
fdmananakdave
authored andcommitted
Btrfs: send, do not issue unnecessary truncate operations
When send finishes processing an inode representing a regular file, it always issues a truncate operation for that file, even if its size did not change or the last write sets the file size correctly. In the most common cases, the issued write operations set the file to correct size (either full or incremental sends) or the file size did not change (for incremental sends), so the only case where a truncate operation is needed is when a file size becomes smaller in the send snapshot when compared to the parent snapshot. By not issuing unnecessary truncate operations we reduce the stream size and save time in the receiver. Currently truncating a file to the same size triggers writeback of its last page (if it's dirty) and waits for it to complete (only if the file size is not aligned with the filesystem's sector size). This is being fixed by another patch and is independent of this change (that patch's title is "Btrfs: skip writeback of last page when truncating file to same size"). The following script was used to measure time spent by a receiver without this change applied, with this change applied, and without this change and with the truncate fix applied (the fix to not make it start and wait for writeback to complete). $ cat test_send.sh #!/bin/bash SRC_DEV=/dev/sdc DST_DEV=/dev/sdd SRC_MNT=/mnt/sdc DST_MNT=/mnt/sdd mkfs.btrfs -f $SRC_DEV >/dev/null mkfs.btrfs -f $DST_DEV >/dev/null mount $SRC_DEV $SRC_MNT mount $DST_DEV $DST_MNT echo "Creating source filesystem" for ((t = 0; t < 10; t++)); do ( for ((i = 1; i <= 20000; i++)); do xfs_io -f -c "pwrite -S 0xab 0 5000" \ $SRC_MNT/file_$i > /dev/null done ) & worker_pids[$t]=$! done wait ${worker_pids[@]} echo "Creating and sending snapshot" btrfs subvolume snapshot -r $SRC_MNT $SRC_MNT/snap1 >/dev/null /usr/bin/time -f "send took %e seconds" \ btrfs send -f $SRC_MNT/send_file $SRC_MNT/snap1 /usr/bin/time -f "receive took %e seconds" \ btrfs receive -f $SRC_MNT/send_file $DST_MNT umount $SRC_MNT umount $DST_MNT The results, which are averages for 5 runs for each case, were the following: * Without this change average receive time was 26.49 seconds standard deviation of 2.53 seconds * Without this change and with the truncate fix average receive time was 12.51 seconds standard deviation of 0.32 seconds * With this change and without the truncate fix average receive time was 10.02 seconds standard deviation of 1.11 seconds Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 213e8c5 commit ffa7c42

File tree

1 file changed

+21
-5
lines changed

1 file changed

+21
-5
lines changed

fs/btrfs/send.c

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ struct send_ctx {
112112
u64 cur_inode_mode;
113113
u64 cur_inode_rdev;
114114
u64 cur_inode_last_extent;
115+
u64 cur_inode_next_write_offset;
115116

116117
u64 send_progress;
117118

@@ -5030,6 +5031,7 @@ static int send_hole(struct send_ctx *sctx, u64 end)
50305031
break;
50315032
offset += len;
50325033
}
5034+
sctx->cur_inode_next_write_offset = offset;
50335035
tlv_put_failure:
50345036
fs_path_free(p);
50355037
return ret;
@@ -5265,6 +5267,7 @@ static int send_write_or_clone(struct send_ctx *sctx,
52655267
} else {
52665268
ret = send_extent_data(sctx, offset, len);
52675269
}
5270+
sctx->cur_inode_next_write_offset = offset + len;
52685271
out:
52695272
return ret;
52705273
}
@@ -5789,6 +5792,7 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
57895792
u64 right_gid;
57905793
int need_chmod = 0;
57915794
int need_chown = 0;
5795+
int need_truncate = 1;
57925796
int pending_move = 0;
57935797
int refs_processed = 0;
57945798

@@ -5826,9 +5830,13 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
58265830
need_chown = 1;
58275831
if (!S_ISLNK(sctx->cur_inode_mode))
58285832
need_chmod = 1;
5833+
if (sctx->cur_inode_next_write_offset == sctx->cur_inode_size)
5834+
need_truncate = 0;
58295835
} else {
5836+
u64 old_size;
5837+
58305838
ret = get_inode_info(sctx->parent_root, sctx->cur_ino,
5831-
NULL, NULL, &right_mode, &right_uid,
5839+
&old_size, NULL, &right_mode, &right_uid,
58325840
&right_gid, NULL);
58335841
if (ret < 0)
58345842
goto out;
@@ -5837,6 +5845,10 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
58375845
need_chown = 1;
58385846
if (!S_ISLNK(sctx->cur_inode_mode) && left_mode != right_mode)
58395847
need_chmod = 1;
5848+
if ((old_size == sctx->cur_inode_size) ||
5849+
(sctx->cur_inode_size > old_size &&
5850+
sctx->cur_inode_next_write_offset == sctx->cur_inode_size))
5851+
need_truncate = 0;
58405852
}
58415853

58425854
if (S_ISREG(sctx->cur_inode_mode)) {
@@ -5855,10 +5867,13 @@ static int finish_inode_if_needed(struct send_ctx *sctx, int at_end)
58555867
goto out;
58565868
}
58575869
}
5858-
ret = send_truncate(sctx, sctx->cur_ino, sctx->cur_inode_gen,
5859-
sctx->cur_inode_size);
5860-
if (ret < 0)
5861-
goto out;
5870+
if (need_truncate) {
5871+
ret = send_truncate(sctx, sctx->cur_ino,
5872+
sctx->cur_inode_gen,
5873+
sctx->cur_inode_size);
5874+
if (ret < 0)
5875+
goto out;
5876+
}
58625877
}
58635878

58645879
if (need_chown) {
@@ -5912,6 +5927,7 @@ static int changed_inode(struct send_ctx *sctx,
59125927
sctx->cur_ino = key->objectid;
59135928
sctx->cur_inode_new_gen = 0;
59145929
sctx->cur_inode_last_extent = (u64)-1;
5930+
sctx->cur_inode_next_write_offset = 0;
59155931

59165932
/*
59175933
* Set send_progress to current inode. This will tell all get_cur_xxx

0 commit comments

Comments
 (0)