All of lore.kernel.org
 help / color / mirror / Atom feed
From: Usama Arif <usama.arif@linux.dev>
To: Yosry Ahmed <yosry@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>,
	chengming.zhou@linux.dev, dsterba@suse.com, hannes@cmpxchg.org,
	linux-kernel@vger.kernel.org, linux-mm@kvack.org,
	nphamcs@gmail.com, terrelln@fb.com, riel@surriel.com,
	shakeel.butt@linux.dev, alex@ghiti.fr, senozhatsky@chromium.org,
	kernel-team@meta.com
Subject: Re: [PATCH 1/2] mm: zswap: use separate compression and decompression requests
Date: Fri, 9 Oct 2026 16:07:17 +0100	[thread overview]
Message-ID: <3465c889-de07-48b7-b6b4-7fc8dcac4e67@linux.dev> (raw)
In-Reply-To: <CAO9r8zPm4B1_wtAUMeSR9PQ5ja+kJz14Ub7MCuJit_NSH2DQsQ@mail.gmail.com>



On 07/10/2026 23:01, Yosry Ahmed wrote:
> On Mon, Oct 5, 2026 at 5:23 PM Usama Arif <usama.arif@linux.dev> wrote:
>>
>> Stores and loads serialize on the same per-CPU acomp request and mutex.
>> A low-priority store can be preempted as soon as the compressor drops
>> its stream lock, while it still holds the mutex. A higher-priority load
>> on that CPU then waits until the store runs again, which can take a
>> long time when other tasks are runnable.
>>
>> Give compression and decompression their own request, completion wait
>> and mutex. Since commit e2c3b6b21c77f ("mm: zswap: use SG list
>> decompression APIs from zsmalloc"), the per-CPU buffer is only used for
>> compression. The two requests can share the per-CPU transform: no
>> in-tree implementation modifies transform state while (de)compressing,
>> and shared codec state has its own locking.


Hello Yosry!

Thanks for the reviews!

> 
> I am a bit uncomfortable with this. If future changes modify the
> transform state while (de)compressing, it may result in nasty bugs.

Independent acomp requests can share a transform. UBIFS already uses its
shared compr->cc for compression and decompression without caller
serialization. IPComp also submits per-packet requests on a shared
transform, and EROFS allows concurrent decompression on one transform.

Drivers synchronize their shared state internally. For example, HiSilicon
protects its transform-owned request bitmap with req_lock. Unsynchronized
shared state would break those existing callers too. Separate compression
and decompression transforms would still leave concurrent stack
decompressions sharing a transform.


> 
> As for the buffer, I would also prefer some protection, but I feel
> less strongly about this. For example, we can put it inside
> zswap_acomp_req and not initialize it for the decompression request.
> Alternatively, we can have an intermediary struct that contains
> zswap_acomp_req + buffer, and use that for the compression request.


Done for next revision. Added zswap_comp_ctx containing the compression
request and output buffer. Allocation, use and cleanup now go through that
context. The compression mutex protects the buffer until zs_obj_write()
finishes, and decompression has no buffer member.

> 
>> Loads can still wait for
>> each other on the decompression mutex, and stores still serialize on
>> the compression mutex.
>>
>> This follows the proposal from Sergey Senozhatsky for the same split
>> for zram [1].
>>
>> [1] https://lore.kernel.org/all/20261005122036.718976-10-senozhatsky@chromium.org/
>>
>> Signed-off-by: Usama Arif <usama.arif@linux.dev>
>> ---
>>  mm/zswap.c | 89 +++++++++++++++++++++++++++++++-----------------------
>>  1 file changed, 52 insertions(+), 37 deletions(-)
>>
>> diff --git a/mm/zswap.c b/mm/zswap.c
>> index ae19e301fced7..54187b1ef751d 100644
>> --- a/mm/zswap.c
>> +++ b/mm/zswap.c
>> @@ -137,14 +137,20 @@ bool zswap_never_enabled(void)
>>  * data structures
>>  **********************************/
>>
>> -struct crypto_acomp_ctx {
>> -       struct crypto_acomp *acomp;
>> +struct zswap_acomp_req {
>>         struct acomp_req *req;
>>         struct crypto_wait wait;
>> -       u8 *buffer;
>>         struct mutex mutex;
>>  };
>>
>> +/* Separate requests, so that decompression does not wait for compression. */
>> +struct crypto_acomp_ctx {
>> +       struct crypto_acomp *acomp;
>> +       struct zswap_acomp_req comp;
>> +       struct zswap_acomp_req decomp;
>> +       u8 *buffer;
>> +};
>> +
>>  /*
>>   * The lock ordering is zswap_tree.lock -> zswap_pool.lru_lock.
>>   * The only case where lru_lock is not acquired while holding tree.lock is
>> @@ -270,14 +276,10 @@ static void acomp_ctx_free(struct crypto_acomp_ctx *acomp_ctx)
>>         if (!acomp_ctx)
>>                 return;
>>
>> -       /*
>> -        * If there was an error in allocating @acomp_ctx->req, it
>> -        * would be set to NULL.
>> -        */
>> -       if (acomp_ctx->req)
>> -               acomp_request_free(acomp_ctx->req);
>> -
>> -       acomp_ctx->req = NULL;
>> +       acomp_request_free(acomp_ctx->comp.req);
>> +       acomp_ctx->comp.req = NULL;
>> +       acomp_request_free(acomp_ctx->decomp.req);
>> +       acomp_ctx->decomp.req = NULL;
>>
>>         /*
>>          * We have to handle both cases here: an error pointer return from
>> @@ -796,6 +798,28 @@ static void zswap_entry_free(struct zswap_entry *entry)
>>  /*********************************
>>  * compressed storage functions
>>  **********************************/
>> +static int zswap_acomp_req_init(struct zswap_acomp_req *areq,
>> +                               struct crypto_acomp *acomp)
>> +{
>> +       /* acomp_request_alloc() returns NULL in case of an error. */
>> +       areq->req = acomp_request_alloc(acomp);
>> +       if (!areq->req)
>> +               return -ENOMEM;
>> +
>> +       crypto_init_wait(&areq->wait);
>> +
>> +       /*
>> +        * if the backend of acomp is async zip, crypto_req_done() will wakeup
>> +        * crypto_wait_req(); if the backend of acomp is scomp, the callback
>> +        * won't be called, crypto_wait_req() will return without blocking.
>> +        */
>> +       acomp_request_set_callback(areq->req, CRYPTO_TFM_REQ_MAY_BACKLOG,
>> +                                  crypto_req_done, &areq->wait);
>> +
>> +       mutex_init(&areq->mutex);
>> +       return 0;
>> +}
>> +
>>  static int zswap_cpu_comp_prepare(unsigned int cpu, struct hlist_node *node)
>>  {
>>         struct zswap_pool *pool = hlist_entry(node, struct zswap_pool, node);
>> @@ -827,25 +851,13 @@ static int zswap_cpu_comp_prepare(unsigned int cpu, struct hlist_node *node)
>>                 goto fail;
>>         }
>>
>> -       /* acomp_request_alloc() returns NULL in case of an error. */
>> -       acomp_ctx->req = acomp_request_alloc(acomp_ctx->acomp);
>> -       if (!acomp_ctx->req) {
>> +       if (zswap_acomp_req_init(&acomp_ctx->comp, acomp_ctx->acomp) ||
>> +           zswap_acomp_req_init(&acomp_ctx->decomp, acomp_ctx->acomp)) {
>>                 pr_err("could not alloc crypto acomp_request %s\n",
>>                        pool->tfm_name);
>>                 goto fail;
>>         }
>>
>> -       crypto_init_wait(&acomp_ctx->wait);
>> -
>> -       /*
>> -        * if the backend of acomp is async zip, crypto_req_done() will wakeup
>> -        * crypto_wait_req(); if the backend of acomp is scomp, the callback
>> -        * won't be called, crypto_wait_req() will return without blocking.
>> -        */
>> -       acomp_request_set_callback(acomp_ctx->req, CRYPTO_TFM_REQ_MAY_BACKLOG,
>> -                                  crypto_req_done, &acomp_ctx->wait);
>> -
>> -       mutex_init(&acomp_ctx->mutex);
>>         return 0;
>>
>>  fail:
>> @@ -866,14 +878,15 @@ static bool zswap_compress(struct folio *folio, long index,
>>         bool mapped = false;
>>
>>         acomp_ctx = raw_cpu_ptr(pool->acomp_ctx);
>> -       mutex_lock(&acomp_ctx->mutex);
>> +       mutex_lock(&acomp_ctx->comp.mutex);
>>
>>         dst = acomp_ctx->buffer;
>>         sg_init_table(&input, 1);
>>         sg_set_folio(&input, folio, PAGE_SIZE, index * PAGE_SIZE);
>>
>>         sg_init_one(&output, dst, PAGE_SIZE);
>> -       acomp_request_set_params(acomp_ctx->req, &input, &output, PAGE_SIZE, dlen);
>> +       acomp_request_set_params(acomp_ctx->comp.req, &input, &output,
>> +                                PAGE_SIZE, dlen);
>>
>>         /*
>>          * it maybe looks a little bit silly that we send an asynchronous request,
>> @@ -885,10 +898,12 @@ static bool zswap_compress(struct folio *folio, long index,
>>          * existing method to send the second page before the first page is done
>>          * in one thread doing zswap.
>>          * but in different threads running on different cpu, we have different
>> -        * acomp instance, so multiple threads can do (de)compression in parallel.
>> +        * acomp instance, and compression and decompression use separate
>> +        * requests, so multiple threads can do (de)compression in parallel.
>>          */
>> -       comp_ret = crypto_wait_req(crypto_acomp_compress(acomp_ctx->req), &acomp_ctx->wait);
>> -       dlen = acomp_ctx->req->dlen;
>> +       comp_ret = crypto_wait_req(crypto_acomp_compress(acomp_ctx->comp.req),
>> +                                  &acomp_ctx->comp.wait);
>> +       dlen = acomp_ctx->comp.req->dlen;
>>
>>         /*
>>          * If a page cannot be compressed into a size smaller than PAGE_SIZE,
>> @@ -932,7 +947,7 @@ static bool zswap_compress(struct folio *folio, long index,
>>         else if (alloc_ret)
>>                 zswap_reject_alloc_fail++;
>>
>> -       mutex_unlock(&acomp_ctx->mutex);
>> +       mutex_unlock(&acomp_ctx->comp.mutex);
>>         return comp_ret == 0 && alloc_ret == 0;
>>  }
>>
>> @@ -948,7 +963,7 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
>>                 return false;
>>
>>         acomp_ctx = raw_cpu_ptr(pool->acomp_ctx);
>> -       mutex_lock(&acomp_ctx->mutex);
>> +       mutex_lock(&acomp_ctx->decomp.mutex);
>>         zs_obj_read_sg_begin(pool->zs_pool, entry->handle, input, entry->length);
>>
>>         /* zswap entries of length PAGE_SIZE are not compressed. */
>> @@ -965,15 +980,15 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
>>         } else {
>>                 sg_init_table(&output, 1);
>>                 sg_set_folio(&output, folio, PAGE_SIZE, 0);
>> -               acomp_request_set_params(acomp_ctx->req, input, &output,
>> +               acomp_request_set_params(acomp_ctx->decomp.req, input, &output,
>>                                          entry->length, PAGE_SIZE);
>> -               ret = crypto_acomp_decompress(acomp_ctx->req);
>> -               ret = crypto_wait_req(ret, &acomp_ctx->wait);
>> -               dlen = acomp_ctx->req->dlen;
>> +               ret = crypto_acomp_decompress(acomp_ctx->decomp.req);
>> +               ret = crypto_wait_req(ret, &acomp_ctx->decomp.wait);
>> +               dlen = acomp_ctx->decomp.req->dlen;
>>         }
>>
>>         zs_obj_read_sg_end(pool->zs_pool, entry->handle);
>> -       mutex_unlock(&acomp_ctx->mutex);
>> +       mutex_unlock(&acomp_ctx->decomp.mutex);
>>
>>         if (!ret && dlen == PAGE_SIZE)
>>                 return true;
>> --
>> 2.53.0-Meta
>>



  reply	other threads:[~2026-10-09 15:07 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-06  0:22 [PATCH 0/2] mm: zswap: reduce request contention on loads Usama Arif
2026-10-06  0:22 ` [PATCH 1/2] mm: zswap: use separate compression and decompression requests Usama Arif
2026-10-06  9:47   ` Nhat Pham
2026-10-06  9:52     ` Sergey Senozhatsky
2026-10-07 11:02     ` Usama Arif
2026-10-07 21:01   ` Yosry Ahmed
2026-10-09 15:07     ` Usama Arif [this message]
2026-10-09 17:27       ` Yosry Ahmed
2026-10-09  7:16   ` Sergey Senozhatsky
2026-10-09 16:26     ` Usama Arif
2026-10-10  4:08       ` Sergey Senozhatsky
2026-10-06  0:22 ` [PATCH 2/2] mm: zswap: use stack requests for synchronous decompression Usama Arif
2026-10-07  5:46   ` Nhat Pham
2026-10-07 21:28   ` Yosry Ahmed
2026-10-09 15:27     ` Usama Arif
2026-10-09 17:28       ` Yosry Ahmed
2026-10-06  9:18 ` [PATCH 0/2] mm: zswap: reduce request contention on loads Usama Arif
2026-10-06  9:35 ` Nhat Pham
2026-10-07 10:56   ` Usama Arif

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3465c889-de07-48b7-b6b4-7fc8dcac4e67@linux.dev \
    --to=usama.arif@linux.dev \
    --cc=akpm@linux-foundation.org \
    --cc=alex@ghiti.fr \
    --cc=chengming.zhou@linux.dev \
    --cc=dsterba@suse.com \
    --cc=hannes@cmpxchg.org \
    --cc=kernel-team@meta.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=nphamcs@gmail.com \
    --cc=riel@surriel.com \
    --cc=senozhatsky@chromium.org \
    --cc=shakeel.butt@linux.dev \
    --cc=terrelln@fb.com \
    --cc=yosry@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.