xfs: improve shortform attr performance [2/3]

Improve performance of the xattr (and parent pointer) code when the
 attr structure is in short format and we can therefore perform all
 updates in a single transaction.  Avoiding the attr intent code brings
 a very nice speedup in those operations.
 
 With a bit of luck, this should all go splendidly.
 
 Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQQ2qTKExjcn+O1o2YRKO3ySh0YRpgUCaXbguQAKCRBKO3ySh0YR
 pkGhAP4q0606NWz+XcF+5f3KlehLBOnpmnozVvudVMCd1rCmpgD9HecarQThh0VI
 ZHo7LrKQpl+jrg0fhuKcbocQzxGNpgI=
 =2NAV
 -----END PGP SIGNATURE-----

Merge tag 'attr-pptr-speedup-7.0_2026-01-25' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux into xfs-7.0-merge

xfs: improve shortform attr performance [2/3]

Improve performance of the xattr (and parent pointer) code when the
attr structure is in short format and we can therefore perform all
updates in a single transaction.  Avoiding the attr intent code brings
a very nice speedup in those operations.

With a bit of luck, this should all go splendidly.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
This commit is contained in:
Carlos Maiolino 2026-01-28 10:16:12 +01:00
commit c04ed39d85
6 changed files with 157 additions and 17 deletions

View file

