diff mbox series

btrfs: preallocate ulist memory for qgroup rsv

Message ID 353894f3538d150605f0f28636249c332c1bd84a.1719009048.git.boris@bur.io (mailing list archive)
State New, archived
Headers show
Series btrfs: preallocate ulist memory for qgroup rsv | expand

Commit Message

Boris Burkov June 21, 2024, 10:31 p.m. UTC
When qgroups are enabled, during data reservation, we allocate the
ulist_nodes that track the exact reserved extents with GFP_ATOMIC
unconditionally. This is unnecessary, and we can follow the model
already employed by the struct extent_state we preallocate in the non
qgroups case, which should reduce the risk of allocation failures with
GFP_ATOMIC.

Add a prealloc node to struct ulist which ulist_add will grab when it is
present, and try to allocate it before taking the tree lock while we can
still take advantage of a less strict gfp mask. The lifetime of that
node belongs to the new prealloc field, until it is used, at which point
it belongs to the ulist linked list.

Signed-off-by: Boris Burkov <boris@bur.io>
---
 fs/btrfs/extent-io-tree.c |  4 ++++
 fs/btrfs/extent_io.h      |  5 +++++
 fs/btrfs/ulist.c          | 21 ++++++++++++++++++---
 fs/btrfs/ulist.h          |  2 ++
 4 files changed, 29 insertions(+), 3 deletions(-)

Comments

Qu Wenruo June 21, 2024, 11:15 p.m. UTC | #1
在 2024/6/22 08:01, Boris Burkov 写道:
> When qgroups are enabled, during data reservation, we allocate the
> ulist_nodes that track the exact reserved extents with GFP_ATOMIC
> unconditionally. This is unnecessary, and we can follow the model
> already employed by the struct extent_state we preallocate in the non
> qgroups case, which should reduce the risk of allocation failures with
> GFP_ATOMIC.
>
> Add a prealloc node to struct ulist which ulist_add will grab when it is
> present, and try to allocate it before taking the tree lock while we can
> still take advantage of a less strict gfp mask. The lifetime of that
> node belongs to the new prealloc field, until it is used, at which point
> it belongs to the ulist linked list.
>
> Signed-off-by: Boris Burkov <boris@bur.io>

Reviewed-by: Qu Wenruo <wqu@suse.com>

