modules: Use a better scheme for refcounting
PATCH 1/1 modules: Use a better scheme for refcounting
Current refcounting for modules (done if CONFIG_MODULE_UNLOAD=y) is using a lot of memory.
Each 'struct module' contains an [NR_CPUS] array of full cache lines.
This patch uses existing infrastructure (percpu_modalloc() & percpu_modfree()) to allocate percpu space for the refcount storage.
Instead of wasting NR_CPUS*128 bytes (on i386), we now use num_possible_cpus*sizeof(local_t) bytes.
On a typical distro, where NR_CPUS=8, shiping 2000 modules, we reduce size of module files by about 2 Mbytes. (1Kb per module)
Instead of having all refcounters in the same memory node - with TLB misses because of vmalloc() - this new implementation permits to have better NUMA properties, since each CPU will use storage on its preferred node, thanks to percpu storage.
---
include/linux/module.h | 25 ++++++++++++++++--------- kernel/module.c | 37 +++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 19 deletions(-)
include/linux/module.h +16 -9
@@ -217,11 +217,6 @@ void *__symbol_get_gpl(const char *symbol);#endif -struct module_ref -{ -local_t count; -}____cacheline_aligned ; -enum module_state {MODULE_STATE_LIVE ,
@@ -307,8 +302,11 @@ struct module#ifdef CONFIG_MODULE_UNLOAD /* Reference counts */ -struct module_ref ref[NR_CPUS ]; - +#ifdef CONFIG_SMP +char *refptr; +#else +local_t ref; +#endif /* What modules depend on me? */ struct list_head modules_which_use_me;
@@ -378,13 +376,22 @@ void __symbol_put(const char *symbol);#define symbol_put (x) __symbol_put(MODULE_SYMBOL_PREFIX #x)void symbol_put_addr (void *addr); +static inline local_t *__module_ref_addr (struct module *mod,int cpu) +{ +#ifdef CONFIG_SMP +return (local_t *) (mod->refptr +per_cpu_offset (cpu)); +#else +return &mod->ref; +#endif +} +/* Sometimes we know we already have a refcount, and it's easier not to handle the error case (which only happens with rmmod --wait). */ static inline void __module_get (struct module *module) {if (module) {BUG_ON (module_refcount (module) ==0 ); -local_inc (&module->ref[get_cpu ()].count); +local_inc (__module_ref_addr (module,get_cpu ()));put_cpu (); } }
@@ -396,7 +403,7 @@ static inline int try_module_get(struct module *module)if (module) {unsigned int cpu =get_cpu ();if (likely (module_is_live (module))) -local_inc (&module->ref[cpu].count); +local_inc (__module_ref_addr (module, cpu));else ret =0 ;put_cpu ();
kernel/module.c +27 -10
@@ -522,13 +522,13 @@ static char last_unloaded_module[MODULE_NAME_LEN+1];/* Init the unload section of the module. */ static void module_unload_init (struct module *mod) { -unsigned int i; +int cpu;INIT_LIST_HEAD (&mod->modules_which_use_me); -for (i =0 ; i <NR_CPUS ; i++) -local_set (&mod->ref[i].count,0 ); +for_each_possible_cpu (cpu) +local_set (__module_ref_addr (mod, cpu),0 );/* Hold reference count during initialization. */ -local_set (&mod->ref[raw_smp_processor_id ()].count,1 ); +local_set (__module_ref_addr (mod,raw_smp_processor_id ()),1 );/* Backwards compatibility macros put refcount during init. */ mod->waiter = current; }
@@ -659,10 +659,11 @@ static int try_stop_module(struct module *mod, int flags, int *forced)unsigned int module_refcount (struct module *mod) { -unsigned int i, total =0 ; +unsigned int total =0 ; +int cpu; -for (i =0 ; i <NR_CPUS ; i++) - total +=local_read (&mod->ref[i].count); +for_each_possible_cpu (cpu) + total +=local_read (__module_ref_addr (mod, cpu));return total; }EXPORT_SYMBOL (module_refcount);
@@ -824,7 +825,7 @@ void module_put(struct module *module) {if (module) {unsigned int cpu =get_cpu (); -local_dec (&module->ref[cpu].count); +local_dec (__module_ref_addr (module, cpu));/* Maybe they're waiting for us to drop reference? */ if (unlikely (!module_is_live (module)))wake_up_process (module->waiter);
@@ -1392,7 +1393,10 @@ static void free_module(struct module *mod)kfree (mod->args);if (mod->percpu)percpu_modfree (mod->percpu); - +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +if (mod->refptr) +percpu_modfree (mod->refptr); +#endif /* Free lock-classes: */ lockdep_free_key_range (mod->module_core, mod->core_size);
@@ -1760,6 +1764,9 @@ static struct module *load_module(void __user *umod,unsigned int markersstringsindex;struct module *mod;long err =0 ; +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +void *refptr =NULL ; +#endif void *percpu =NULL , *ptr =NULL ;/* Stops spurious gcc warning */ struct exception_table_entry *extable;mm_segment_t old_fs;
@@ -1904,6 +1911,12 @@ static struct module *load_module(void __user *umod,if (err <0 ) goto free_mod; +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) + refptr =percpu_modalloc (sizeof (local_t),sizeof (local_t), mod->name); +if (!refptr) + goto free_mod; + mod->refptr = refptr; +#endif if (pcpuindex) {/* We have a special allocation for this section. */ percpu = percpu_modalloc(sechdrs[pcpuindex].sh_size,
@@ -1911,7 +1924,7 @@ static struct module *load_module(void __user *umod, mod->name);if (!percpu) { err = -ENOMEM ; - goto free_mod; + goto free_percpu; } sechdrs[pcpuindex].sh_flags &= ~(unsigned long )SHF_ALLOC ; mod->percpu = percpu;
@@ -2164,6 +2177,10 @@ static struct module *load_module(void __user *umod, free_percpu:if (percpu)percpu_modfree (percpu); +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +if (refptr) +percpu_modfree (refptr); +#endif free_mod:kfree (args); free_hdr:
Quoted 4 lines · Eric Dumazet
Instead of wasting NR_CPUS*128 bytes (on i386), we now use num_possible_cpus*sizeof(local_t) bytes.
On a typical distro, where NR_CPUS=8,
More typical would be NR_CPUS=128, with NR_CPUS=four digits when Mike Travis et.al. are finished
Quoted 1 line
shiping 2000 modules, we reduce
Surely only the loaded modules count? Perhaps 20-30.
But it's a cool improvement. Very nice.
-Andi
Quoted 2 lines · On Friday 16 May 2008 06:40:37 Eric Dumazet wrote:
Current refcounting for modules (done if
CONFIG_MODULE_UNLOAD=y) is using a lot of memory.
Hi Eric,
I like this patch! The plan was always to create a proper dynamic per-cpu
allocator which used the normal per-cpu offsets, but I think module refcounts are worthwhile as a special case.
Any chance I can ask you look at the issue of full dynamic per-cpu
allocation? The problem of allocating memory which is laid out precisely as the original per-cpu alloc is vexing on NUMA, and probably requires reserving virtual address space and remapping into it, but the rewards would be maximally-efficient per-cpu accessors, and getting rid of that boutique allocator in module.c.
Only minor commentry on the patch itself:
include/linux/module.h struct module · line 307
Quoted 3 lines
include/linux/module.h +3
-0+#ifdef CONFIG_SMP +char *refptr; +#else
void * would seem more natural here (love those gcc'isms)
kernel/module.c static struct module *load_module(void __user *umod, · line 1769
Quoted 3 lines
kernel/module.c +3
-0+#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +void *refptr =NULL ; +#endif
Looks like you can skip this if you assign to mod->refptr directly below:
kernel/module.c static struct module *load_module(void __user *umod, · line 1919
Quoted 6 lines
kernel/module.c +6
-0+#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) + refptr =percpu_modalloc (sizeof (local_t),sizeof (local_t), mod->name); +if (!refptr) + goto free_mod; + mod->refptr = refptr; +#endif
And finally:
kernel/module.c static struct module *load_module(void __user *umod, · line 2183
Quoted 4 lines
kernel/module.c +4
-0+#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +if (refptr) +percpu_modfree (refptr); +#endif
This if (refptr) seems redundant.
Thanks!
Rusty.
Quoted 11 lines · Andi Kleen
Quoted 5 lines · Eric Dumazet
Instead of wasting NR_CPUS*128 bytes (on i386), we now use num_possible_cpus*sizeof(local_t) bytes.
On a typical distro, where NR_CPUS=8,
More typical would be NR_CPUS=128, with NR_CPUS=four digits when Mike Travis et.al. are finished
Yes. With NR_CPUS=4096, we save about half a megabyte per module. (!!!)
Quoted 6 lines
Quoted 2 lines
shiping 2000 modules, we reduce
Surely only the loaded modules count? Perhaps 20-30.
Well, I should have stated that this saving also takes place in the module disk file. (The "struct module" is included in it, in the ".gnu.linkonce.this_module" section) Here on my distro, around 2000 modules are shiped.
Quoted 2 lines
But it's a cool improvement. Very nice.
Thanks
Quoted 12 lines · Rusty Russell
Quoted 3 lines · On Friday 16 May 2008 06:40:37 Eric Dumazet wrote:
Current refcounting for modules (done if
CONFIG_MODULE_UNLOAD=y) is using a lot of memory.Hi Eric,
I like this patch! The plan was always to create a proper dynamic per-cpu
allocator which used the normal per-cpu offsets, but I think module refcounts are worthwhile as a special case.
Quoted 8 lines
Any chance I can ask you look at the issue of full dynamic per-cpu
allocation? The problem of allocating memory which is laid out precisely as the original per-cpu alloc is vexing on NUMA, and probably requires reserving virtual address space and remapping into it, but the rewards would be maximally-efficient per-cpu accessors, and getting rid of that boutique allocator inmodule.c.
You mean using alloc_percpu() ? Problem is that current implementation is expensive, since it is using an extra array of pointers (struct percpu_data). On x86_64, that means at least a 200% space increase over the solution of using 4 bytes in the static percpu zone. We probably can change this to dynamic per-cpu as soon as Mike or Christopher finish their work on new dynamic per-cpu implementation ?
Quoted 11 lines
Only minor commentry on the patch itself:
Review
Quoted 4 lines
(quoted diff) +3
-0+#ifdef CONFIG_SMP + char *refptr; +#elsevoid * would seem more natural here (love those gcc'isms)
Yes, sure.
Quoted 8 lines
Review
Quoted 4 lines
(quoted diff) +3
-0+#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP) + void *refptr = NULL; +#endifLooks like you can skip this if you assign to
mod->refptrdirectly below:
Quoted 9 lines
Review
Quoted 7 lines
(quoted diff) +6
-0+#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP) + refptr = percpu_modalloc(sizeof(local_t), sizeof(local_t), mod->name); + if (!refptr) + goto free_mod; + mod->refptr = refptr; +#endif
Unfortunatly no. I tried it and failed.
This is the problem that at freeing time, we cannot anymore use mod->refptr, since we just unmaped mod, using vfree().
You had same problem with percpu, and had to use a temporaty variable on stack :)
Quoted 12 lines
And finally:
Review
Quoted 5 lines
(quoted diff) +4
-0+#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP) + if (refptr) + percpu_modfree(refptr); +#endifThis if (refptr) seems redundant.
Yes, thanks a lot. Please find an updated patch.
Quoted 5 lines
Thanks!
Rusty.
[PATCH] modules: Use a better scheme for refcounting
Current refcounting for modules (done if CONFIG_MODULE_UNLOAD=y) is using a lot of memory (and disk space)
Each 'struct module' contains an [NR_CPUS] array of full cache lines.
This patch uses existing infrastructure (percpu_modalloc() & percpu_modfree()) to allocate percpu space for the refcount storage.
Instead of wasting NR_CPUS*128 bytes (on i386), we now use num_possible_cpus*sizeof(local_t) bytes.
On a typical distro, where NR_CPUS=8, shiping 2000 modules, we reduce size of module files by about 2 Mbytes. (1Kb per module)
Instead of having all refcounters in the same memory node - with TLB misses because of vmalloc() - this new implementation permits to have better NUMA properties, since each CPU will use storage on its preferred node, thanks to percpu storage.
---
include/linux/module.h | 25 +++++++++++++++--------- kernel/module.c | 40 +++++++++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 19 deletions(-)
include/linux/module.h +16 -9
@@ -217,11 +217,6 @@ void *__symbol_get_gpl(const char *symbol);#endif -struct module_ref -{ -local_t count; -}____cacheline_aligned ; -enum module_state {MODULE_STATE_LIVE ,
@@ -307,8 +302,11 @@ struct module#ifdef CONFIG_MODULE_UNLOAD /* Reference counts */ -struct module_ref ref[NR_CPUS ]; - +#ifdef CONFIG_SMP +void *refptr; +#else +local_t ref; +#endif /* What modules depend on me? */ struct list_head modules_which_use_me;
@@ -378,13 +376,22 @@ void __symbol_put(const char *symbol);#define symbol_put (x) __symbol_put(MODULE_SYMBOL_PREFIX #x)void symbol_put_addr (void *addr); +static inline local_t *__module_ref_addr (struct module *mod,int cpu) +{ +#ifdef CONFIG_SMP +return (local_t *) (mod->refptr +per_cpu_offset (cpu)); +#else +return &mod->ref; +#endif +} +/* Sometimes we know we already have a refcount, and it's easier not to handle the error case (which only happens with rmmod --wait). */ static inline void __module_get (struct module *module) {if (module) {BUG_ON (module_refcount (module) ==0 ); -local_inc (&module->ref[get_cpu ()].count); +local_inc (__module_ref_addr (module,get_cpu ()));put_cpu (); } }
@@ -396,7 +403,7 @@ static inline int try_module_get(struct module *module)if (module) {unsigned int cpu =get_cpu ();if (likely (module_is_live (module))) -local_inc (&module->ref[cpu].count); +local_inc (__module_ref_addr (module, cpu));else ret =0 ;put_cpu ();
kernel/module.c +30 -10
@@ -522,13 +522,13 @@ static char last_unloaded_module[MODULE_NAME_LEN+1];/* Init the unload section of the module. */ static void module_unload_init (struct module *mod) { -unsigned int i; +int cpu;INIT_LIST_HEAD (&mod->modules_which_use_me); -for (i =0 ; i <NR_CPUS ; i++) -local_set (&mod->ref[i].count,0 ); +for_each_possible_cpu (cpu) +local_set (__module_ref_addr (mod, cpu),0 );/* Hold reference count during initialization. */ -local_set (&mod->ref[raw_smp_processor_id ()].count,1 ); +local_set (__module_ref_addr (mod,raw_smp_processor_id ()),1 );/* Backwards compatibility macros put refcount during init. */ mod->waiter = current; }
@@ -659,10 +659,11 @@ static int try_stop_module(struct module *mod, int flags, int *forced)unsigned int module_refcount (struct module *mod) { -unsigned int i, total =0 ; +unsigned int total =0 ; +int cpu; -for (i =0 ; i <NR_CPUS ; i++) - total +=local_read (&mod->ref[i].count); +for_each_possible_cpu (cpu) + total +=local_read (__module_ref_addr (mod, cpu));return total; }EXPORT_SYMBOL (module_refcount);
@@ -824,7 +825,7 @@ void module_put(struct module *module) {if (module) {unsigned int cpu =get_cpu (); -local_dec (&module->ref[cpu].count); +local_dec (__module_ref_addr (module, cpu));/* Maybe they're waiting for us to drop reference? */ if (unlikely (!module_is_live (module)))wake_up_process (module->waiter);
@@ -1392,7 +1393,9 @@ static void free_module(struct module *mod)kfree (mod->args);if (mod->percpu)percpu_modfree (mod->percpu); - +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +percpu_modfree (mod->refptr); +#endif /* Free lock-classes: */ lockdep_free_key_range (mod->module_core, mod->core_size);
@@ -1760,6 +1763,9 @@ static struct module *load_module(void __user *umod,unsigned int markersstringsindex;struct module *mod;long err =0 ; +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +void *refptr =NULL ; +#endif void *percpu =NULL , *ptr =NULL ;/* Stops spurious gcc warning */ struct exception_table_entry *extable;mm_segment_t old_fs;
@@ -1904,6 +1910,16 @@ static struct module *load_module(void __user *umod,if (err <0 ) goto free_mod; +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) + refptr =percpu_modalloc (sizeof (local_t), +sizeof (local_t), + mod->name); +if (!refptr) { + err = -ENOMEM ; + goto free_mod; + } + mod->refptr = refptr; +#endif if (pcpuindex) {/* We have a special allocation for this section. */ percpu = percpu_modalloc(sechdrs[pcpuindex].sh_size,
@@ -1911,7 +1927,7 @@ static struct module *load_module(void __user *umod, mod->name);if (!percpu) { err = -ENOMEM ; - goto free_mod; + goto free_refptr; } sechdrs[pcpuindex].sh_flags &= ~(unsigned long )SHF_ALLOC ; mod->percpu = percpu;
@@ -2164,6 +2180,10 @@ static struct module *load_module(void __user *umod, free_percpu:if (percpu)percpu_modfree (percpu); + free_refptr: +#if defined(CONFIG_MODULE_UNLOAD ) && defined(CONFIG_SMP ) +percpu_modfree (refptr); +#endif free_mod:kfree (args); free_hdr:
Quoted 1 line · Eric Dumazet
Rusty Russell a écrit :
...
Quoted 17 lines
Quoted 9 lines
Hi Eric,
I like this patch! The plan was always to create a proper dynamic
per-cpu
allocator which used the normal per-cpu offsets, but I think module refcounts
are worthwhile as a special case.Quoted 8 lines
Any chance I can ask you look at the issue of full dynamic per-cpu
allocation? The problem of allocating memory which is laid out precisely as the original per-cpu alloc is vexing on NUMA, and probably requires reserving virtual address space and remapping into it, but the rewards would be maximally-efficient per-cpu accessors, and getting rid of that boutique allocator inmodule.c.
Quoted 8 lines
You mean using
alloc_percpu()? Problem is that current implementation is expensive, since it is using
an extra array of pointers (structpercpu_data). Onx86_64, that means at least a 200% space increase
over the solution of using 4 bytes in the static percpu zone. We probably can change this to dynamic
per-cpu as soon as Mike or Christopher finish their work on new dynamic per-cpu implementation ?
Yes, the zero-based percpu variables followed by the cpu_alloc patch should provide this and shrink the code quite well, including in some cases removing locking requirements (because the resultant instructions will be atomic.)
Thanks,
Mike
Quoted 7 lines · On Friday 16 May 2008 23:41:16 Mike Travis wrote:
Quoted 6 lines · Eric Dumazet
Rusty Russell a écrit :
Quoted 2 lines
Any chance I can ask you look at the issue of full dynamic per-cpu
allocation?You mean using
alloc_percpu()? Problem is that current implementation is expensive,
I mean rewriting alloc_percpu :)
Quoted 7 lines
Quoted 2 lines
We probably can change this to dynamic per-cpu as soon as Mike or Christopher finish their work on new dynamic per-cpu implementation ?
Yes, the zero-based percpu variables followed by the
cpu_allocpatch should provide this and shrink the code quite well, including in some cases removing locking requirements (because the resultant instructions will be atomic.)
Ah, I hadn't realized that Mike was already working on this. Mike, have you published patches already?
Thanks,
Rusty.
Quoted 19 lines · Rusty Russell
Quoted 10 lines · On Friday 16 May 2008 23:41:16 Mike Travis wrote:
Quoted 8 lines · Eric Dumazet
Rusty Russell a écrit :
Quoted 3 lines
Any chance I can ask you look at the issue of full dynamic per-cpu
allocation?You mean using
alloc_percpu()? Problem is that current implementation is expensive,I mean rewriting
alloc_percpu:)Quoted 3 lines
Quoted 3 lines
We probably can change this to dynamic per-cpu as soon as Mike or Christopher finish their work on new dynamic per-cpu implementation ?
Quoted 9 lines
Quoted 5 lines
Yes, the zero-based percpu variables followed by the
cpu_allocpatch should provide this and shrink the code quite well, including in some cases removing locking requirements (because the resultant instructions will be atomic.)Ah, I hadn't realized that Mike was already working on this. Mike, have you published patches already?
Christoph Lameter made good work last year and apparently the path is to :
1) Put pda at the begining of percpu section.
2) Relocate percpu variables to begin at zero
3) Use %gs (or %fs) register to address pda AND percpu section.
4) Reserve an appropriate large virtual zone into percpu section so that it can fullfill :
- percpu "static" allocations needed by module loader.
- per_cpu "dynamic" allocations
(No more need for an array of pointers to find the address of dynamicper_cpu variable)
- Eventually allocates PAGES dynamically into this zone to satisfy any size of percpu needs. (But with a limit of say 256 MB per cpu on x86_64)
Thats a lot of work and apparently Mike and Christoph are doing it.
Some pointers to previous work:
http://kerneltrap.org/mailarchive/linux-kernel/2008/2/1/683104
http://linux.derkeiler.com/Mailing-Lists/Kernel/2008-01/msg09052.html
Quoted 3 lines · On Saturday 17 May 2008 17:36:21 Eric Dumazet wrote:
Christoph Lameter made good work last year and apparently the path is to :
1) Put pda at the begining of percpu section.
Well, we killed the pda on i386: it's basically a limited arch-specific percpu section. Unfortunately, ISTR that doing so kills -fstack-protector on x86-64 (gcc was explicitly patched to support the kernel doing this, but it nailed us to a fixed offset off %fs). But the PDA is a redundant idea which should be removed.
Quoted 2 lines
2) Relocate percpu variables to begin at zero
3) Use %gs (or %fs) register to address pda AND percpu section.
Thanks for the prod Eric. ISTR acking those patches; Christoph, what happened to them?
Cheers,
Rusty.
Quoted 4 lines · Rusty Russell
Quoted 2 lines
Quoted 2 lines
You mean using
alloc_percpu()? Problem is that current implementation is expensive,I mean rewriting
alloc_percpu:)
cpu_alooc is going to replace that completely.
Quoted 11 lines
Quoted 8 lines
Quoted 3 lines
We probably can change this to dynamic per-cpu as soon as Mike or Christopher finish their work on new dynamic per-cpu implementation ?
Yes, the zero-based percpu variables followed by the
cpu_allocpatch should provide this and shrink the code quite well, including in some cases removing locking requirements (because the resultant instructions will be atomic.)Ah, I hadn't realized that Mike was already working on this. Mike, have you published patches already?
He is reworking my patches. He can use some encouragement though...
Quoted 1 line · Eric Dumazet
Thats a lot of work and apparently Mike and Christoph are doing it.
Indeed. Thanks for the support. I hope Mike gets the stuff out soon. I know he was working on one issue that cause the zero based stuff to break. Maybe it would help if he would post what he has so far?
Quoted 2 lines · Rusty Russell
Thanks for the prod Eric. ISTR acking those patches; Christoph, what happened to them?
Mike is handling them.
Quoted 7 lines · Christoph Lameter
Quoted 1 line · Eric Dumazet
Thats a lot of work and apparently Mike and Christoph are doing it.
Indeed. Thanks for the support. I hope Mike gets the stuff out soon. I know he was working on one issue that cause the zero based stuff to break. Maybe it would help if he would post what he has so far?
I'm re-basing it on the x86/latest tree as sched/latest was failing my tests last week on both the AMD and Intel boxes. As soon as I re-verify everything I'll send it in.
Thanks,
Mike