Commit d47704bd authored by Filipe Manana's avatar Filipe Manana Committed by David Sterba
Browse files

btrfs: get the next extent map during fiemap/lseek more efficiently



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: default avatarFilipe Manana <fdmanana@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent 88074c8b
Loading
Loading
Loading
Loading
+30 −1
Original line number Diff line number Diff line
@@ -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;
+2 −0
Original line number Diff line number Diff line
@@ -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);
+27 −17
Original line number Diff line number Diff line
@@ -3569,40 +3569,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);
	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 ||
	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);
		next_em = btrfs_next_extent_map(em_tree, em);
		free_extent_map(em);

		/*
		 * 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);
		}

		free_extent_map(em);
		em_end = next_em ? extent_map_end(next_em) : 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