@ -350,16 +350,14 @@ xfs_attr_set_resv(
*/
STATIC int
xfs_attr_try_sf_addname(
struct xfs_inode *dp,
struct xfs_da_args *args)
{
int error;
/*
* Build initial attribute list (if required).
*/
if (dp->i_af.if_format == XFS_DINODE_FMT_EXTENTS)
if (args->dp->i_af.if_format == XFS_DINODE_FMT_EXTENTS)
xfs_attr_shortform_create(args);
error = xfs_attr_shortform_addname(args);
@ -371,9 +369,9 @@ xfs_attr_try_sf_addname(
* NOTE: this is also the error path (EEXIST, etc).
*/
if (!error)
xfs_trans_ichgtime(args->trans, dp, XFS_ICHGTIME_CHG);
xfs_trans_ichgtime(args->trans, args->dp, XFS_ICHGTIME_CHG);
if (xfs_has_wsync(dp->i_mount))
if (xfs_has_wsync(args->dp->i_mount))
xfs_trans_set_sync(args->trans);
return error;
@ -384,10 +382,9 @@ xfs_attr_sf_addname(
struct xfs_attr_intent *attr)
{
struct xfs_da_args *args = attr->xattri_da_args;
struct xfs_inode *dp = args->dp;
int error = 0;
error = xfs_attr_try_sf_addname(dp, args);
error = xfs_attr_try_sf_addname(args);
if (error != -ENOSPC) {
ASSERT(!error || error == -EEXIST);
attr->xattri_dela_state = XFS_DAS_DONE;
@ -1031,6 +1028,95 @@ trans_cancel:
return error;
}
/*
* Decide if it is theoretically possible to try to bypass the attr intent
* mechanism for better performance. Other constraints (e.g. available space
* in the existing structure) are not considered here.
*/
static inline bool
xfs_attr_can_shortcut(
const struct xfs_inode *ip)
{
return xfs_inode_has_attr_fork(ip) && xfs_attr_is_shortform(ip);
}
/* Try to set an attr in one transaction or fall back to attr intents. */
int
xfs_attr_setname(
struct xfs_da_args *args,
int rmt_blks)
{
int error;
if (!rmt_blks && xfs_attr_can_shortcut(args->dp)) {
args->op_flags |= XFS_DA_OP_ADDNAME;
error = xfs_attr_try_sf_addname(args);
if (error != -ENOSPC)
return error;
}
xfs_attr_defer_add(args, XFS_ATTR_DEFER_SET);
return 0;
}
/* Try to remove an attr in one transaction or fall back to attr intents. */
int
xfs_attr_removename(
struct xfs_da_args *args)
{
if (xfs_attr_can_shortcut(args->dp))
return xfs_attr_sf_removename(args);
xfs_attr_defer_add(args, XFS_ATTR_DEFER_REMOVE);
return 0;
}
/* Try to replace an attr in one transaction or fall back to attr intents. */
int
xfs_attr_replacename(
struct xfs_da_args *args,
int rmt_blks)
{
int error;
if (rmt_blks || !xfs_attr_can_shortcut(args->dp)) {
xfs_attr_defer_add(args, XFS_ATTR_DEFER_REPLACE);
return 0;
}
error = xfs_attr_shortform_replace(args);
if (error != -ENOSPC)
return error;
args->op_flags |= XFS_DA_OP_ADDNAME | XFS_DA_OP_REPLACE;
error = xfs_attr_sf_removename(args);
if (error)
return error;
if (args->attr_filter & XFS_ATTR_PARENT) {
/*
* Move the new name/value to the regular name/value slots and
* zero out the new name/value slots because we don't need to
* log them for a PPTR_SET operation.
*/
xfs_attr_update_pptr_replace_args(args);
args->new_name = NULL;
args->new_namelen = 0;
args->new_value = NULL;
args->new_valuelen = 0;
}
args->op_flags &= ~XFS_DA_OP_REPLACE;
error = xfs_attr_try_sf_addname(args);
if (error != -ENOSPC)
return error;
xfs_attr_defer_add(args, XFS_ATTR_DEFER_SET);
return 0;
}
/*
* Make a change to the xattr structure.
*
@ -1111,14 +1197,19 @@ xfs_attr_set(
case -EEXIST:
if (op == XFS_ATTRUPDATE_REMOVE) {
/* if no value, we are performing a remove operation */
xfs_attr_defer_add(args, XFS_ATTR_DEFER_REMOVE);
error = xfs_attr_removename(args);
if (error)
goto out_trans_cancel;
break;
}
/* Pure create fails if the attr already exists */
if (op == XFS_ATTRUPDATE_CREATE)
goto out_trans_cancel;
xfs_attr_defer_add(args, XFS_ATTR_DEFER_REPLACE);
error = xfs_attr_replacename(args, rmt_blks);
if (error)
goto out_trans_cancel;
break;
case -ENOATTR:
/* Can't remove what isn't there. */
@ -1128,7 +1219,10 @@ xfs_attr_set(
/* Pure replace fails if no existing attr to replace. */
if (op == XFS_ATTRUPDATE_REPLACE)
goto out_trans_cancel;
xfs_attr_defer_add(args, XFS_ATTR_DEFER_SET);
error = xfs_attr_setname(args, rmt_blks);
if (error)
goto out_trans_cancel;
break;
default:
goto out_trans_cancel;

View file

@ -573,7 +573,7 @@ struct xfs_trans_res xfs_attr_set_resv(const struct xfs_da_args *args);
*/
static inline bool
xfs_attr_is_shortform(
struct xfs_inode *ip)
const struct xfs_inode *ip)
{
return ip->i_af.if_format == XFS_DINODE_FMT_LOCAL ||
(ip->i_af.if_format == XFS_DINODE_FMT_EXTENTS &&
@ -649,4 +649,8 @@ void xfs_attr_intent_destroy_cache(void);
int xfs_attr_sf_totsize(struct xfs_inode *dp);
int xfs_attr_add_fork(struct xfs_inode *ip, int size, int rsvd);
int xfs_attr_setname(struct xfs_da_args *args, int rmt_blks);
int xfs_attr_removename(struct xfs_da_args *args);
int xfs_attr_replacename(struct xfs_da_args *args, int rmt_blks);
#endif /* __XFS_ATTR_H__ */

View file

@ -842,6 +842,44 @@ xfs_attr_sf_findname(
return NULL;
}
/*
* Replace a shortform xattr if it's the right length. Returns 0 on success,
* -ENOSPC if the length is wrong, or -ENOATTR if the attr was not found.
*/
int
xfs_attr_shortform_replace(
struct xfs_da_args *args)
{
struct xfs_attr_sf_entry *sfe;
ASSERT(args->dp->i_af.if_format == XFS_DINODE_FMT_LOCAL);
trace_xfs_attr_sf_replace(args);
sfe = xfs_attr_sf_findname(args);
if (!sfe)
return -ENOATTR;
if (args->attr_filter & XFS_ATTR_PARENT) {
if (sfe->namelen != args->new_namelen ||
sfe->valuelen != args->new_valuelen)
return -ENOSPC;
memcpy(sfe->nameval, args->new_name, sfe->namelen);
memcpy(&sfe->nameval[sfe->namelen], args->new_value,
sfe->valuelen);
} else {
if (sfe->valuelen != args->valuelen)
return -ENOSPC;
memcpy(&sfe->nameval[sfe->namelen], args->value,
sfe->valuelen);
}
xfs_trans_log_inode(args->trans, args->dp,
XFS_ILOG_CORE | XFS_ILOG_ADATA);
return 0;
}
/*
* Add a name/value pair to the shortform attribute list.
* Overflow from the inode has already been checked for.

View file

@ -46,6 +46,7 @@ struct xfs_attr3_icleaf_hdr {
* Internal routines when attribute fork size < XFS_LITINO(mp).
*/
void xfs_attr_shortform_create(struct xfs_da_args *args);
int xfs_attr_shortform_replace(struct xfs_da_args *args);
void xfs_attr_shortform_add(struct xfs_da_args *args, int forkoff);
int xfs_attr_shortform_getvalue(struct xfs_da_args *args);
int xfs_attr_shortform_to_leaf(struct xfs_da_args *args);

View file

@ -29,6 +29,7 @@
#include "xfs_trans_space.h"
#include "xfs_attr_item.h"
#include "xfs_health.h"
#include "xfs_attr_leaf.h"
struct kmem_cache *xfs_parent_args_cache;
@ -202,8 +203,8 @@ xfs_parent_addname(
xfs_inode_to_parent_rec(&ppargs->rec, dp);
xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child,
child->i_ino, parent_name);
xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_SET);
return 0;
return xfs_attr_setname(&ppargs->args, 0);
}
/* Remove a parent pointer to reflect a dirent removal. */
@ -224,8 +225,8 @@ xfs_parent_removename(
xfs_inode_to_parent_rec(&ppargs->rec, dp);
xfs_parent_da_args_init(&ppargs->args, tp, &ppargs->rec, child,
child->i_ino, parent_name);
xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_REMOVE);
return 0;
return xfs_attr_removename(&ppargs->args);
}
/* Replace one parent pointer with another to reflect a rename. */
@ -250,12 +251,13 @@ xfs_parent_replacename(
child->i_ino, old_name);
xfs_inode_to_parent_rec(&ppargs->new_rec, new_dp);
ppargs->args.new_name = new_name->name;
ppargs->args.new_namelen = new_name->len;
ppargs->args.new_value = &ppargs->new_rec;
ppargs->args.new_valuelen = sizeof(struct xfs_parent_rec);
xfs_attr_defer_add(&ppargs->args, XFS_ATTR_DEFER_REPLACE);
return 0;
return xfs_attr_replacename(&ppargs->args, 0);
}
/*

View file

@ -2413,6 +2413,7 @@ DEFINE_ATTR_EVENT(xfs_attr_sf_addname);
DEFINE_ATTR_EVENT(xfs_attr_sf_create);
DEFINE_ATTR_EVENT(xfs_attr_sf_lookup);
DEFINE_ATTR_EVENT(xfs_attr_sf_remove);
DEFINE_ATTR_EVENT(xfs_attr_sf_replace);
DEFINE_ATTR_EVENT(xfs_attr_sf_to_leaf);
DEFINE_ATTR_EVENT(xfs_attr_leaf_add);