Thanks,
Qu
> ---
>   fs/btrfs/extent-io-tree.c |  4 ++++
>   fs/btrfs/extent_io.h      |  5 +++++
>   fs/btrfs/ulist.c          | 21 ++++++++++++++++++---
>   fs/btrfs/ulist.h          |  2 ++
>   4 files changed, 29 insertions(+), 3 deletions(-)
>
> diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
> index ed2cfc3d5d8a..c54c5d7a5cd5 100644
> --- a/fs/btrfs/extent-io-tree.c
> +++ b/fs/btrfs/extent-io-tree.c
> @@ -4,6 +4,7 @@
>   #include <trace/events/btrfs.h>
>   #include "messages.h"
>   #include "ctree.h"
> +#include "extent_io.h"
>   #include "extent-io-tree.h"
>   #include "btrfs_inode.h"
>
> @@ -1084,6 +1085,9 @@ static int __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
>   		 */
>   		prealloc = alloc_extent_state(mask);
>   	}
> +	/* Optimistically preallocate the extent changeset ulist node. */
> +	if (changeset)
> +		extent_changeset_prealloc(changeset, mask);
>
>   	spin_lock(&tree->lock);
>   	if (cached_state && *cached_state) {
> diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
> index 96c6bbdcd5d6..8b33cfea6b75 100644
> --- a/fs/btrfs/extent_io.h
> +++ b/fs/btrfs/extent_io.h
> @@ -215,6 +215,11 @@ static inline struct extent_changeset *extent_changeset_alloc(void)
>   	return ret;
>   }
>
> +static inline void extent_changeset_prealloc(struct extent_changeset *changeset, gfp_t gfp_mask)
> +{
> +	ulist_prealloc(&changeset->range_changed, gfp_mask);
> +}
> +
>   static inline void extent_changeset_release(struct extent_changeset *changeset)
>   {
>   	if (!changeset)
> diff --git a/fs/btrfs/ulist.c b/fs/btrfs/ulist.c
> index 183863f4bfa4..f35d3e93996b 100644
> --- a/fs/btrfs/ulist.c
> +++ b/fs/btrfs/ulist.c
> @@ -50,6 +50,7 @@ void ulist_init(struct ulist *ulist)
>   	INIT_LIST_HEAD(&ulist->nodes);
>   	ulist->root = RB_ROOT;
>   	ulist->nnodes = 0;
> +	ulist->prealloc = NULL;
>   }
>
>   /*
> @@ -68,6 +69,8 @@ void ulist_release(struct ulist *ulist)
>   	list_for_each_entry_safe(node, next, &ulist->nodes, list) {
>   		kfree(node);
>   	}
> +	kfree(ulist->prealloc);
> +	ulist->prealloc = NULL;
>   	ulist->root = RB_ROOT;
>   	INIT_LIST_HEAD(&ulist->nodes);
>   }
> @@ -105,6 +108,12 @@ struct ulist *ulist_alloc(gfp_t gfp_mask)
>   	return ulist;
>   }
>
> +void ulist_prealloc(struct ulist *ulist, gfp_t gfp_mask)
> +{
> +	if (ulist && !ulist->prealloc)
> +		ulist->prealloc = kzalloc(sizeof(*ulist->prealloc), gfp_mask);
> +}
> +
>   /*
>    * Free dynamically allocated ulist.
>    *
> @@ -206,9 +215,15 @@ int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
>   			*old_aux = node->aux;
>   		return 0;
>   	}
> -	node = kmalloc(sizeof(*node), gfp_mask);
> -	if (!node)
> -		return -ENOMEM;
> +
> +	if (ulist->prealloc) {
> +		node = ulist->prealloc;
> +		ulist->prealloc = NULL;
> +	} else {
> +		node = kmalloc(sizeof(*node), gfp_mask);
> +		if (!node)
> +			return -ENOMEM;
> +	}
>
>   	node->val = val;
>   	node->aux = aux;
> diff --git a/fs/btrfs/ulist.h b/fs/btrfs/ulist.h
> index 8e200fe1a2dd..c62a372f1462 100644
> --- a/fs/btrfs/ulist.h
> +++ b/fs/btrfs/ulist.h
> @@ -41,12 +41,14 @@ struct ulist {
>
>   	struct list_head nodes;
>   	struct rb_root root;
> +	struct ulist_node *prealloc;
>   };
>
>   void ulist_init(struct ulist *ulist);
>   void ulist_release(struct ulist *ulist);
>   void ulist_reinit(struct ulist *ulist);
>   struct ulist *ulist_alloc(gfp_t gfp_mask);
> +void ulist_prealloc(struct ulist *ulist, gfp_t mask);
>   void ulist_free(struct ulist *ulist);
>   int ulist_add(struct ulist *ulist, u64 val, u64 aux, gfp_t gfp_mask);
>   int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
Filipe Manana June 23, 2024, 11:51 a.m. UTC | #2
On Fri, Jun 21, 2024 at 11:41 PM Boris Burkov <boris@bur.io> wrote:
>
> When qgroups are enabled, during data reservation, we allocate the
> ulist_nodes that track the exact reserved extents with GFP_ATOMIC
> unconditionally. This is unnecessary, and we can follow the model
> already employed by the struct extent_state we preallocate in the non
> qgroups case, which should reduce the risk of allocation failures with
> GFP_ATOMIC.
>
> Add a prealloc node to struct ulist which ulist_add will grab when it is
> present, and try to allocate it before taking the tree lock while we can
> still take advantage of a less strict gfp mask. The lifetime of that
> node belongs to the new prealloc field, until it is used, at which point
> it belongs to the ulist linked list.
>
> Signed-off-by: Boris Burkov <boris@bur.io>

Reviewed-by: Filipe Manana <fdmanana@suse.com>

Looks good, thanks.

> ---
>  fs/btrfs/extent-io-tree.c |  4 ++++
>  fs/btrfs/extent_io.h      |  5 +++++
>  fs/btrfs/ulist.c          | 21 ++++++++++++++++++---
>  fs/btrfs/ulist.h          |  2 ++
>  4 files changed, 29 insertions(+), 3 deletions(-)
>
> diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
> index ed2cfc3d5d8a..c54c5d7a5cd5 100644
> --- a/fs/btrfs/extent-io-tree.c
> +++ b/fs/btrfs/extent-io-tree.c
> @@ -4,6 +4,7 @@
>  #include <trace/events/btrfs.h>
>  #include "messages.h"
>  #include "ctree.h"
> +#include "extent_io.h"
>  #include "extent-io-tree.h"
>  #include "btrfs_inode.h"
>
> @@ -1084,6 +1085,9 @@ static int __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
>                  */
>                 prealloc = alloc_extent_state(mask);
>         }
> +       /* Optimistically preallocate the extent changeset ulist node. */
> +       if (changeset)
> +               extent_changeset_prealloc(changeset, mask);
>
>         spin_lock(&tree->lock);
>         if (cached_state && *cached_state) {
> diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
> index 96c6bbdcd5d6..8b33cfea6b75 100644
> --- a/fs/btrfs/extent_io.h
> +++ b/fs/btrfs/extent_io.h
> @@ -215,6 +215,11 @@ static inline struct extent_changeset *extent_changeset_alloc(void)
>         return ret;
>  }
>
> +static inline void extent_changeset_prealloc(struct extent_changeset *changeset, gfp_t gfp_mask)
> +{
> +       ulist_prealloc(&changeset->range_changed, gfp_mask);
> +}
> +
>  static inline void extent_changeset_release(struct extent_changeset *changeset)
>  {
>         if (!changeset)
> diff --git a/fs/btrfs/ulist.c b/fs/btrfs/ulist.c
> index 183863f4bfa4..f35d3e93996b 100644
> --- a/fs/btrfs/ulist.c
> +++ b/fs/btrfs/ulist.c
> @@ -50,6 +50,7 @@ void ulist_init(struct ulist *ulist)
>         INIT_LIST_HEAD(&ulist->nodes);
>         ulist->root = RB_ROOT;
>         ulist->nnodes = 0;
> +       ulist->prealloc = NULL;
>  }
>
>  /*
> @@ -68,6 +69,8 @@ void ulist_release(struct ulist *ulist)
>         list_for_each_entry_safe(node, next, &ulist->nodes, list) {
>                 kfree(node);
>         }
> +       kfree(ulist->prealloc);
> +       ulist->prealloc = NULL;
>         ulist->root = RB_ROOT;
>         INIT_LIST_HEAD(&ulist->nodes);
>  }
> @@ -105,6 +108,12 @@ struct ulist *ulist_alloc(gfp_t gfp_mask)
>         return ulist;
>  }
>
> +void ulist_prealloc(struct ulist *ulist, gfp_t gfp_mask)
> +{
> +       if (ulist && !ulist->prealloc)
> +               ulist->prealloc = kzalloc(sizeof(*ulist->prealloc), gfp_mask);
> +}
> +
>  /*
>   * Free dynamically allocated ulist.
>   *
> @@ -206,9 +215,15 @@ int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
>                         *old_aux = node->aux;
>                 return 0;
>         }
> -       node = kmalloc(sizeof(*node), gfp_mask);
> -       if (!node)
> -               return -ENOMEM;
> +
> +       if (ulist->prealloc) {
> +               node = ulist->prealloc;
> +               ulist->prealloc = NULL;
> +       } else {
> +               node = kmalloc(sizeof(*node), gfp_mask);
> +               if (!node)
> +                       return -ENOMEM;
> +       }
>
>         node->val = val;
>         node->aux = aux;
> diff --git a/fs/btrfs/ulist.h b/fs/btrfs/ulist.h
> index 8e200fe1a2dd..c62a372f1462 100644
> --- a/fs/btrfs/ulist.h
> +++ b/fs/btrfs/ulist.h
> @@ -41,12 +41,14 @@ struct ulist {
>
>         struct list_head nodes;
>         struct rb_root root;
> +       struct ulist_node *prealloc;
>  };
>
>  void ulist_init(struct ulist *ulist);
>  void ulist_release(struct ulist *ulist);
>  void ulist_reinit(struct ulist *ulist);
>  struct ulist *ulist_alloc(gfp_t gfp_mask);
> +void ulist_prealloc(struct ulist *ulist, gfp_t mask);
>  void ulist_free(struct ulist *ulist);
>  int ulist_add(struct ulist *ulist, u64 val, u64 aux, gfp_t gfp_mask);
>  int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> --
> 2.45.2
>
>
Filipe Manana June 23, 2024, 12:15 p.m. UTC | #3
On Fri, Jun 21, 2024 at 11:41 PM Boris Burkov <boris@bur.io> wrote:
>
> When qgroups are enabled, during data reservation, we allocate the
> ulist_nodes that track the exact reserved extents with GFP_ATOMIC
> unconditionally. This is unnecessary, and we can follow the model
> already employed by the struct extent_state we preallocate in the non
> qgroups case, which should reduce the risk of allocation failures with
> GFP_ATOMIC.
>
> Add a prealloc node to struct ulist which ulist_add will grab when it is
> present, and try to allocate it before taking the tree lock while we can
> still take advantage of a less strict gfp mask. The lifetime of that
> node belongs to the new prealloc field, until it is used, at which point
> it belongs to the ulist linked list.
>
> Signed-off-by: Boris Burkov <boris@bur.io>
> ---
>  fs/btrfs/extent-io-tree.c |  4 ++++
>  fs/btrfs/extent_io.h      |  5 +++++
>  fs/btrfs/ulist.c          | 21 ++++++++++++++++++---
>  fs/btrfs/ulist.h          |  2 ++
>  4 files changed, 29 insertions(+), 3 deletions(-)
>
> diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
> index ed2cfc3d5d8a..c54c5d7a5cd5 100644
> --- a/fs/btrfs/extent-io-tree.c
> +++ b/fs/btrfs/extent-io-tree.c
> @@ -4,6 +4,7 @@
>  #include <trace/events/btrfs.h>
>  #include "messages.h"
>  #include "ctree.h"
> +#include "extent_io.h"
>  #include "extent-io-tree.h"
>  #include "btrfs_inode.h"
>
> @@ -1084,6 +1085,9 @@ static int __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
>                  */
>                 prealloc = alloc_extent_state(mask);
>         }
> +       /* Optimistically preallocate the extent changeset ulist node. */
> +       if (changeset)
> +               extent_changeset_prealloc(changeset, mask);
>
>         spin_lock(&tree->lock);
>         if (cached_state && *cached_state) {
> diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
> index 96c6bbdcd5d6..8b33cfea6b75 100644
> --- a/fs/btrfs/extent_io.h
> +++ b/fs/btrfs/extent_io.h
> @@ -215,6 +215,11 @@ static inline struct extent_changeset *extent_changeset_alloc(void)
>         return ret;
>  }
>
> +static inline void extent_changeset_prealloc(struct extent_changeset *changeset, gfp_t gfp_mask)
> +{
> +       ulist_prealloc(&changeset->range_changed, gfp_mask);
> +}
> +
>  static inline void extent_changeset_release(struct extent_changeset *changeset)
>  {
>         if (!changeset)
> diff --git a/fs/btrfs/ulist.c b/fs/btrfs/ulist.c
> index 183863f4bfa4..f35d3e93996b 100644
> --- a/fs/btrfs/ulist.c
> +++ b/fs/btrfs/ulist.c
> @@ -50,6 +50,7 @@ void ulist_init(struct ulist *ulist)
>         INIT_LIST_HEAD(&ulist->nodes);
>         ulist->root = RB_ROOT;
>         ulist->nnodes = 0;
> +       ulist->prealloc = NULL;
>  }
>
>  /*
> @@ -68,6 +69,8 @@ void ulist_release(struct ulist *ulist)
>         list_for_each_entry_safe(node, next, &ulist->nodes, list) {
>                 kfree(node);
>         }
> +       kfree(ulist->prealloc);
> +       ulist->prealloc = NULL;
>         ulist->root = RB_ROOT;
>         INIT_LIST_HEAD(&ulist->nodes);
>  }
> @@ -105,6 +108,12 @@ struct ulist *ulist_alloc(gfp_t gfp_mask)
>         return ulist;
>  }
>
> +void ulist_prealloc(struct ulist *ulist, gfp_t gfp_mask)
> +{
> +       if (ulist && !ulist->prealloc)

Btw, why the non-NULL ulist check?
There are no callers that pass a NULL ulist, and that pattern/check is
only used for free functions.

Otherwise it looks fine. Thanks.


> +               ulist->prealloc = kzalloc(sizeof(*ulist->prealloc), gfp_mask);
> +}
> +
>  /*
>   * Free dynamically allocated ulist.
>   *
> @@ -206,9 +215,15 @@ int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
>                         *old_aux = node->aux;
>                 return 0;
>         }
> -       node = kmalloc(sizeof(*node), gfp_mask);
> -       if (!node)
> -               return -ENOMEM;
> +
> +       if (ulist->prealloc) {
> +               node = ulist->prealloc;
> +               ulist->prealloc = NULL;
> +       } else {
> +               node = kmalloc(sizeof(*node), gfp_mask);
> +               if (!node)
> +                       return -ENOMEM;
> +       }
>
>         node->val = val;
>         node->aux = aux;
> diff --git a/fs/btrfs/ulist.h b/fs/btrfs/ulist.h
> index 8e200fe1a2dd..c62a372f1462 100644
> --- a/fs/btrfs/ulist.h
> +++ b/fs/btrfs/ulist.h
> @@ -41,12 +41,14 @@ struct ulist {
>
>         struct list_head nodes;
>         struct rb_root root;
> +       struct ulist_node *prealloc;
>  };
>
>  void ulist_init(struct ulist *ulist);
>  void ulist_release(struct ulist *ulist);
>  void ulist_reinit(struct ulist *ulist);
>  struct ulist *ulist_alloc(gfp_t gfp_mask);
> +void ulist_prealloc(struct ulist *ulist, gfp_t mask);
>  void ulist_free(struct ulist *ulist);
>  int ulist_add(struct ulist *ulist, u64 val, u64 aux, gfp_t gfp_mask);
>  int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> --
> 2.45.2
>
>
Filipe Manana June 25, 2024, 5:11 p.m. UTC | #4
On Sun, Jun 23, 2024 at 1:15 PM Filipe Manana <fdmanana@kernel.org> wrote:
>
> On Fri, Jun 21, 2024 at 11:41 PM Boris Burkov <boris@bur.io> wrote:
> >
> > When qgroups are enabled, during data reservation, we allocate the
> > ulist_nodes that track the exact reserved extents with GFP_ATOMIC
> > unconditionally. This is unnecessary, and we can follow the model
> > already employed by the struct extent_state we preallocate in the non
> > qgroups case, which should reduce the risk of allocation failures with
> > GFP_ATOMIC.
> >
> > Add a prealloc node to struct ulist which ulist_add will grab when it is
> > present, and try to allocate it before taking the tree lock while we can
> > still take advantage of a less strict gfp mask. The lifetime of that
> > node belongs to the new prealloc field, until it is used, at which point
> > it belongs to the ulist linked list.
> >
> > Signed-off-by: Boris Burkov <boris@bur.io>
> > ---
> >  fs/btrfs/extent-io-tree.c |  4 ++++
> >  fs/btrfs/extent_io.h      |  5 +++++
> >  fs/btrfs/ulist.c          | 21 ++++++++++++++++++---
> >  fs/btrfs/ulist.h          |  2 ++
> >  4 files changed, 29 insertions(+), 3 deletions(-)
> >
> > diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
> > index ed2cfc3d5d8a..c54c5d7a5cd5 100644
> > --- a/fs/btrfs/extent-io-tree.c
> > +++ b/fs/btrfs/extent-io-tree.c
> > @@ -4,6 +4,7 @@
> >  #include <trace/events/btrfs.h>
> >  #include "messages.h"
> >  #include "ctree.h"
> > +#include "extent_io.h"
> >  #include "extent-io-tree.h"
> >  #include "btrfs_inode.h"
> >
> > @@ -1084,6 +1085,9 @@ static int __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
> >                  */
> >                 prealloc = alloc_extent_state(mask);
> >         }
> > +       /* Optimistically preallocate the extent changeset ulist node. */
> > +       if (changeset)
> > +               extent_changeset_prealloc(changeset, mask);
> >
> >         spin_lock(&tree->lock);
> >         if (cached_state && *cached_state) {
> > diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
> > index 96c6bbdcd5d6..8b33cfea6b75 100644
> > --- a/fs/btrfs/extent_io.h
> > +++ b/fs/btrfs/extent_io.h
> > @@ -215,6 +215,11 @@ static inline struct extent_changeset *extent_changeset_alloc(void)
> >         return ret;
> >  }
> >
> > +static inline void extent_changeset_prealloc(struct extent_changeset *changeset, gfp_t gfp_mask)
> > +{
> > +       ulist_prealloc(&changeset->range_changed, gfp_mask);
> > +}
> > +
> >  static inline void extent_changeset_release(struct extent_changeset *changeset)
> >  {
> >         if (!changeset)
> > diff --git a/fs/btrfs/ulist.c b/fs/btrfs/ulist.c
> > index 183863f4bfa4..f35d3e93996b 100644
> > --- a/fs/btrfs/ulist.c
> > +++ b/fs/btrfs/ulist.c
> > @@ -50,6 +50,7 @@ void ulist_init(struct ulist *ulist)
> >         INIT_LIST_HEAD(&ulist->nodes);
> >         ulist->root = RB_ROOT;
> >         ulist->nnodes = 0;
> > +       ulist->prealloc = NULL;
> >  }
> >
> >  /*
> > @@ -68,6 +69,8 @@ void ulist_release(struct ulist *ulist)
> >         list_for_each_entry_safe(node, next, &ulist->nodes, list) {
> >                 kfree(node);
> >         }
> > +       kfree(ulist->prealloc);
> > +       ulist->prealloc = NULL;
> >         ulist->root = RB_ROOT;
> >         INIT_LIST_HEAD(&ulist->nodes);
> >  }
> > @@ -105,6 +108,12 @@ struct ulist *ulist_alloc(gfp_t gfp_mask)
> >         return ulist;
> >  }
> >
> > +void ulist_prealloc(struct ulist *ulist, gfp_t gfp_mask)
> > +{
> > +       if (ulist && !ulist->prealloc)
>
> Btw, why the non-NULL ulist check?
> There are no callers that pass a NULL ulist, and that pattern/check is
> only used for free functions.

Boris?

>
> Otherwise it looks fine. Thanks.
>
>
> > +               ulist->prealloc = kzalloc(sizeof(*ulist->prealloc), gfp_mask);
> > +}
> > +
> >  /*
> >   * Free dynamically allocated ulist.
> >   *
> > @@ -206,9 +215,15 @@ int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> >                         *old_aux = node->aux;
> >                 return 0;
> >         }
> > -       node = kmalloc(sizeof(*node), gfp_mask);
> > -       if (!node)
> > -               return -ENOMEM;
> > +
> > +       if (ulist->prealloc) {
> > +               node = ulist->prealloc;
> > +               ulist->prealloc = NULL;
> > +       } else {
> > +               node = kmalloc(sizeof(*node), gfp_mask);
> > +               if (!node)
> > +                       return -ENOMEM;
> > +       }
> >
> >         node->val = val;
> >         node->aux = aux;
> > diff --git a/fs/btrfs/ulist.h b/fs/btrfs/ulist.h
> > index 8e200fe1a2dd..c62a372f1462 100644
> > --- a/fs/btrfs/ulist.h
> > +++ b/fs/btrfs/ulist.h
> > @@ -41,12 +41,14 @@ struct ulist {
> >
> >         struct list_head nodes;
> >         struct rb_root root;
> > +       struct ulist_node *prealloc;
> >  };
> >
> >  void ulist_init(struct ulist *ulist);
> >  void ulist_release(struct ulist *ulist);
> >  void ulist_reinit(struct ulist *ulist);
> >  struct ulist *ulist_alloc(gfp_t gfp_mask);
> > +void ulist_prealloc(struct ulist *ulist, gfp_t mask);
> >  void ulist_free(struct ulist *ulist);
> >  int ulist_add(struct ulist *ulist, u64 val, u64 aux, gfp_t gfp_mask);
> >  int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> > --
> > 2.45.2
> >
> >
Boris Burkov June 25, 2024, 5:20 p.m. UTC | #5
On Tue, Jun 25, 2024 at 06:11:55PM +0100, Filipe Manana wrote:
> On Sun, Jun 23, 2024 at 1:15 PM Filipe Manana <fdmanana@kernel.org> wrote:
> >
> > On Fri, Jun 21, 2024 at 11:41 PM Boris Burkov <boris@bur.io> wrote:
> > >
> > > When qgroups are enabled, during data reservation, we allocate the
> > > ulist_nodes that track the exact reserved extents with GFP_ATOMIC
> > > unconditionally. This is unnecessary, and we can follow the model
> > > already employed by the struct extent_state we preallocate in the non
> > > qgroups case, which should reduce the risk of allocation failures with
> > > GFP_ATOMIC.
> > >
> > > Add a prealloc node to struct ulist which ulist_add will grab when it is
> > > present, and try to allocate it before taking the tree lock while we can
> > > still take advantage of a less strict gfp mask. The lifetime of that
> > > node belongs to the new prealloc field, until it is used, at which point
> > > it belongs to the ulist linked list.
> > >
> > > Signed-off-by: Boris Burkov <boris@bur.io>
> > > ---
> > >  fs/btrfs/extent-io-tree.c |  4 ++++
> > >  fs/btrfs/extent_io.h      |  5 +++++
> > >  fs/btrfs/ulist.c          | 21 ++++++++++++++++++---
> > >  fs/btrfs/ulist.h          |  2 ++
> > >  4 files changed, 29 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
> > > index ed2cfc3d5d8a..c54c5d7a5cd5 100644
> > > --- a/fs/btrfs/extent-io-tree.c
> > > +++ b/fs/btrfs/extent-io-tree.c
> > > @@ -4,6 +4,7 @@
> > >  #include <trace/events/btrfs.h>
> > >  #include "messages.h"
> > >  #include "ctree.h"
> > > +#include "extent_io.h"
> > >  #include "extent-io-tree.h"
> > >  #include "btrfs_inode.h"
> > >
> > > @@ -1084,6 +1085,9 @@ static int __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
> > >                  */
> > >                 prealloc = alloc_extent_state(mask);
> > >         }
> > > +       /* Optimistically preallocate the extent changeset ulist node. */
> > > +       if (changeset)
> > > +               extent_changeset_prealloc(changeset, mask);
> > >
> > >         spin_lock(&tree->lock);
> > >         if (cached_state && *cached_state) {
> > > diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
> > > index 96c6bbdcd5d6..8b33cfea6b75 100644
> > > --- a/fs/btrfs/extent_io.h
> > > +++ b/fs/btrfs/extent_io.h
> > > @@ -215,6 +215,11 @@ static inline struct extent_changeset *extent_changeset_alloc(void)
> > >         return ret;
> > >  }
> > >
> > > +static inline void extent_changeset_prealloc(struct extent_changeset *changeset, gfp_t gfp_mask)
> > > +{
> > > +       ulist_prealloc(&changeset->range_changed, gfp_mask);
> > > +}
> > > +
> > >  static inline void extent_changeset_release(struct extent_changeset *changeset)
> > >  {
> > >         if (!changeset)
> > > diff --git a/fs/btrfs/ulist.c b/fs/btrfs/ulist.c
> > > index 183863f4bfa4..f35d3e93996b 100644
> > > --- a/fs/btrfs/ulist.c
> > > +++ b/fs/btrfs/ulist.c
> > > @@ -50,6 +50,7 @@ void ulist_init(struct ulist *ulist)
> > >         INIT_LIST_HEAD(&ulist->nodes);
> > >         ulist->root = RB_ROOT;
> > >         ulist->nnodes = 0;
> > > +       ulist->prealloc = NULL;
> > >  }
> > >
> > >  /*
> > > @@ -68,6 +69,8 @@ void ulist_release(struct ulist *ulist)
> > >         list_for_each_entry_safe(node, next, &ulist->nodes, list) {
> > >                 kfree(node);
> > >         }
> > > +       kfree(ulist->prealloc);
> > > +       ulist->prealloc = NULL;
> > >         ulist->root = RB_ROOT;
> > >         INIT_LIST_HEAD(&ulist->nodes);
> > >  }
> > > @@ -105,6 +108,12 @@ struct ulist *ulist_alloc(gfp_t gfp_mask)
> > >         return ulist;
> > >  }
> > >
> > > +void ulist_prealloc(struct ulist *ulist, gfp_t gfp_mask)
> > > +{
> > > +       if (ulist && !ulist->prealloc)
> >
> > Btw, why the non-NULL ulist check?
> > There are no callers that pass a NULL ulist, and that pattern/check is
> > only used for free functions.
> 
> Boris?

Sorry, I thought about this but forgot to reply! Thanks for the reminder :)

I wanted to check ulist->prealloc to avoid leaking the prealloc if some
user prealloced in a loop without knowing that it actually got sinked
into the linked list.
At that point, I also didn't want to make assumptions on whether that
was a safe access to make.

Would you prefer changing it to just: `if (!ulist->prealloc)` ?
I could throw in an ASSERT(ulist) before as well.

Thanks for the review,
Boris

> 
> >
> > Otherwise it looks fine. Thanks.
> >
> >
> > > +               ulist->prealloc = kzalloc(sizeof(*ulist->prealloc), gfp_mask);
> > > +}
> > > +
> > >  /*
> > >   * Free dynamically allocated ulist.
> > >   *
> > > @@ -206,9 +215,15 @@ int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> > >                         *old_aux = node->aux;
> > >                 return 0;
> > >         }
> > > -       node = kmalloc(sizeof(*node), gfp_mask);
> > > -       if (!node)
> > > -               return -ENOMEM;
> > > +
> > > +       if (ulist->prealloc) {
> > > +               node = ulist->prealloc;
> > > +               ulist->prealloc = NULL;
> > > +       } else {
> > > +               node = kmalloc(sizeof(*node), gfp_mask);
> > > +               if (!node)
> > > +                       return -ENOMEM;
> > > +       }
> > >
> > >         node->val = val;
> > >         node->aux = aux;
> > > diff --git a/fs/btrfs/ulist.h b/fs/btrfs/ulist.h
> > > index 8e200fe1a2dd..c62a372f1462 100644
> > > --- a/fs/btrfs/ulist.h
> > > +++ b/fs/btrfs/ulist.h
> > > @@ -41,12 +41,14 @@ struct ulist {
> > >
> > >         struct list_head nodes;
> > >         struct rb_root root;
> > > +       struct ulist_node *prealloc;
> > >  };
> > >
> > >  void ulist_init(struct ulist *ulist);
> > >  void ulist_release(struct ulist *ulist);
> > >  void ulist_reinit(struct ulist *ulist);
> > >  struct ulist *ulist_alloc(gfp_t gfp_mask);
> > > +void ulist_prealloc(struct ulist *ulist, gfp_t mask);
> > >  void ulist_free(struct ulist *ulist);
> > >  int ulist_add(struct ulist *ulist, u64 val, u64 aux, gfp_t gfp_mask);
> > >  int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> > > --
> > > 2.45.2
> > >
> > >
Filipe Manana June 25, 2024, 5:23 p.m. UTC | #6
On Tue, Jun 25, 2024 at 6:21 PM Boris Burkov <boris@bur.io> wrote:
>
> On Tue, Jun 25, 2024 at 06:11:55PM +0100, Filipe Manana wrote:
> > On Sun, Jun 23, 2024 at 1:15 PM Filipe Manana <fdmanana@kernel.org> wrote:
> > >
> > > On Fri, Jun 21, 2024 at 11:41 PM Boris Burkov <boris@bur.io> wrote:
> > > >
> > > > When qgroups are enabled, during data reservation, we allocate the
> > > > ulist_nodes that track the exact reserved extents with GFP_ATOMIC
> > > > unconditionally. This is unnecessary, and we can follow the model
> > > > already employed by the struct extent_state we preallocate in the non
> > > > qgroups case, which should reduce the risk of allocation failures with
> > > > GFP_ATOMIC.
> > > >
> > > > Add a prealloc node to struct ulist which ulist_add will grab when it is
> > > > present, and try to allocate it before taking the tree lock while we can
> > > > still take advantage of a less strict gfp mask. The lifetime of that
> > > > node belongs to the new prealloc field, until it is used, at which point
> > > > it belongs to the ulist linked list.
> > > >
> > > > Signed-off-by: Boris Burkov <boris@bur.io>
> > > > ---
> > > >  fs/btrfs/extent-io-tree.c |  4 ++++
> > > >  fs/btrfs/extent_io.h      |  5 +++++
> > > >  fs/btrfs/ulist.c          | 21 ++++++++++++++++++---
> > > >  fs/btrfs/ulist.h          |  2 ++
> > > >  4 files changed, 29 insertions(+), 3 deletions(-)
> > > >
> > > > diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
> > > > index ed2cfc3d5d8a..c54c5d7a5cd5 100644
> > > > --- a/fs/btrfs/extent-io-tree.c
> > > > +++ b/fs/btrfs/extent-io-tree.c
> > > > @@ -4,6 +4,7 @@
> > > >  #include <trace/events/btrfs.h>
> > > >  #include "messages.h"
> > > >  #include "ctree.h"
> > > > +#include "extent_io.h"
> > > >  #include "extent-io-tree.h"
> > > >  #include "btrfs_inode.h"
> > > >
> > > > @@ -1084,6 +1085,9 @@ static int __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
> > > >                  */
> > > >                 prealloc = alloc_extent_state(mask);
> > > >         }
> > > > +       /* Optimistically preallocate the extent changeset ulist node. */
> > > > +       if (changeset)
> > > > +               extent_changeset_prealloc(changeset, mask);
> > > >
> > > >         spin_lock(&tree->lock);
> > > >         if (cached_state && *cached_state) {
> > > > diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
> > > > index 96c6bbdcd5d6..8b33cfea6b75 100644
> > > > --- a/fs/btrfs/extent_io.h
> > > > +++ b/fs/btrfs/extent_io.h
> > > > @@ -215,6 +215,11 @@ static inline struct extent_changeset *extent_changeset_alloc(void)
> > > >         return ret;
> > > >  }
> > > >
> > > > +static inline void extent_changeset_prealloc(struct extent_changeset *changeset, gfp_t gfp_mask)
> > > > +{
> > > > +       ulist_prealloc(&changeset->range_changed, gfp_mask);
> > > > +}
> > > > +
> > > >  static inline void extent_changeset_release(struct extent_changeset *changeset)
> > > >  {
> > > >         if (!changeset)
> > > > diff --git a/fs/btrfs/ulist.c b/fs/btrfs/ulist.c
> > > > index 183863f4bfa4..f35d3e93996b 100644
> > > > --- a/fs/btrfs/ulist.c
> > > > +++ b/fs/btrfs/ulist.c
> > > > @@ -50,6 +50,7 @@ void ulist_init(struct ulist *ulist)
> > > >         INIT_LIST_HEAD(&ulist->nodes);
> > > >         ulist->root = RB_ROOT;
> > > >         ulist->nnodes = 0;
> > > > +       ulist->prealloc = NULL;
> > > >  }
> > > >
> > > >  /*
> > > > @@ -68,6 +69,8 @@ void ulist_release(struct ulist *ulist)
> > > >         list_for_each_entry_safe(node, next, &ulist->nodes, list) {
> > > >                 kfree(node);
> > > >         }
> > > > +       kfree(ulist->prealloc);
> > > > +       ulist->prealloc = NULL;
> > > >         ulist->root = RB_ROOT;
> > > >         INIT_LIST_HEAD(&ulist->nodes);
> > > >  }
> > > > @@ -105,6 +108,12 @@ struct ulist *ulist_alloc(gfp_t gfp_mask)
> > > >         return ulist;
> > > >  }
> > > >
> > > > +void ulist_prealloc(struct ulist *ulist, gfp_t gfp_mask)
> > > > +{
> > > > +       if (ulist && !ulist->prealloc)
> > >
> > > Btw, why the non-NULL ulist check?
> > > There are no callers that pass a NULL ulist, and that pattern/check is
> > > only used for free functions.
> >
> > Boris?
>
> Sorry, I thought about this but forgot to reply! Thanks for the reminder :)
>
> I wanted to check ulist->prealloc to avoid leaking the prealloc if some
> user prealloced in a loop without knowing that it actually got sinked
> into the linked list.
> At that point, I also didn't want to make assumptions on whether that
> was a safe access to make.
>
> Would you prefer changing it to just: `if (!ulist->prealloc)` ?

Yes, just that.

> I could throw in an ASSERT(ulist) before as well.

I don't think it's useful. The stack trace one gets from a NULL
pointer deref is explicit enough and the ASSERT doesn't add any value.

Thanks.

>
> Thanks for the review,
> Boris
>
> >
> > >
> > > Otherwise it looks fine. Thanks.
> > >
> > >
> > > > +               ulist->prealloc = kzalloc(sizeof(*ulist->prealloc), gfp_mask);
> > > > +}
> > > > +
> > > >  /*
> > > >   * Free dynamically allocated ulist.
> > > >   *
> > > > @@ -206,9 +215,15 @@ int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> > > >                         *old_aux = node->aux;
> > > >                 return 0;
> > > >         }
> > > > -       node = kmalloc(sizeof(*node), gfp_mask);
> > > > -       if (!node)
> > > > -               return -ENOMEM;
> > > > +
> > > > +       if (ulist->prealloc) {
> > > > +               node = ulist->prealloc;
> > > > +               ulist->prealloc = NULL;
> > > > +       } else {
> > > > +               node = kmalloc(sizeof(*node), gfp_mask);
> > > > +               if (!node)
> > > > +                       return -ENOMEM;
> > > > +       }
> > > >
> > > >         node->val = val;
> > > >         node->aux = aux;
> > > > diff --git a/fs/btrfs/ulist.h b/fs/btrfs/ulist.h
> > > > index 8e200fe1a2dd..c62a372f1462 100644
> > > > --- a/fs/btrfs/ulist.h
> > > > +++ b/fs/btrfs/ulist.h
> > > > @@ -41,12 +41,14 @@ struct ulist {
> > > >
> > > >         struct list_head nodes;
> > > >         struct rb_root root;
> > > > +       struct ulist_node *prealloc;
> > > >  };
> > > >
> > > >  void ulist_init(struct ulist *ulist);
> > > >  void ulist_release(struct ulist *ulist);
> > > >  void ulist_reinit(struct ulist *ulist);
> > > >  struct ulist *ulist_alloc(gfp_t gfp_mask);
> > > > +void ulist_prealloc(struct ulist *ulist, gfp_t mask);
> > > >  void ulist_free(struct ulist *ulist);
> > > >  int ulist_add(struct ulist *ulist, u64 val, u64 aux, gfp_t gfp_mask);
> > > >  int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
> > > > --
> > > > 2.45.2
> > > >
> > > >
diff mbox series

Patch

diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c
index ed2cfc3d5d8a..c54c5d7a5cd5 100644
--- a/fs/btrfs/extent-io-tree.c
+++ b/fs/btrfs/extent-io-tree.c
@@ -4,6 +4,7 @@ 
 #include <trace/events/btrfs.h>
 #include "messages.h"
 #include "ctree.h"
+#include "extent_io.h"
 #include "extent-io-tree.h"
 #include "btrfs_inode.h"
 
@@ -1084,6 +1085,9 @@  static int __set_extent_bit(struct extent_io_tree *tree, u64 start, u64 end,
 		 */
 		prealloc = alloc_extent_state(mask);
 	}
