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
>>
next prev parent 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox