From nobody Sun May 12 17:32:55 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1664880188; cv=none; d=zohomail.com; s=zohoarc; b=QXk1F/soki/DIKW5mj1L+0fz5BbaMogRrwpVg+Bx5qQNg5xZ4c2NGsE/IRa41p8kZY13d3bCk4VnPbMcCUBCvtV5iI8fNL+/gQ6BtX3y+6+p9tFrw0vDF6Mln64bguU5gk/utZ/4NUtTLwU3O5MW+obk+yHNzbzz7yFDPByoi9I= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1664880188; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:To; bh=PCASHbc5tkaz2xflkZ60bPrEDSnGb3iMXf/nDXXr5BA=; b=HyxRUy+f3ObyCIFuhTdlnnLMAZpy/mg3joxNDwHoqchRTnNIFWlAjDM4u4FGyp0I4/yPoEfQBt2vVKE0a9IXOFYW7me7rDD6kEkF1WM59e6H3pi3Uc5VBRp1L7rLhcryDx+M/FVHVsalS4EfjWrWA+datGwOTrxgz4GC3Ha+NAc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1664880188198533.5622060383578; Tue, 4 Oct 2022 03:43:08 -0700 (PDT) Received: from localhost ([::1]:51794 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1offOF-0002Wh-4N for importer@patchew.org; Tue, 04 Oct 2022 06:43:07 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:49954) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1offMm-0000cM-Ar; Tue, 04 Oct 2022 06:41:36 -0400 Received: from sosiego.soundray.org ([2a01:4f8:c2c:a9a0::1]:34452) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1offMh-0002T7-By; Tue, 04 Oct 2022 06:41:34 -0400 From: Linus Heckemann DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sphalerite.org; s=sosiego; t=1664880084; bh=PCASHbc5tkaz2xflkZ60bPrEDSnGb3iMXf/nDXXr5BA=; h=From:To:Cc:Subject:Date; b=3OFhN86Z1g5vm1O5biVPsgUJmRF44rdVCfoE/d2g3DG1d2zB9VnfzMpIZuxTkIBpU eOsuhxpZDEoyLax3+Y8ABf8AENaHzN1hZzld1YNnt0HVtTthBD4mdpZQgj1Y+pUt7I Jc641DfydYMgajcoWHVI1NQpOSPYUvsLdEJiAptI= To: Christian Schoenebeck , qemu-devel@nongnu.org Cc: Greg Kurz , Qemu-block , Linus Heckemann , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Subject: [PATCH v6] 9pfs: use GHashTable for fid table Date: Tue, 4 Oct 2022 12:41:21 +0200 Message-Id: <20221004104121.713689-1-git@sphalerite.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a01:4f8:c2c:a9a0::1; envelope-from=linus@sphalerite.org; helo=sosiego.soundray.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @sphalerite.org) X-ZM-MESSAGEID: 1664880189628100001 The previous implementation would iterate over the fid table for lookup operations, resulting in an operation with O(n) complexity on the number of open files and poor cache locality -- for every open, stat, read, write, etc operation. This change uses a hashtable for this instead, significantly improving the performance of the 9p filesystem. The runtime of NixOS's simple installer test, which copies ~122k files totalling ~1.8GiB from 9p, decreased by a factor of about 10. Signed-off-by: Linus Heckemann Reviewed-by: Philippe Mathieu-Daud=C3=A9 Reviewed-by: Greg Kurz Message-Id: <20220908112353.289267-1-git@sphalerite.org> [CS: - Retain BUG_ON(f->clunked) in get_fid(). - Add TODO comment in clunk_fid(). ] Signed-off-by: Christian Schoenebeck --- Christian Schoenebeck writes: > Not a big deal, but I start thinking whether to keep BUG_ON() here as wel= l. > That would require using g_hash_table_lookup() here instead of > g_hash_table_contains(). Not that I would insist. Done. > checkpatch.pl Fixed. > OK, I get it that you squashed your previous patch and that you ended up = with > this comment instead. But if you read the old comment version here, you'll > notice that it describes the actual problem more accurately: especially t= hat > v9fs_reopen_fid() and put_fid() are the 2 functions that may cause the > coroutine to "yield" here. That's an important information to be retained= in > this comment here as it's not obvious. Reworded to mention these functions explicitly. > I would not move this to the 2nd loop. I would still set the > FID_NON_RECLAIMABLE flag here, for the same reasons that you are bumping = the > refcount already in the first loop below. Good point! Fixed. > ... that's not the same behaviour as before, is it? Old behaviour was to= put > the respective (last) fid only on error. And this would now put all fids. Indeed it isn't the old behaviour, but I believe it's correct: since before we only incremented the reference counter for each one when we started reopening it. Now, we increment _all_ their refcounts, so we need to put all of them back as well. > Wasn't there something like GLIST_FOREACH()? Then you wouldn't need to a= dd > that variable. glib does provide g_list_foreach, but that requires a function pointer. I'm not aware of a macro for this. I could change this to use a QLIST (for which we do have a macro) instead of a GList if you insist, but I'd default to leaving this as is. Thanks for your repeated reviews and patience! Linus hw/9pfs/9p.c | 195 +++++++++++++++++++++++++++++---------------------- hw/9pfs/9p.h | 2 +- 2 files changed, 113 insertions(+), 84 deletions(-) diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c index aebadeaa03..729d3181e0 100644 --- a/hw/9pfs/9p.c +++ b/hw/9pfs/9p.c @@ -256,7 +256,8 @@ static size_t v9fs_string_size(V9fsString *str) } /* - * returns 0 if fid got re-opened, 1 if not, < 0 on error */ + * returns 0 if fid got re-opened, 1 if not, < 0 on error + */ static int coroutine_fn v9fs_reopen_fid(V9fsPDU *pdu, V9fsFidState *f) { int err =3D 1; @@ -282,33 +283,32 @@ static V9fsFidState *coroutine_fn get_fid(V9fsPDU *pd= u, int32_t fid) V9fsFidState *f; V9fsState *s =3D pdu->s; - QSIMPLEQ_FOREACH(f, &s->fid_list, next) { + f =3D g_hash_table_lookup(s->fids, GINT_TO_POINTER(fid)); + if (f) { BUG_ON(f->clunked); - if (f->fid =3D=3D fid) { - /* - * Update the fid ref upfront so that - * we don't get reclaimed when we yield - * in open later. - */ - f->ref++; - /* - * check whether we need to reopen the - * file. We might have closed the fd - * while trying to free up some file - * descriptors. - */ - err =3D v9fs_reopen_fid(pdu, f); - if (err < 0) { - f->ref--; - return NULL; - } - /* - * Mark the fid as referenced so that the LRU - * reclaim won't close the file descriptor - */ - f->flags |=3D FID_REFERENCED; - return f; + /* + * Update the fid ref upfront so that + * we don't get reclaimed when we yield + * in open later. + */ + f->ref++; + /* + * check whether we need to reopen the + * file. We might have closed the fd + * while trying to free up some file + * descriptors. + */ + err =3D v9fs_reopen_fid(pdu, f); + if (err < 0) { + f->ref--; + return NULL; } + /* + * Mark the fid as referenced so that the LRU + * reclaim won't close the file descriptor + */ + f->flags |=3D FID_REFERENCED; + return f; } return NULL; } @@ -317,12 +317,11 @@ static V9fsFidState *alloc_fid(V9fsState *s, int32_t = fid) { V9fsFidState *f; - QSIMPLEQ_FOREACH(f, &s->fid_list, next) { + f =3D g_hash_table_lookup(s->fids, GINT_TO_POINTER(fid)); + if (f) { /* If fid is already there return NULL */ BUG_ON(f->clunked); - if (f->fid =3D=3D fid) { - return NULL; - } + return NULL; } f =3D g_new0(V9fsFidState, 1); f->fid =3D fid; @@ -333,7 +332,7 @@ static V9fsFidState *alloc_fid(V9fsState *s, int32_t fi= d) * reclaim won't close the file descriptor */ f->flags |=3D FID_REFERENCED; - QSIMPLEQ_INSERT_TAIL(&s->fid_list, f, next); + g_hash_table_insert(s->fids, GINT_TO_POINTER(fid), f); v9fs_readdir_init(s->proto_version, &f->fs.dir); v9fs_readdir_init(s->proto_version, &f->fs_reclaim.dir); @@ -424,12 +423,12 @@ static V9fsFidState *clunk_fid(V9fsState *s, int32_t = fid) { V9fsFidState *fidp; - QSIMPLEQ_FOREACH(fidp, &s->fid_list, next) { - if (fidp->fid =3D=3D fid) { - QSIMPLEQ_REMOVE(&s->fid_list, fidp, V9fsFidState, next); - fidp->clunked =3D true; - return fidp; - } + /* TODO: Use g_hash_table_steal_extended() instead? */ + fidp =3D g_hash_table_lookup(s->fids, GINT_TO_POINTER(fid)); + if (fidp) { + g_hash_table_remove(s->fids, GINT_TO_POINTER(fid)); + fidp->clunked =3D true; + return fidp; } return NULL; } @@ -439,10 +438,15 @@ void coroutine_fn v9fs_reclaim_fd(V9fsPDU *pdu) int reclaim_count =3D 0; V9fsState *s =3D pdu->s; V9fsFidState *f; + GHashTableIter iter; + gpointer fid; + + g_hash_table_iter_init(&iter, s->fids); + QSLIST_HEAD(, V9fsFidState) reclaim_list =3D QSLIST_HEAD_INITIALIZER(reclaim_list); - QSIMPLEQ_FOREACH(f, &s->fid_list, next) { + while (g_hash_table_iter_next(&iter, &fid, (gpointer *) &f)) { /* * Unlink fids cannot be reclaimed. Check * for them and skip them. Also skip fids @@ -514,72 +518,86 @@ void coroutine_fn v9fs_reclaim_fd(V9fsPDU *pdu) } } +/* + * This is used when a path is removed from the directory tree. Any + * fids that still reference it must not be closed from then on, since + * they cannot be reopened. + */ static int coroutine_fn v9fs_mark_fids_unreclaim(V9fsPDU *pdu, V9fsPath *p= ath) { - int err; + int err =3D 0; V9fsState *s =3D pdu->s; - V9fsFidState *fidp, *fidp_next; + V9fsFidState *fidp; + gpointer fid; + GHashTableIter iter; + /* + * The most common case is probably that we have exactly one + * fid for the given path, so preallocate exactly one. + */ + g_autoptr(GArray) to_reopen =3D g_array_sized_new(FALSE, FALSE, + sizeof(V9fsFidState *), 1); + gint i; - fidp =3D QSIMPLEQ_FIRST(&s->fid_list); - if (!fidp) { - return 0; - } + g_hash_table_iter_init(&iter, s->fids); /* - * v9fs_reopen_fid() can yield : a reference on the fid must be held - * to ensure its pointer remains valid and we can safely pass it to - * QSIMPLEQ_NEXT(). The corresponding put_fid() can also yield so - * we must keep a reference on the next fid as well. So the logic here - * is to get a reference on a fid and only put it back during the next - * iteration after we could get a reference on the next fid. Start with - * the first one. + * We iterate over the fid table looking for the entries we need + * to reopen, and store them in to_reopen. This is because + * v9fs_reopen_fid() and put_fid() yield. This allows the fid table + * to be modified in the meantime, invalidating our iterator. */ - for (fidp->ref++; fidp; fidp =3D fidp_next) { + while (g_hash_table_iter_next(&iter, &fid, (gpointer *) &fidp)) { if (fidp->path.size =3D=3D path->size && !memcmp(fidp->path.data, path->data, path->size)) { - /* Mark the fid non reclaimable. */ - fidp->flags |=3D FID_NON_RECLAIMABLE; - - /* reopen the file/dir if already closed */ - err =3D v9fs_reopen_fid(pdu, fidp); - if (err < 0) { - put_fid(pdu, fidp); - return err; - } - } - - fidp_next =3D QSIMPLEQ_NEXT(fidp, next); - - if (fidp_next) { /* - * Ensure the next fid survives a potential clunk request duri= ng - * put_fid() below and v9fs_reopen_fid() in the next iteration. + * Ensure the fid survives a potential clunk request during + * v9fs_reopen_fid or put_fid. */ - fidp_next->ref++; + fidp->ref++; + fidp->flags |=3D FID_NON_RECLAIMABLE; + g_array_append_val(to_reopen, fidp); } + } - /* We're done with this fid */ - put_fid(pdu, fidp); + for (i =3D 0; i < to_reopen->len; i++) { + fidp =3D g_array_index(to_reopen, V9fsFidState*, i); + /* reopen the file/dir if already closed */ + err =3D v9fs_reopen_fid(pdu, fidp); + if (err < 0) { + goto out; + } } - return 0; + out: + for (i =3D 0; i < to_reopen->len; i++) { + put_fid(pdu, g_array_index(to_reopen, V9fsFidState*, i)); + } + return err; } static void coroutine_fn virtfs_reset(V9fsPDU *pdu) { V9fsState *s =3D pdu->s; V9fsFidState *fidp; + GList *freeing; + /* + * Get a list of all the values (fid states) in the table, which + * we then... + */ + g_autoptr(GList) fids =3D g_hash_table_get_values(s->fids); - /* Free all fids */ - while (!QSIMPLEQ_EMPTY(&s->fid_list)) { - /* Get fid */ - fidp =3D QSIMPLEQ_FIRST(&s->fid_list); - fidp->ref++; + /* ... remove from the table, taking over ownership. */ + g_hash_table_steal_all(s->fids); - /* Clunk fid */ - QSIMPLEQ_REMOVE(&s->fid_list, fidp, V9fsFidState, next); + /* + * This allows us to release our references to them asynchronously wit= hout + * iterating over the hash table and risking iterator invalidation + * through concurrent modifications. + */ + for (freeing =3D fids; freeing; freeing =3D freeing->next) { + fidp =3D freeing->data; + fidp->ref++; fidp->clunked =3D true; - put_fid(pdu, fidp); } } @@ -3205,6 +3223,8 @@ static int coroutine_fn v9fs_complete_rename(V9fsPDU = *pdu, V9fsFidState *fidp, V9fsFidState *tfidp; V9fsState *s =3D pdu->s; V9fsFidState *dirfidp =3D NULL; + GHashTableIter iter; + gpointer fid; v9fs_path_init(&new_path); if (newdirfid !=3D -1) { @@ -3238,11 +3258,13 @@ static int coroutine_fn v9fs_complete_rename(V9fsPD= U *pdu, V9fsFidState *fidp, if (err < 0) { goto out; } + /* * Fixup fid's pointing to the old name to * start pointing to the new name */ - QSIMPLEQ_FOREACH(tfidp, &s->fid_list, next) { + g_hash_table_iter_init(&iter, s->fids); + while (g_hash_table_iter_next(&iter, &fid, (gpointer *) &tfidp)) { if (v9fs_path_is_ancestor(&fidp->path, &tfidp->path)) { /* replace the name */ v9fs_fix_path(&tfidp->path, &new_path, strlen(fidp->path.data)= ); @@ -3320,6 +3342,8 @@ static int coroutine_fn v9fs_fix_fid_paths(V9fsPDU *p= du, V9fsPath *olddir, V9fsPath oldpath, newpath; V9fsState *s =3D pdu->s; int err; + GHashTableIter iter; + gpointer fid; v9fs_path_init(&oldpath); v9fs_path_init(&newpath); @@ -3336,7 +3360,8 @@ static int coroutine_fn v9fs_fix_fid_paths(V9fsPDU *p= du, V9fsPath *olddir, * Fixup fid's pointing to the old name to * start pointing to the new name */ - QSIMPLEQ_FOREACH(tfidp, &s->fid_list, next) { + g_hash_table_iter_init(&iter, s->fids); + while (g_hash_table_iter_next(&iter, &fid, (gpointer *) &tfidp)) { if (v9fs_path_is_ancestor(&oldpath, &tfidp->path)) { /* replace the name */ v9fs_fix_path(&tfidp->path, &newpath, strlen(oldpath.data)); @@ -4226,7 +4251,7 @@ int v9fs_device_realize_common(V9fsState *s, const V9= fsTransport *t, s->ctx.fmode =3D fse->fmode; s->ctx.dmode =3D fse->dmode; - QSIMPLEQ_INIT(&s->fid_list); + s->fids =3D g_hash_table_new(NULL, NULL); qemu_co_rwlock_init(&s->rename_lock); if (s->ops->init(&s->ctx, errp) < 0) { @@ -4286,6 +4311,10 @@ void v9fs_device_unrealize_common(V9fsState *s) if (s->ctx.fst) { fsdev_throttle_cleanup(s->ctx.fst); } + if (s->fids) { + g_hash_table_destroy(s->fids); + s->fids =3D NULL; + } g_free(s->tag); qp_table_destroy(&s->qpd_table); qp_table_destroy(&s->qpp_table); diff --git a/hw/9pfs/9p.h b/hw/9pfs/9p.h index 994f952600..10fd2076c2 100644 --- a/hw/9pfs/9p.h +++ b/hw/9pfs/9p.h @@ -339,7 +339,7 @@ typedef struct { struct V9fsState { QLIST_HEAD(, V9fsPDU) free_list; QLIST_HEAD(, V9fsPDU) active_list; - QSIMPLEQ_HEAD(, V9fsFidState) fid_list; + GHashTable *fids; FileOperations *ops; FsContext ctx; char *tag; -- 2.36.2