[PATCH] erofs: rename per-CPU buffers to global buffer pool and make it configurable

Chunhai Guo guochunhai at vivo.com
Fri Mar 22 18:18:14 AEDT 2024


Got it. I will send patch v2 as your suggestion.

Thanks,

在 2024/3/22 12:10, Gao Xiang 写道:
>
> On 2024/3/22 11:47, Chunhai Guo wrote:
>> It will cost more time if compressed buffers are allocated on demand for
>> low-latency algorithms (like lz4) so EROFS uses per-CPU buffers to keep
>> compressed data if in-place decompression is unfulfilled.  While it is
>> kind of wasteful of memory for a device with hundreds of CPUs, and only
>> a small number of CPUs are concurrently decompressing most of the time.
> a small number of CPUs concurrently decompress most of the time.
>
>> This patch renames it as 'global buffer pool' and makes it configurable.
>> This allows two or more CPUs to share a common buffer to reduce memory
>> occupation.
>>
>> Signed-off-by: Chunhai Guo <guochunhai at vivo.com>
>> Suggested-by: Gao Xiang <xiang at kernel.org>
>> ---
>>    fs/erofs/Makefile       |   2 +-
>>    fs/erofs/decompressor.c |   6 +-
>>    fs/erofs/internal.h     |  14 ++--
>>    fs/erofs/pcpubuf.c      | 148 --------------------------------------
>>    fs/erofs/super.c        |   4 +-
>>    fs/erofs/utils.c        | 155 ++++++++++++++++++++++++++++++++++++++++
>>    6 files changed, 168 insertions(+), 161 deletions(-)
>>    delete mode 100644 fs/erofs/pcpubuf.c
>>
>> diff --git a/fs/erofs/Makefile b/fs/erofs/Makefile
>> index 994d0b9deddf..6cf1504c63e6 100644
>> --- a/fs/erofs/Makefile
>> +++ b/fs/erofs/Makefile
>> @@ -3,7 +3,7 @@
>>    obj-$(CONFIG_EROFS_FS) += erofs.o
>>    erofs-objs := super.o inode.o data.o namei.o dir.o utils.o sysfs.o
>>    erofs-$(CONFIG_EROFS_FS_XATTR) += xattr.o
>> -erofs-$(CONFIG_EROFS_FS_ZIP) += decompressor.o zmap.o zdata.o pcpubuf.o
>> +erofs-$(CONFIG_EROFS_FS_ZIP) += decompressor.o zmap.o zdata.o
>>    erofs-$(CONFIG_EROFS_FS_ZIP_LZMA) += decompressor_lzma.o
>>    erofs-$(CONFIG_EROFS_FS_ZIP_DEFLATE) += decompressor_deflate.o
>>    erofs-$(CONFIG_EROFS_FS_ONDEMAND) += fscache.o
>> diff --git a/fs/erofs/decompressor.c b/fs/erofs/decompressor.c
>> index d4cee95af14c..99a344684132 100644
>> --- a/fs/erofs/decompressor.c
>> +++ b/fs/erofs/decompressor.c
>> @@ -54,7 +54,7 @@ static int z_erofs_load_lz4_config(struct super_block *sb,
>>    	sbi->lz4.max_distance_pages = distance ?
>>    					DIV_ROUND_UP(distance, PAGE_SIZE) + 1 :
>>    					LZ4_MAX_DISTANCE_PAGES;
>> -	return erofs_pcpubuf_growsize(sbi->lz4.max_pclusterblks);
>> +	return erofs_gbuf_growsize(sbi->lz4.max_pclusterblks);
>>    }
>>    
>>    /*
>> @@ -159,7 +159,7 @@ static void *z_erofs_lz4_handle_overlap(struct z_erofs_lz4_decompress_ctx *ctx,
>>    docopy:
>>    	/* Or copy compressed data which can be overlapped to per-CPU buffer */
>>    	in = rq->in;
>> -	src = erofs_get_pcpubuf(ctx->inpages);
>> +	src = erofs_get_gbuf(ctx->inpages);
>>    	if (!src) {
>>    		DBG_BUGON(1);
>>    		kunmap_local(inpage);
>> @@ -260,7 +260,7 @@ static int z_erofs_lz4_decompress_mem(struct z_erofs_lz4_decompress_ctx *ctx,
>>    	} else if (maptype == 1) {
>>    		vm_unmap_ram(src, ctx->inpages);
>>    	} else if (maptype == 2) {
>> -		erofs_put_pcpubuf(src);
>> +		erofs_put_gbuf(src);
>>    	} else if (maptype != 3) {
>>    		DBG_BUGON(1);
>>    		return -EFAULT;
>> diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
>> index b0409badb017..320d3d0d3526 100644
>> --- a/fs/erofs/internal.h
>> +++ b/fs/erofs/internal.h
>> @@ -471,11 +471,11 @@ int erofs_try_to_free_all_cached_pages(struct erofs_sb_info *sbi,
>>    				       struct erofs_workgroup *egrp);
>>    int z_erofs_map_blocks_iter(struct inode *inode, struct erofs_map_blocks *map,
>>    			    int flags);
>> -void *erofs_get_pcpubuf(unsigned int requiredpages);
>> -void erofs_put_pcpubuf(void *ptr);
>> -int erofs_pcpubuf_growsize(unsigned int nrpages);
>> -void __init erofs_pcpubuf_init(void);
>> -void erofs_pcpubuf_exit(void);
>> +void *erofs_get_gbuf(unsigned int requiredpages);
>> +void erofs_put_gbuf(void *ptr);
>> +int erofs_gbuf_growsize(unsigned int nrpages);
>> +void __init erofs_gbuf_init(void);
>> +void erofs_gbuf_exit(void);
>>    int erofs_init_managed_cache(struct super_block *sb);
>>    int z_erofs_parse_cfgs(struct super_block *sb, struct erofs_super_block *dsb);
>>    #else
>> @@ -485,8 +485,8 @@ static inline int erofs_init_shrinker(void) { return 0; }
>>    static inline void erofs_exit_shrinker(void) {}
>>    static inline int z_erofs_init_zip_subsystem(void) { return 0; }
>>    static inline void z_erofs_exit_zip_subsystem(void) {}
>> -static inline void erofs_pcpubuf_init(void) {}
>> -static inline void erofs_pcpubuf_exit(void) {}
>> +static inline void erofs_gbuf_init(void) {}
>> +static inline void erofs_gbuf_exit(void) {}
>>    static inline int erofs_init_managed_cache(struct super_block *sb) { return 0; }
>>    #endif	/* !CONFIG_EROFS_FS_ZIP */
>>    
>> diff --git a/fs/erofs/pcpubuf.c b/fs/erofs/pcpubuf.c
>> deleted file mode 100644
>> index c7a4b1d77069..000000000000
>> --- a/fs/erofs/pcpubuf.c
>> +++ /dev/null
>> @@ -1,148 +0,0 @@
>> -// SPDX-License-Identifier: GPL-2.0-only
>> -/*
>> - * Copyright (C) Gao Xiang <xiang at kernel.org>
>> - *
>> - * For low-latency decompression algorithms (e.g. lz4), reserve consecutive
>> - * per-CPU virtual memory (in pages) in advance to store such inplace I/O
>> - * data if inplace decompression is failed (due to unmet inplace margin for
>> - * example).
>> - */
>> -#include "internal.h"
>> -
>> -struct erofs_pcpubuf {
>> -	raw_spinlock_t lock;
>> -	void *ptr;
>> -	struct page **pages;
>> -	unsigned int nrpages;
>> -};
>> -
>> -static DEFINE_PER_CPU(struct erofs_pcpubuf, erofs_pcb);
>> -
>> -void *erofs_get_pcpubuf(unsigned int requiredpages)
>> -	__acquires(pcb->lock)
>> -{
>> -	struct erofs_pcpubuf *pcb = &get_cpu_var(erofs_pcb);
>> -
>> -	raw_spin_lock(&pcb->lock);
>> -	/* check if the per-CPU buffer is too small */
>> -	if (requiredpages > pcb->nrpages) {
>> -		raw_spin_unlock(&pcb->lock);
>> -		put_cpu_var(erofs_pcb);
>> -		/* (for sparse checker) pretend pcb->lock is still taken */
>> -		__acquire(pcb->lock);
>> -		return NULL;
>> -	}
>> -	return pcb->ptr;
>> -}
>> -
>> -void erofs_put_pcpubuf(void *ptr) __releases(pcb->lock)
>> -{
>> -	struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, smp_processor_id());
>> -
>> -	DBG_BUGON(pcb->ptr != ptr);
>> -	raw_spin_unlock(&pcb->lock);
>> -	put_cpu_var(erofs_pcb);
>> -}
>> -
>> -/* the next step: support per-CPU page buffers hotplug */
>> -int erofs_pcpubuf_growsize(unsigned int nrpages)
>> -{
>> -	static DEFINE_MUTEX(pcb_resize_mutex);
>> -	static unsigned int pcb_nrpages;
>> -	struct page *pagepool = NULL;
>> -	int delta, cpu, ret, i;
>> -
>> -	mutex_lock(&pcb_resize_mutex);
>> -	delta = nrpages - pcb_nrpages;
>> -	ret = 0;
>> -	/* avoid shrinking pcpubuf, since no idea how many fses rely on */
>> -	if (delta <= 0)
>> -		goto out;
>> -
>> -	for_each_possible_cpu(cpu) {
>> -		struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, cpu);
>> -		struct page **pages, **oldpages;
>> -		void *ptr, *old_ptr;
>> -
>> -		pages = kmalloc_array(nrpages, sizeof(*pages), GFP_KERNEL);
>> -		if (!pages) {
>> -			ret = -ENOMEM;
>> -			break;
>> -		}
>> -
>> -		for (i = 0; i < nrpages; ++i) {
>> -			pages[i] = erofs_allocpage(&pagepool, GFP_KERNEL);
>> -			if (!pages[i]) {
>> -				ret = -ENOMEM;
>> -				oldpages = pages;
>> -				goto free_pagearray;
>> -			}
>> -		}
>> -		ptr = vmap(pages, nrpages, VM_MAP, PAGE_KERNEL);
>> -		if (!ptr) {
>> -			ret = -ENOMEM;
>> -			oldpages = pages;
>> -			goto free_pagearray;
>> -		}
>> -		raw_spin_lock(&pcb->lock);
>> -		old_ptr = pcb->ptr;
>> -		pcb->ptr = ptr;
>> -		oldpages = pcb->pages;
>> -		pcb->pages = pages;
>> -		i = pcb->nrpages;
>> -		pcb->nrpages = nrpages;
>> -		raw_spin_unlock(&pcb->lock);
>> -
>> -		if (!oldpages) {
>> -			DBG_BUGON(old_ptr);
>> -			continue;
>> -		}
>> -
>> -		if (old_ptr)
>> -			vunmap(old_ptr);
>> -free_pagearray:
>> -		while (i)
>> -			erofs_pagepool_add(&pagepool, oldpages[--i]);
>> -		kfree(oldpages);
>> -		if (ret)
>> -			break;
>> -	}
>> -	pcb_nrpages = nrpages;
>> -	erofs_release_pages(&pagepool);
>> -out:
>> -	mutex_unlock(&pcb_resize_mutex);
>> -	return ret;
>> -}
>> -
>> -void __init erofs_pcpubuf_init(void)
>> -{
>> -	int cpu;
>> -
>> -	for_each_possible_cpu(cpu) {
>> -		struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, cpu);
>> -
>> -		raw_spin_lock_init(&pcb->lock);
>> -	}
>> -}
>> -
>> -void erofs_pcpubuf_exit(void)
>> -{
>> -	int cpu, i;
>> -
>> -	for_each_possible_cpu(cpu) {
>> -		struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, cpu);
>> -
>> -		if (pcb->ptr) {
>> -			vunmap(pcb->ptr);
>> -			pcb->ptr = NULL;
>> -		}
>> -		if (!pcb->pages)
>> -			continue;
>> -
>> -		for (i = 0; i < pcb->nrpages; ++i)
>> -			if (pcb->pages[i])
>> -				put_page(pcb->pages[i]);
>> -		kfree(pcb->pages);
>> -		pcb->pages = NULL;
>> -	}
>> -}
>> diff --git a/fs/erofs/super.c b/fs/erofs/super.c
>> index 5f60f163bd56..5161923c33fc 100644
>> --- a/fs/erofs/super.c
>> +++ b/fs/erofs/super.c
>> @@ -902,7 +902,7 @@ static int __init erofs_module_init(void)
>>    	if (err)
>>    		goto deflate_err;
>>    
>> -	erofs_pcpubuf_init();
>> +	erofs_gbuf_init();
>>    	err = z_erofs_init_zip_subsystem();
>>    	if (err)
>>    		goto zip_err;
>> @@ -945,7 +945,7 @@ static void __exit erofs_module_exit(void)
>>    	z_erofs_lzma_exit();
>>    	erofs_exit_shrinker();
>>    	kmem_cache_destroy(erofs_inode_cachep);
>> -	erofs_pcpubuf_exit();
>> +	erofs_gbuf_exit();
>>    }
>>    
>>    static int erofs_statfs(struct dentry *dentry, struct kstatfs *buf)
>> diff --git a/fs/erofs/utils.c b/fs/erofs/utils.c
>> index e146d09151af..7bdff1c7ce19 100644
>> --- a/fs/erofs/utils.c
>> +++ b/fs/erofs/utils.c
>> @@ -284,4 +284,159 @@ void erofs_exit_shrinker(void)
>>    {
>>    	shrinker_free(erofs_shrinker_info);
>>    }
>> +
>> +struct erofs_gbuf {
> struct z_erofs_gbuf {
>
>> +	spinlock_t lock;
>> +	void *ptr;
>> +	struct page **pages;
>> +	unsigned int nrpages, ref;
>> +	struct list_head list;
>> +};
>> +
>> +struct erofs_gbufpool {
>> +	struct erofs_gbuf *gbuf_array;
>> +	unsigned int gbuf_num, gbuf_nrpages;
>> +};
> could we avoid this structure? I think it's only used once:
>
> struct z_erofs_gbuf *z_erofs_gbufpool;
> unsigned int z_erofs_gbuf_count, z_erofs_gbuf_nrpages;
>
> module_param_named(global_buffers, z_erofs_gbuf_count, uint, 0444);
>
>> +
>> +struct erofs_gbufpool z_erofs_gbufpool;
>> +
>> +module_param_named(pcluster_buf_num, z_erofs_gbufpool.gbuf_num, uint, 0444);
>> +
>> +static inline unsigned int erofs_cur_gbuf(void)
> inline here is unnecessary, and
>
> static unsigned int z_erofs_gbuf_id(void)
> {
>
>> +{
>> +	return smp_processor_id() % z_erofs_gbufpool.gbuf_num;
>> +}
>> +
>> +void *erofs_get_gbuf(unsigned int requiredpages)
> z_erofs_get_gbuf
>
>> +	__acquires(gbuf->lock)
>> +{
>> +	struct erofs_gbuf *gbuf;
>> +
>> +	gbuf = &z_erofs_gbufpool.gbuf_array[erofs_cur_gbuf()];
>> +	spin_lock(&gbuf->lock);
>> +	/* check if the buffer is too small */
>> +	if (requiredpages > gbuf->nrpages) {
>> +		spin_unlock(&gbuf->lock);
>> +		/* (for sparse checker) pretend gbuf->lock is still taken */
>> +		__acquire(gbuf->lock);
>> +		return NULL;
>> +	}
>> +	return gbuf->ptr;
>> +}
>> +
>> +void erofs_put_gbuf(void *ptr) __releases(gbuf->lock)
> z_erofs_put_gbuf
>
>> +{
>> +	struct erofs_gbuf *gbuf;
>> +
>> +	gbuf = &z_erofs_gbufpool.gbuf_array[erofs_cur_gbuf()];
>> +	DBG_BUGON(gbuf->ptr != ptr);
>> +	spin_unlock(&gbuf->lock);
>> +}
>> +
>> +int erofs_gbuf_growsize(unsigned int nrpages)
> z_erofs_get_growsize
>
>> +{
>> +	static DEFINE_MUTEX(gbuf_resize_mutex);
>> +	struct page *pagepool = NULL;
> Since we just grow global buffers, I think we could just copy
> the old array and fill the rest? so that pagepool is needed
> and we don't touch `struct page` anymore so `folio` is not
> eeded.
>
> But it can be done with a seperate patch.
>
>
>> +	int delta, ret, i, j;
>> +
>> +	if (!z_erofs_gbufpool.gbuf_array)
>> +		return -ENOMEM;
>> +
>> +	mutex_lock(&gbuf_resize_mutex);
>> +	delta = nrpages - z_erofs_gbufpool.gbuf_nrpages;
>> +	ret = 0;
> 	ret = -EINVAL;
>
>> +	/* avoid shrinking pcl_buf, since no idea how many fses rely on */
> /* avoid shrinking gbufs ... */
>
>> +	if (delta <= 0)
>> +		goto out;
>> +
>> +	for (i = 0; i < z_erofs_gbufpool.gbuf_num; ++i) {
>> +		struct erofs_gbuf *gbuf = &z_erofs_gbufpool.gbuf_array[i];
>> +		struct page **pages, **tmp_pages;
>> +		void *ptr, *old_ptr = NULL;
>> +
>> +		ret = -ENOMEM;
>> +		tmp_pages = kmalloc_array(nrpages, sizeof(*tmp_pages),
>> +				GFP_KERNEL);
>> +		if (!tmp_pages)
>> +			break;
>> +		for (j = 0; j < nrpages; ++j) {
>> +			tmp_pages[j] = erofs_allocpage(&pagepool, GFP_KERNEL);
>> +			if (!tmp_pages[j])
>> +				goto free_pagearray;
>> +		}
>> +		ptr = vmap(tmp_pages, nrpages, VM_MAP, PAGE_KERNEL);
>> +		if (!ptr)
>> +			goto free_pagearray;
>> +
>> +		pages = tmp_pages;
>> +		spin_lock(&gbuf->lock);
>> +		old_ptr = gbuf->ptr;
>> +		gbuf->ptr = ptr;
>> +		tmp_pages = gbuf->pages;
>> +		gbuf->pages = pages;
>> +		j = gbuf->nrpages;
>> +		gbuf->nrpages = nrpages;
>> +		spin_unlock(&gbuf->lock);
>> +		ret = 0;
>> +		if (!tmp_pages) {
>> +			DBG_BUGON(old_ptr);
>> +			continue;
>> +		}
>> +
>> +		if (old_ptr)
>> +			vunmap(old_ptr);
>> +free_pagearray:
>> +		while (j)
>> +			erofs_pagepool_add(&pagepool, tmp_pages[--j]);
>> +		kfree(tmp_pages);
>> +		if (ret)
>> +			break;
>> +	}
>> +	z_erofs_gbufpool.gbuf_nrpages = nrpages;
>> +	erofs_release_pages(&pagepool);
>> +out:
>> +	mutex_unlock(&gbuf_resize_mutex);
>> +	return ret;
>> +}
>> +
>> +void __init erofs_gbuf_init(void)
>> +{
>> +	if (!z_erofs_gbufpool.gbuf_num)
>> +		z_erofs_gbufpool.gbuf_num = num_possible_cpus();
>> +	else if (z_erofs_gbufpool.gbuf_num > num_possible_cpus())
>> +		z_erofs_gbufpool.gbuf_num = num_possible_cpus();
> 	unsigned int i = num_possible_cpus();
>
> 	if (!z_erofs_gbuf_count)
> 		z_erofs_gbuf_count = cpus;
> 	else
> 		z_erofs_gbuf_count = z_erofs_gbuf_count < i ? : i;
>
>
>
>> +
>> +	z_erofs_gbufpool.gbuf_array = kmalloc_array(z_erofs_gbufpool.gbuf_num,
>> +			sizeof(*z_erofs_gbufpool.gbuf_array),
>> +			GFP_KERNEL | __GFP_ZERO);> +	if (!z_erofs_gbufpool.gbuf_array) {
>> +		erofs_err(NULL, "failed to alloc page for erofs gbuf_array\n");
>
> 		erofs_err(NULL, "failed to alloate global buffer array");
>
>
>> +		return;> +	}
>> +
>> +	for (int i = 0; i < z_erofs_gbufpool.gbuf_num; ++i)
> please don't use `for (int i..`.
>
> so	for (i = 0; i < z_erofs_gbufpool.gbuf_num; ++i)
>
> Thanks,
> Gao Xiang




More information about the Linux-erofs mailing list