+	/* Optimistically preallocate the extent changeset ulist node. */
+	if (changeset)
+		extent_changeset_prealloc(changeset, mask);
 
 	spin_lock(&tree->lock);
 	if (cached_state && *cached_state) {
diff --git a/fs/btrfs/extent_io.h b/fs/btrfs/extent_io.h
index 96c6bbdcd5d6..8b33cfea6b75 100644
--- a/fs/btrfs/extent_io.h
+++ b/fs/btrfs/extent_io.h
@@ -215,6 +215,11 @@  static inline struct extent_changeset *extent_changeset_alloc(void)
 	return ret;
 }
 
+static inline void extent_changeset_prealloc(struct extent_changeset *changeset, gfp_t gfp_mask)
+{
+	ulist_prealloc(&changeset->range_changed, gfp_mask);
+}
+
 static inline void extent_changeset_release(struct extent_changeset *changeset)
 {
 	if (!changeset)
diff --git a/fs/btrfs/ulist.c b/fs/btrfs/ulist.c
index 183863f4bfa4..f35d3e93996b 100644
--- a/fs/btrfs/ulist.c
+++ b/fs/btrfs/ulist.c
@@ -50,6 +50,7 @@  void ulist_init(struct ulist *ulist)
 	INIT_LIST_HEAD(&ulist->nodes);
 	ulist->root = RB_ROOT;
 	ulist->nnodes = 0;
+	ulist->prealloc = NULL;
 }
 
 /*
@@ -68,6 +69,8 @@  void ulist_release(struct ulist *ulist)
 	list_for_each_entry_safe(node, next, &ulist->nodes, list) {
 		kfree(node);
 	}
+	kfree(ulist->prealloc);
+	ulist->prealloc = NULL;
 	ulist->root = RB_ROOT;
 	INIT_LIST_HEAD(&ulist->nodes);
 }
@@ -105,6 +108,12 @@  struct ulist *ulist_alloc(gfp_t gfp_mask)
 	return ulist;
 }
 
+void ulist_prealloc(struct ulist *ulist, gfp_t gfp_mask)
+{
+	if (ulist && !ulist->prealloc)
+		ulist->prealloc = kzalloc(sizeof(*ulist->prealloc), gfp_mask);
+}
+
 /*
  * Free dynamically allocated ulist.
  *
@@ -206,9 +215,15 @@  int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,
 			*old_aux = node->aux;
 		return 0;
 	}
-	node = kmalloc(sizeof(*node), gfp_mask);
-	if (!node)
-		return -ENOMEM;
+
+	if (ulist->prealloc) {
+		node = ulist->prealloc;
+		ulist->prealloc = NULL;
+	} else {
+		node = kmalloc(sizeof(*node), gfp_mask);
+		if (!node)
+			return -ENOMEM;
+	}
 
 	node->val = val;
 	node->aux = aux;
diff --git a/fs/btrfs/ulist.h b/fs/btrfs/ulist.h
index 8e200fe1a2dd..c62a372f1462 100644
--- a/fs/btrfs/ulist.h
+++ b/fs/btrfs/ulist.h
@@ -41,12 +41,14 @@  struct ulist {
 
 	struct list_head nodes;
 	struct rb_root root;
+	struct ulist_node *prealloc;
 };
 
 void ulist_init(struct ulist *ulist);
 void ulist_release(struct ulist *ulist);
 void ulist_reinit(struct ulist *ulist);
 struct ulist *ulist_alloc(gfp_t gfp_mask);
+void ulist_prealloc(struct ulist *ulist, gfp_t mask);
 void ulist_free(struct ulist *ulist);
 int ulist_add(struct ulist *ulist, u64 val, u64 aux, gfp_t gfp_mask);
 int ulist_add_merge(struct ulist *ulist, u64 val, u64 aux,