btrfs: get the next extent map during fiemap/lseek more efficiently
authorFilipe Manana <fdmanana@suse.com>
Tue, 11 Oct 2022 12:16:54 +0000 (13:16 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 26 Apr 2023 12:28:38 +0000 (14:28 +0200)
commit d47704bd1c78c85831561bcf701b90dd66f811b2 upstream.

At find_delalloc_subrange(), when we need to get the next extent map, we
do a full search on the extent map tree (a red black tree). This is fine
but it's a lot more efficient to simply use rb_next(), which typically
requires iterating over less nodes of the tree and never needs to compare
the ranges of nodes with the one we are looking for.

So add a public helper to extent_map.{h,c} to get the extent map that
immediately follows another extent map, using rb_next(), and use that
helper at find_delalloc_subrange().

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/btrfs/extent_map.c
fs/btrfs/extent_map.h
fs/btrfs/file.c

index b8ae02a..4abbe4b 100644 (file)
@@ -523,7 +523,7 @@ void replace_extent_mapping(struct extent_map_tree *tree,
        setup_extent_mapping(tree, new, modified);
 }
 
-static struct extent_map *next_extent_map(struct extent_map *em)
+static struct extent_map *next_extent_map(const struct extent_map *em)
 {
        struct rb_node *next;
 
@@ -533,6 +533,35 @@ static struct extent_map *next_extent_map(struct extent_map *em)
        return container_of(next, struct extent_map, rb_node);
 }
 
+/*
+ * Get the extent map that immediately follows another one.
+ *
+ * @tree:       The extent map tree that the extent map belong to.
+ *              Holding read or write access on the tree's lock is required.
+ * @em:         An extent map from the given tree. The caller must ensure that
+ *              between getting @em and between calling this function, the
+ *              extent map @em is not removed from the tree - for example, by
+ *              holding the tree's lock for the duration of those 2 operations.
+ *
+ * Returns the extent map that immediately follows @em, or NULL if @em is the
+ * last extent map in the tree.
+ */
+struct extent_map *btrfs_next_extent_map(const struct extent_map_tree *tree,
+                                        const struct extent_map *em)
+{
+       struct extent_map *next;
+
+       /* The lock must be acquired either in read mode or write mode. */
+       lockdep_assert_held(&tree->lock);
+       ASSERT(extent_map_in_tree(em));
+
+       next = next_extent_map(em);
+       if (next)
+               refcount_inc(&next->refs);
+
+       return next;
+}
+
 static struct extent_map *prev_extent_map(struct extent_map *em)
 {
        struct rb_node *prev;
index ad31186..68d3f2c 100644 (file)
@@ -87,6 +87,8 @@ static inline u64 extent_map_block_end(struct extent_map *em)
 void extent_map_tree_init(struct extent_map_tree *tree);
 struct extent_map *lookup_extent_mapping(struct extent_map_tree *tree,
                                         u64 start, u64 len);
+struct extent_map *btrfs_next_extent_map(const struct extent_map_tree *tree,
+                                        const struct extent_map *em);
 int add_extent_mapping(struct extent_map_tree *tree,
                       struct extent_map *em, int modified);
 void remove_extent_mapping(struct extent_map_tree *tree, struct extent_map *em);
index 1bda59c..77202ad 100644 (file)
@@ -3248,40 +3248,50 @@ static bool find_delalloc_subrange(struct btrfs_inode *inode, u64 start, u64 end
         */
        read_lock(&em_tree->lock);
        em = lookup_extent_mapping(em_tree, start, len);
-       read_unlock(&em_tree->lock);
+       if (!em) {
+               read_unlock(&em_tree->lock);
+               return (delalloc_len > 0);
+       }
 
        /* extent_map_end() returns a non-inclusive end offset. */
-       em_end = em ? extent_map_end(em) : 0;
+       em_end = extent_map_end(em);
 
        /*
         * If we have a hole/prealloc extent map, check the next one if this one
         * ends before our range's end.
         */
-       if (em && (em->block_start == EXTENT_MAP_HOLE ||
-                  test_bit(EXTENT_FLAG_PREALLOC, &em->flags)) && em_end < end) {
+       if ((em->block_start == EXTENT_MAP_HOLE ||
+            test_bit(EXTENT_FLAG_PREALLOC, &em->flags)) && em_end < end) {
                struct extent_map *next_em;
 
-               read_lock(&em_tree->lock);
-               next_em = lookup_extent_mapping(em_tree, em_end, len - em_end);
-               read_unlock(&em_tree->lock);
-
+               next_em = btrfs_next_extent_map(em_tree, em);
                free_extent_map(em);
-               em_end = next_em ? extent_map_end(next_em) : 0;
+
+               /*
+                * There's no next extent map or the next one starts beyond our
+                * range, return the range found in the io tree (if any).
+                */
+               if (!next_em || next_em->start > end) {
+                       read_unlock(&em_tree->lock);
+                       free_extent_map(next_em);
+                       return (delalloc_len > 0);
+               }
+
+               em_end = extent_map_end(next_em);
                em = next_em;
        }
 
-       if (em && (em->block_start == EXTENT_MAP_HOLE ||
-                  test_bit(EXTENT_FLAG_PREALLOC, &em->flags))) {
-               free_extent_map(em);
-               em = NULL;
-       }
+       read_unlock(&em_tree->lock);
 
        /*
-        * No extent map or one for a hole or prealloc extent. Use the delalloc
-        * range we found in the io tree if we have one.
+        * We have a hole or prealloc extent that ends at or beyond our range's
+        * end, return the range found in the io tree (if any).
         */
-       if (!em)
+       if (em->block_start == EXTENT_MAP_HOLE ||
+           test_bit(EXTENT_FLAG_PREALLOC, &em->flags)) {
+               free_extent_map(em);
                return (delalloc_len > 0);
+       }
 
        /*
         * We don't have any range as EXTENT_DELALLOC in the io tree, so the