From: Alexander Lobakin <hidden> Date: 2021-02-10 00:16:29
Currently, all sorts of skb allocation always do allocate
skbuff_heads one by one via kmem_cache_alloc().
On the other hand, we have percpu napi_alloc_cache to store
skbuff_heads queued up for freeing and flush them by bulks.
We can use this cache not only for bulk-wiping, but also to obtain
heads for new skbs and avoid unconditional allocations, as well as
for bulk-allocating.
As accessing napi_alloc_cache implies NAPI softirq context, decaching
is protected with in_serving_softirq() check, with the option to
bypass the check when the context is 100% known.
iperf3 showed 35-70 Mbps bumps for both TCP and UDP while performing
VLAN NAT on 1.2 GHz MIPS board. The boost is likely to be way bigger
on more powerful hosts and NICs with tens of Mpps.
Note on skbuff_heads from distant slabs or pfmemalloc'ed slabs:
- kmalloc()/kmem_cache_alloc() itself allows by default allocating
memory from the remote nodes to defragment their slabs. This is
controlled by sysctl, but according to this, skbuff_head from a
remote node is an OK case;
- The easiest way to check if the slab of skbuff_head is remote or
pfmemalloc'ed is:
if (!dev_page_is_reusable(virt_to_head_page(skb)))
/* drop it */;
...*but*, regarding that most slabs are built of compound pages,
virt_to_head_page() will hit unlikely-branch every single call.
This check costed at least 20 Mbps in test scenarios and seems
like it'd be better to _not_ do this.
Since v2 [1]:
- also cover {,__}alloc_skb() and {,__}build_skb() cases (became handy
after the changes that pass tiny skbs requests to kmalloc layer);
- cover the cache with KASAN instrumentation (suggested by Eric
Dumazet, help of Dmitry Vyukov);
- completely drop redundant __kfree_skb_flush() (also Eric);
- lots of code cleanups;
- expand the commit message with NUMA and pfmemalloc points (Jakub).
Since v1 [0]:
- use one unified cache instead of two separate to greatly simplify
the logics and reduce hotpath overhead (Edward Cree);
- new: recycle also GRO_MERGED_FREE skbs instead of immediate
freeing;
- correct performance numbers after optimizations and performing
lots of tests for different use cases.
[0] https://lore.kernel.org/netdev/20210111182655.12159-1-alobakin@pm.me
[1] https://lore.kernel.org/netdev/20210113133523.39205-1-alobakin@pm.me
Alexander Lobakin (10):
skbuff: move __alloc_skb() next to the other skb allocation functions
skbuff: simplify kmalloc_reserve()
skbuff: make __build_skb_around() return void
skbuff: simplify __alloc_skb() a bit
skbuff: use __build_skb_around() in __alloc_skb()
skbuff: remove __kfree_skb_flush()
skbuff: move NAPI cache declarations upper in the file
skbuff: reuse NAPI skb cache on allocation path (__build_skb())
skbuff: reuse NAPI skb cache on allocation path (__alloc_skb())
skbuff: queue NAPI_MERGED_FREE skbs into NAPI cache instead of freeing
include/linux/skbuff.h | 4 +-
net/core/dev.c | 15 +-
net/core/skbuff.c | 392 ++++++++++++++++++++-------------------
net/netlink/af_netlink.c | 2 +-
4 files changed, 202 insertions(+), 211 deletions(-)
--
2.30.0
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:17:19
In preparation before reusing several functions in all three skb
allocation variants, move __alloc_skb() next to the
__netdev_alloc_skb() and __napi_alloc_skb().
No functional changes.
Signed-off-by: Alexander Lobakin <redacted>
---
net/core/skbuff.c | 284 +++++++++++++++++++++++-----------------------
1 file changed, 142 insertions(+), 142 deletions(-)
@@ -119,148 +119,6 @@ static void skb_under_panic(struct sk_buff *skb, unsigned int sz, void *addr)skb_panic(skb,sz,addr,__func__);}-/*-*kmalloc_reserveisawrapperaroundkmalloc_node_track_callerthattells-*thecallerifemergencypfmemallocreservesarebeingused.Ifitisand-*thesocketislaterfoundtobeSOCK_MEMALLOCthenPFMEMALLOCreserves-*maybeused.Otherwise,thepacketdatamaybediscardeduntilenough-*memoryisfree-*/-#define kmalloc_reserve(size, gfp, node, pfmemalloc) \-__kmalloc_reserve(size,gfp,node,_RET_IP_,pfmemalloc)--staticvoid*__kmalloc_reserve(size_tsize,gfp_tflags,intnode,-unsignedlongip,bool*pfmemalloc)-{-void*obj;-boolret_pfmemalloc=false;--/*-*Tryaregularallocation,whenthatfailsandwe'renotentitled-*tothereserves,fail.-*/-obj=kmalloc_node_track_caller(size,-flags|__GFP_NOMEMALLOC|__GFP_NOWARN,-node);-if(obj||!(gfp_pfmemalloc_allowed(flags)))-gotoout;--/* Try again but now we are using pfmemalloc reserves */-ret_pfmemalloc=true;-obj=kmalloc_node_track_caller(size,flags,node);--out:-if(pfmemalloc)-*pfmemalloc=ret_pfmemalloc;--returnobj;-}--/* Allocate a new skbuff. We do this ourselves so we can fill in a few-*'private'fieldsandalsodomemorystatisticstofindallthe-*[BEEP]leaks.-*-*/--/**-*__alloc_skb-allocateanetworkbuffer-*@size:sizetoallocate-*@gfp_mask:allocationmask-*@flags:IfSKB_ALLOC_FCLONEisset,allocatefromfclonecache-*insteadofheadcacheandallocateacloned(child)skb.-*IfSKB_ALLOC_RXisset,__GFP_MEMALLOCwillbeusedfor-*allocationsincasethedataisrequiredforwriteback-*@node:numanodetoallocatememoryon-*-*Allocateanew&sk_buff.Thereturnedbufferhasnoheadroomanda-*tailroomofatleastsizebytes.Theobjecthasareferencecount-*ofone.Thereturnisthebuffer.Onafailurethereturnis%NULL.-*-*Buffersmayonlybeallocatedfrominterruptsusinga@gfp_maskof-*%GFP_ATOMIC.-*/-structsk_buff*__alloc_skb(unsignedintsize,gfp_tgfp_mask,-intflags,intnode)-{-structkmem_cache*cache;-structskb_shared_info*shinfo;-structsk_buff*skb;-u8*data;-boolpfmemalloc;--cache=(flags&SKB_ALLOC_FCLONE)-?skbuff_fclone_cache:skbuff_head_cache;--if(sk_memalloc_socks()&&(flags&SKB_ALLOC_RX))-gfp_mask|=__GFP_MEMALLOC;--/* Get the HEAD */-skb=kmem_cache_alloc_node(cache,gfp_mask&~__GFP_DMA,node);-if(!skb)-gotoout;-prefetchw(skb);--/* We do our best to align skb_shared_info on a separate cache-*line.Itusuallyworksbecausekmalloc(X>SMP_CACHE_BYTES)gives-*alignedmemoryblocks,unlessSLUB/SLABdebugisenabled.-*Bothskb->headandskb_shared_infoarecachelinealigned.-*/-size=SKB_DATA_ALIGN(size);-size+=SKB_DATA_ALIGN(sizeof(structskb_shared_info));-data=kmalloc_reserve(size,gfp_mask,node,&pfmemalloc);-if(!data)-gotonodata;-/* kmalloc(size) might give us more room than requested.-*Putskb_shared_infoexactlyattheendofallocatedzone,-*toallowmaxpossiblefillingbeforereallocation.-*/-size=SKB_WITH_OVERHEAD(ksize(data));-prefetchw(data+size);--/*-*Onlyclearthosefieldsweneedtoclear,notthosethatwewill-*actuallyinitialisebelow.Hence,don'tputanymorefieldsafter-*thetailpointerinstructsk_buff!-*/-memset(skb,0,offsetof(structsk_buff,tail));-/* Account for allocated memory : skb + skb->head */-skb->truesize=SKB_TRUESIZE(size);-skb->pfmemalloc=pfmemalloc;-refcount_set(&skb->users,1);-skb->head=data;-skb->data=data;-skb_reset_tail_pointer(skb);-skb->end=skb->tail+size;-skb->mac_header=(typeof(skb->mac_header))~0U;-skb->transport_header=(typeof(skb->transport_header))~0U;--/* make sure we initialize shinfo sequentially */-shinfo=skb_shinfo(skb);-memset(shinfo,0,offsetof(structskb_shared_info,dataref));-atomic_set(&shinfo->dataref,1);--if(flags&SKB_ALLOC_FCLONE){-structsk_buff_fclones*fclones;--fclones=container_of(skb,structsk_buff_fclones,skb1);--skb->fclone=SKB_FCLONE_ORIG;-refcount_set(&fclones->fclone_ref,1);--fclones->skb2.fclone=SKB_FCLONE_CLONE;-}--skb_set_kcov_handle(skb,kcov_common_handle());--out:-returnskb;-nodata:-kmem_cache_free(cache,skb);-skb=NULL;-gotoout;-}-EXPORT_SYMBOL(__alloc_skb);-/* Caller must provide SKB that is memset cleared */staticstructsk_buff*__build_skb_around(structsk_buff*skb,void*data,unsignedintfrag_size)
@@ -408,6 +266,148 @@ void *__netdev_alloc_frag_align(unsigned int fragsz, unsigned int align_mask)}EXPORT_SYMBOL(__netdev_alloc_frag_align);+/*+*kmalloc_reserveisawrapperaroundkmalloc_node_track_callerthattells+*thecallerifemergencypfmemallocreservesarebeingused.Ifitisand+*thesocketislaterfoundtobeSOCK_MEMALLOCthenPFMEMALLOCreserves+*maybeused.Otherwise,thepacketdatamaybediscardeduntilenough+*memoryisfree+*/+#define kmalloc_reserve(size, gfp, node, pfmemalloc) \+__kmalloc_reserve(size,gfp,node,_RET_IP_,pfmemalloc)++staticvoid*__kmalloc_reserve(size_tsize,gfp_tflags,intnode,+unsignedlongip,bool*pfmemalloc)+{+void*obj;+boolret_pfmemalloc=false;++/*+*Tryaregularallocation,whenthatfailsandwe'renotentitled+*tothereserves,fail.+*/+obj=kmalloc_node_track_caller(size,+flags|__GFP_NOMEMALLOC|__GFP_NOWARN,+node);+if(obj||!(gfp_pfmemalloc_allowed(flags)))+gotoout;++/* Try again but now we are using pfmemalloc reserves */+ret_pfmemalloc=true;+obj=kmalloc_node_track_caller(size,flags,node);++out:+if(pfmemalloc)+*pfmemalloc=ret_pfmemalloc;++returnobj;+}++/* Allocate a new skbuff. We do this ourselves so we can fill in a few+*'private'fieldsandalsodomemorystatisticstofindallthe+*[BEEP]leaks.+*+*/++/**+*__alloc_skb-allocateanetworkbuffer+*@size:sizetoallocate+*@gfp_mask:allocationmask+*@flags:IfSKB_ALLOC_FCLONEisset,allocatefromfclonecache+*insteadofheadcacheandallocateacloned(child)skb.+*IfSKB_ALLOC_RXisset,__GFP_MEMALLOCwillbeusedfor+*allocationsincasethedataisrequiredforwriteback+*@node:numanodetoallocatememoryon+*+*Allocateanew&sk_buff.Thereturnedbufferhasnoheadroomanda+*tailroomofatleastsizebytes.Theobjecthasareferencecount+*ofone.Thereturnisthebuffer.Onafailurethereturnis%NULL.+*+*Buffersmayonlybeallocatedfrominterruptsusinga@gfp_maskof+*%GFP_ATOMIC.+*/+structsk_buff*__alloc_skb(unsignedintsize,gfp_tgfp_mask,+intflags,intnode)+{+structkmem_cache*cache;+structskb_shared_info*shinfo;+structsk_buff*skb;+u8*data;+boolpfmemalloc;++cache=(flags&SKB_ALLOC_FCLONE)+?skbuff_fclone_cache:skbuff_head_cache;++if(sk_memalloc_socks()&&(flags&SKB_ALLOC_RX))+gfp_mask|=__GFP_MEMALLOC;++/* Get the HEAD */+skb=kmem_cache_alloc_node(cache,gfp_mask&~__GFP_DMA,node);+if(!skb)+gotoout;+prefetchw(skb);++/* We do our best to align skb_shared_info on a separate cache+*line.Itusuallyworksbecausekmalloc(X>SMP_CACHE_BYTES)gives+*alignedmemoryblocks,unlessSLUB/SLABdebugisenabled.+*Bothskb->headandskb_shared_infoarecachelinealigned.+*/+size=SKB_DATA_ALIGN(size);+size+=SKB_DATA_ALIGN(sizeof(structskb_shared_info));+data=kmalloc_reserve(size,gfp_mask,node,&pfmemalloc);+if(!data)+gotonodata;+/* kmalloc(size) might give us more room than requested.+*Putskb_shared_infoexactlyattheendofallocatedzone,+*toallowmaxpossiblefillingbeforereallocation.+*/+size=SKB_WITH_OVERHEAD(ksize(data));+prefetchw(data+size);++/*+*Onlyclearthosefieldsweneedtoclear,notthosethatwewill+*actuallyinitialisebelow.Hence,don'tputanymorefieldsafter+*thetailpointerinstructsk_buff!+*/+memset(skb,0,offsetof(structsk_buff,tail));+/* Account for allocated memory : skb + skb->head */+skb->truesize=SKB_TRUESIZE(size);+skb->pfmemalloc=pfmemalloc;+refcount_set(&skb->users,1);+skb->head=data;+skb->data=data;+skb_reset_tail_pointer(skb);+skb->end=skb->tail+size;+skb->mac_header=(typeof(skb->mac_header))~0U;+skb->transport_header=(typeof(skb->transport_header))~0U;++/* make sure we initialize shinfo sequentially */+shinfo=skb_shinfo(skb);+memset(shinfo,0,offsetof(structskb_shared_info,dataref));+atomic_set(&shinfo->dataref,1);++if(flags&SKB_ALLOC_FCLONE){+structsk_buff_fclones*fclones;++fclones=container_of(skb,structsk_buff_fclones,skb1);++skb->fclone=SKB_FCLONE_ORIG;+refcount_set(&fclones->fclone_ref,1);++fclones->skb2.fclone=SKB_FCLONE_CLONE;+}++skb_set_kcov_handle(skb,kcov_common_handle());++out:+returnskb;+nodata:+kmem_cache_free(cache,skb);+skb=NULL;+gotoout;+}+EXPORT_SYMBOL(__alloc_skb);+/***__netdev_alloc_skb-allocateanskbuffforrxonaspecificdevice*@dev:networkdevicetoreceiveon
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:18:31
Use unlikely() annotations for skbuff_head and data similarly to the
two other allocation functions and remove totally redundant goto.
Signed-off-by: Alexander Lobakin <redacted>
---
net/core/skbuff.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
@@ -339,8 +339,8 @@ struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,/* Get the HEAD */skb=kmem_cache_alloc_node(cache,gfp_mask&~__GFP_DMA,node);-if(!skb)-gotoout;+if(unlikely(!skb))+returnNULL;prefetchw(skb);/* We do our best to align skb_shared_info on a separate cache
@@ -351,7 +351,7 @@ struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,size=SKB_DATA_ALIGN(size);size+=SKB_DATA_ALIGN(sizeof(structskb_shared_info));data=kmalloc_reserve(size,gfp_mask,node,&pfmemalloc);-if(!data)+if(unlikely(!data))gotonodata;/* kmalloc(size) might give us more room than requested.*Putskb_shared_infoexactlyattheendofallocatedzone,
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:18:32
napi_frags_finish() and napi_skb_finish() can only be called inside
NAPI Rx context, so we can feed NAPI cache with skbuff_heads that
got NAPI_MERGED_FREE verdict instead of immediate freeing.
Replace __kfree_skb() with __kfree_skb_defer() in napi_skb_finish()
and move napi_skb_free_stolen_head() to skbuff.c, so it can drop skbs
to NAPI cache.
As many drivers call napi_alloc_skb()/napi_get_frags() on their
receive path, this becomes especially useful.
Signed-off-by: Alexander Lobakin <redacted>
---
include/linux/skbuff.h | 1 +
net/core/dev.c | 9 +--------
net/core/skbuff.c | 12 +++++++++---
3 files changed, 11 insertions(+), 11 deletions(-)
@@ -872,9 +872,6 @@ static void napi_skb_cache_put(struct sk_buff *skb)structnapi_alloc_cache*nc=this_cpu_ptr(&napi_alloc_cache);u32i;-/* drop skb->head and call any destructors for packet */-skb_release_all(skb);-kasan_poison_object_data(skbuff_head_cache,skb);nc->skb_cache[nc->skb_count++]=skb;
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:18:53
NAPI cache structures will be used for allocating skbuff_heads,
so move their declarations a bit upper.
Signed-off-by: Alexander Lobakin <redacted>
---
net/core/skbuff.c | 90 +++++++++++++++++++++++------------------------
1 file changed, 45 insertions(+), 45 deletions(-)
@@ -119,6 +119,51 @@ static void skb_under_panic(struct sk_buff *skb, unsigned int sz, void *addr)skb_panic(skb,sz,addr,__func__);}+#define NAPI_SKB_CACHE_SIZE 64++structnapi_alloc_cache{+structpage_frag_cachepage;+unsignedintskb_count;+void*skb_cache[NAPI_SKB_CACHE_SIZE];+};++staticDEFINE_PER_CPU(structpage_frag_cache,netdev_alloc_cache);+staticDEFINE_PER_CPU(structnapi_alloc_cache,napi_alloc_cache);++staticvoid*__alloc_frag_align(unsignedintfragsz,gfp_tgfp_mask,+unsignedintalign_mask)+{+structnapi_alloc_cache*nc=this_cpu_ptr(&napi_alloc_cache);++returnpage_frag_alloc_align(&nc->page,fragsz,gfp_mask,align_mask);+}++void*__napi_alloc_frag_align(unsignedintfragsz,unsignedintalign_mask)+{+fragsz=SKB_DATA_ALIGN(fragsz);++return__alloc_frag_align(fragsz,GFP_ATOMIC,align_mask);+}+EXPORT_SYMBOL(__napi_alloc_frag_align);++void*__netdev_alloc_frag_align(unsignedintfragsz,unsignedintalign_mask)+{+structpage_frag_cache*nc;+void*data;++fragsz=SKB_DATA_ALIGN(fragsz);+if(in_irq()||irqs_disabled()){+nc=this_cpu_ptr(&netdev_alloc_cache);+data=page_frag_alloc_align(nc,fragsz,GFP_ATOMIC,align_mask);+}else{+local_bh_disable();+data=__alloc_frag_align(fragsz,GFP_ATOMIC,align_mask);+local_bh_enable();+}+returndata;+}+EXPORT_SYMBOL(__netdev_alloc_frag_align);+/* Caller must provide SKB that is memset cleared */staticvoid__build_skb_around(structsk_buff*skb,void*data,unsignedintfrag_size)
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:19:29
__build_skb_around() can never fail and always returns passed skb.
Make it return void to simplify and optimize the code.
Signed-off-by: Alexander Lobakin <redacted>
---
net/core/skbuff.c | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
@@ -120,8 +120,8 @@ static void skb_under_panic(struct sk_buff *skb, unsigned int sz, void *addr)}/* Caller must provide SKB that is memset cleared */-staticstructsk_buff*__build_skb_around(structsk_buff*skb,-void*data,unsignedintfrag_size)+staticvoid__build_skb_around(structsk_buff*skb,void*data,+unsignedintfrag_size){structskb_shared_info*shinfo;unsignedintsize=frag_size?:ksize(data);
@@ -176,8 +174,9 @@ struct sk_buff *__build_skb(void *data, unsigned int frag_size)returnNULL;memset(skb,0,offsetof(structsk_buff,tail));+__build_skb_around(skb,data,frag_size);-return__build_skb_around(skb,data,frag_size);+returnskb;}/* build_skb() is wrapper over __build_skb(), that specifically
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:25:22
Try to use the same technique for obtaining skbuff_head from NAPI
cache in {,__}alloc_skb(). Two points here:
- __alloc_skb() can be used for allocating clones or allocating skbs
for distant nodes. Try to grab head from the cache only for
non-clones and for local nodes;
- can be called from any context, so napi_safe == false.
Signed-off-by: Alexander Lobakin <redacted>
---
net/core/skbuff.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
@@ -354,15 +354,19 @@ struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,structsk_buff*skb;u8*data;boolpfmemalloc;+boolclone;-cache=(flags&SKB_ALLOC_FCLONE)-?skbuff_fclone_cache:skbuff_head_cache;+clone=!!(flags&SKB_ALLOC_FCLONE);+cache=clone?skbuff_fclone_cache:skbuff_head_cache;if(sk_memalloc_socks()&&(flags&SKB_ALLOC_RX))gfp_mask|=__GFP_MEMALLOC;/* Get the HEAD */-skb=kmem_cache_alloc_node(cache,gfp_mask&~__GFP_DMA,node);+if(clone||unlikely(node!=NUMA_NO_NODE&&node!=numa_mem_id()))+skb=kmem_cache_alloc_node(cache,gfp_mask&~GFP_DMA,node);+else+skb=napi_skb_cache_get(false);if(unlikely(!skb))returnNULL;prefetchw(skb);
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:33:58
Instead of just bulk-flushing skbuff_heads queued up through
napi_consume_skb() or __kfree_skb_defer(), try to reuse them
on allocation path.
If the cache is empty on allocation, bulk-allocate the first
half, which is more efficient than per-skb allocation.
If the cache is full on freeing, bulk-wipe the second half.
This also includes custom KASAN poisoning/unpoisoning to be
double sure there are no use-after-free cases.
Functions that got cache fastpath:
- {,__}build_skb();
- {,__}netdev_alloc_skb();
- {,__}napi_alloc_skb().
Note on "napi_safe" argument:
NAPI cache should be accessed only from BH-disabled or (better)
NAPI context. To make sure access is safe, in_serving_softirq()
check is used.
Hovewer, there are plenty of cases when we know for sure that
we're in such context. This includes: build_skb() (called only
from NIC drivers in NAPI Rx context) and {,__}napi_alloc_skb()
(called from the same place or from kernel network softirq
functions).
We can use that knowledge to avoid unnecessary checks.
Suggested-by: Edward Cree <ecree.xilinx@gmail.com> # Unified cache part
Suggested-by: Eric Dumazet <edumazet@google.com> # KASAN poisoning
Suggested-by: Dmitry Vyukov <dvyukov@google.com> # Help with KASAN
Signed-off-by: Alexander Lobakin <redacted>
---
include/linux/skbuff.h | 2 +-
net/core/skbuff.c | 61 ++++++++++++++++++++++++++++------------
net/netlink/af_netlink.c | 2 +-
3 files changed, 45 insertions(+), 20 deletions(-)
@@ -164,6 +165,30 @@ void *__netdev_alloc_frag_align(unsigned int fragsz, unsigned int align_mask)}EXPORT_SYMBOL(__netdev_alloc_frag_align);+staticstructsk_buff*napi_skb_cache_get(boolnapi_safe)+{+structnapi_alloc_cache*nc;+structsk_buff*skb;++if(!napi_safe&&unlikely(!in_serving_softirq()))+returnkmem_cache_alloc(skbuff_head_cache,GFP_ATOMIC);++nc=this_cpu_ptr(&napi_alloc_cache);++if(unlikely(!nc->skb_count))+nc->skb_count=kmem_cache_alloc_bulk(skbuff_head_cache,+GFP_ATOMIC,+NAPI_SKB_CACHE_HALF,+nc->skb_cache);+if(unlikely(!nc->skb_count))+returnNULL;++skb=nc->skb_cache[--nc->skb_count];+kasan_unpoison_object_data(skbuff_head_cache,skb);++returnskb;+}+/* Caller must provide SKB that is memset cleared */staticvoid__build_skb_around(structsk_buff*skb,void*data,unsignedintfrag_size)
@@ -838,31 +863,31 @@ void __consume_stateless_skb(struct sk_buff *skb)kfree_skbmem(skb);}-staticinlinevoid_kfree_skb_defer(structsk_buff*skb)+staticvoidnapi_skb_cache_put(structsk_buff*skb){structnapi_alloc_cache*nc=this_cpu_ptr(&napi_alloc_cache);+u32i;/* drop skb->head and call any destructors for packet */skb_release_all(skb);-/* record skb to CPU local list */+kasan_poison_object_data(skbuff_head_cache,skb);nc->skb_cache[nc->skb_count++]=skb;-#ifdef CONFIG_SLUB-/* SLUB writes into objects when freeing */-prefetchw(skb);-#endif--/* flush skb_cache if it is filled */if(unlikely(nc->skb_count==NAPI_SKB_CACHE_SIZE)){-kmem_cache_free_bulk(skbuff_head_cache,NAPI_SKB_CACHE_SIZE,-nc->skb_cache);-nc->skb_count=0;+for(i=NAPI_SKB_CACHE_HALF;i<NAPI_SKB_CACHE_SIZE;i++)+kasan_unpoison_object_data(skbuff_head_cache,+nc->skb_cache[i]);++kmem_cache_free_bulk(skbuff_head_cache,NAPI_SKB_CACHE_HALF,+nc->skb_cache+NAPI_SKB_CACHE_HALF);+nc->skb_count=NAPI_SKB_CACHE_HALF;}}+void__kfree_skb_defer(structsk_buff*skb){-_kfree_skb_defer(skb);+napi_skb_cache_put(skb);}voidnapi_consume_skb(structsk_buff*skb,intbudget)
@@ -887,7 +912,7 @@ void napi_consume_skb(struct sk_buff *skb, int budget)return;}-_kfree_skb_defer(skb);+napi_skb_cache_put(skb);}EXPORT_SYMBOL(napi_consume_skb);
From: Paolo Abeni <pabeni@redhat.com> Date: 2021-02-10 10:35:12
Hello,
I'm sorry for the late feedback, I could not step-in before.
Also adding Jesper for awareness, as he introduced the bulk free
infrastructure.
On Tue, 2021-02-09 at 20:48 +0000, Alexander Lobakin wrote:
I must admit I'm a bit scared of this. There are several high speed
device drivers that will move to bulk allocation, and we don't have any
performance figure for them.
In my experience with (low end) MIPS board, cache misses cost tend to
be much less visible there compared to reasonably recent server H/W,
because the CPU/memory access time difference is much lower.
When moving to higher end H/W the performance gain you measured could
be completely countered by less optimal cache usage.
I fear also latency spikes - I'm unsure if a 32 skbs allocation vs a
single skb would be visible e.g. in a round-robin test. Generally
speaking bulk allocating 32 skbs looks a bit too much. IIRC, when
Edward added listification to GRO, he did several measures with
different list size and found 8 to be the optimal value (for the tested
workload). Above such number the list become too big and the pressure
on the cache outweighted the bulking benefits.
Perhaps giving the device drivers the ability to opt-in on this infra
via a new helper - as done back then with napi_consume_skb() - would
make this change safer?
quoted hunk
@@ -838,31 +863,31 @@ void __consume_stateless_skb(struct sk_buff *skb) kfree_skbmem(skb); }-static inline void _kfree_skb_defer(struct sk_buff *skb)+static void napi_skb_cache_put(struct sk_buff *skb) { struct napi_alloc_cache *nc = this_cpu_ptr(&napi_alloc_cache);+ u32 i; /* drop skb->head and call any destructors for packet */ skb_release_all(skb);- /* record skb to CPU local list */+ kasan_poison_object_data(skbuff_head_cache, skb); nc->skb_cache[nc->skb_count++] = skb;-#ifdef CONFIG_SLUB- /* SLUB writes into objects when freeing */- prefetchw(skb);-#endif
It looks like this chunk has been lost. Is that intentional?
Thanks!
Paolo
From: Alexander Lobakin <hidden> Date: 2021-02-10 12:28:54
From: Paolo Abeni <pabeni@redhat.com>
Date: Wed, 10 Feb 2021 11:21:06 +0100
Hello,
Hi!
I'm sorry for the late feedback, I could not step-in before.
Also adding Jesper for awareness, as he introduced the bulk free
infrastructure.
On Tue, 2021-02-09 at 20:48 +0000, Alexander Lobakin wrote:
I must admit I'm a bit scared of this. There are several high speed
device drivers that will move to bulk allocation, and we don't have any
performance figure for them.
In my experience with (low end) MIPS board, cache misses cost tend to
be much less visible there compared to reasonably recent server H/W,
because the CPU/memory access time difference is much lower.
When moving to higher end H/W the performance gain you measured could
be completely countered by less optimal cache usage.
I fear also latency spikes - I'm unsure if a 32 skbs allocation vs a
single skb would be visible e.g. in a round-robin test. Generally
speaking bulk allocating 32 skbs looks a bit too much. IIRC, when
Edward added listification to GRO, he did several measures with
different list size and found 8 to be the optimal value (for the tested
workload). Above such number the list become too big and the pressure
on the cache outweighted the bulking benefits.
I can change to logics the way so it would allocate the first 8.
I think I've already seen this batch value somewhere in XDP code,
so this might be a balanced one.
Regarding bulk-freeing: can the batch size make sense when freeing
or it's okay to wipe 32 (currently 64 in baseline) in a row?
Perhaps giving the device drivers the ability to opt-in on this infra
via a new helper - as done back then with napi_consume_skb() - would
make this change safer?
That's actually a very nice idea. There's only a little in the code
to change to introduce an ability to take heads from the cache
optionally. This way developers could switch to it when needed.
Thanks for the suggestions! I'll definitely absorb them into the code
and give it a test.
quoted
@@ -838,31 +863,31 @@ void __consume_stateless_skb(struct sk_buff *skb) kfree_skbmem(skb); }-static inline void _kfree_skb_defer(struct sk_buff *skb)+static void napi_skb_cache_put(struct sk_buff *skb) { struct napi_alloc_cache *nc = this_cpu_ptr(&napi_alloc_cache);+ u32 i; /* drop skb->head and call any destructors for packet */ skb_release_all(skb);- /* record skb to CPU local list */+ kasan_poison_object_data(skbuff_head_cache, skb); nc->skb_cache[nc->skb_count++] = skb;-#ifdef CONFIG_SLUB- /* SLUB writes into objects when freeing */- prefetchw(skb);-#endif
It looks like this chunk has been lost. Is that intentional?
Yep. This prefetchw() assumed that skbuff_heads will be wiped
immediately or at the end of network softirq. Reusing this cache
means that heads can be reused later or may be kept in a cache for
some time, so prefetching makes no sense anymore.
From: Paolo Abeni <pabeni@redhat.com> Date: 2021-02-10 12:53:30
On Wed, 2021-02-10 at 12:25 +0000, Alexander Lobakin wrote:
Paolo Abeni [off-list ref] on Wed, 10 Feb 2021 11:21:06 +0100 wrote:
quoted
Perhaps giving the device drivers the ability to opt-in on this infra
via a new helper - as done back then with napi_consume_skb() - would
make this change safer?
That's actually a very nice idea. There's only a little in the code
to change to introduce an ability to take heads from the cache
optionally. This way developers could switch to it when needed.
Thanks for the suggestions! I'll definitely absorb them into the code
and give it a test.
Quick reply before is too late. I suggest to wait a bit for others
opinions before coding - if others dislike this I would regret wasting
your time.
Cheers,
Paolo
On Wed, 10 Feb 2021 12:25:04 +0000
Alexander Lobakin [off-list ref] wrote:
From: Paolo Abeni <pabeni@redhat.com>
Date: Wed, 10 Feb 2021 11:21:06 +0100
quoted
I'm sorry for the late feedback, I could not step-in before.
Also adding Jesper for awareness, as he introduced the bulk free
infrastructure.
Thanks (and Alexander Duyck also did part of the work while at Red Hat).
In my initial versions of my patchsets I actually also had reuse of the
SKBs that were defer freed during NAPI context. But I dropped that
part because it was getting nitpicked and the merge window was getting
close, so I ended up dropping that part.
quoted
On Tue, 2021-02-09 at 20:48 +0000, Alexander Lobakin wrote:
I must admit I'm a bit scared of this. There are several high speed
device drivers that will move to bulk allocation, and we don't have any
performance figure for them.
In my experience with (low end) MIPS board, cache misses cost tend to
be much less visible there compared to reasonably recent server H/W,
because the CPU/memory access time difference is much lower.
When moving to higher end H/W the performance gain you measured could
be completely countered by less optimal cache usage.
I fear also latency spikes - I'm unsure if a 32 skbs allocation vs a
single skb would be visible e.g. in a round-robin test. Generally
speaking bulk allocating 32 skbs looks a bit too much. IIRC, when
Edward added listification to GRO, he did several measures with
different list size and found 8 to be the optimal value (for the tested
workload). Above such number the list become too big and the pressure
on the cache outweighted the bulking benefits.
I can change to logics the way so it would allocate the first 8.
I think I've already seen this batch value somewhere in XDP code,
so this might be a balanced one.
(Speaking about SLUB code): Bulk ALLOC side disables interrupts, and
can call slow path (___slab_alloc), which is bad for latency sensitive
workloads. This I don't recommend large bulk ALLOCATIONS.
Regarding bulk-freeing: can the batch size make sense when freeing
or it's okay to wipe 32 (currently 64 in baseline) in a row?
(Speaking about SLUB code): You can bulk FREE large amount of object
without hurting latency sensitive workloads, because it doesn't disable
interrupts (I'm quite proud that this was possible).
quoted
Perhaps giving the device drivers the ability to opt-in on this infra
via a new helper - as done back then with napi_consume_skb() - would
make this change safer?
That's actually a very nice idea. There's only a little in the code
to change to introduce an ability to take heads from the cache
optionally. This way developers could switch to it when needed.
Well, I actually disagree that this should be hidden behind a switch
for drivers to enable, as this will take forever to get proper enabled.
Thanks for the suggestions! I'll definitely absorb them into the code
and give it a test.
quoted
quoted
@@ -838,31 +863,31 @@ void __consume_stateless_skb(struct sk_buff *skb) kfree_skbmem(skb); }-static inline void _kfree_skb_defer(struct sk_buff *skb)+static void napi_skb_cache_put(struct sk_buff *skb) { struct napi_alloc_cache *nc = this_cpu_ptr(&napi_alloc_cache);+ u32 i; /* drop skb->head and call any destructors for packet */ skb_release_all(skb);- /* record skb to CPU local list */+ kasan_poison_object_data(skbuff_head_cache, skb); nc->skb_cache[nc->skb_count++] = skb;-#ifdef CONFIG_SLUB- /* SLUB writes into objects when freeing */- prefetchw(skb);-#endif
It looks like this chunk has been lost. Is that intentional?
Yep. This prefetchw() assumed that skbuff_heads will be wiped
immediately or at the end of network softirq. Reusing this cache
means that heads can be reused later or may be kept in a cache for
some time, so prefetching makes no sense anymore.
I agree with this statement, the prefetchw() is no-longer needed.
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
LinkedIn: http://www.linkedin.com/in/brouer
we're in such context. This includes: build_skb() (called only
from NIC drivers in NAPI Rx context) and {,__}napi_alloc_skb()
(called from the same place or from kernel network softirq
functions).
build_skb is called from sleepable context in drivers/net/tun.c .
Perhaps there are other cases.
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:36:23
This function isn't much needed as NAPI skb queue gets bulk-freed
anyway when there's no more room, and even may reduce the efficiency
of bulk operations.
It will be even less needed after reusing skb cache on allocation path,
so remove it and this way lighten network softirqs a bit.
Suggested-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Alexander Lobakin <redacted>
---
include/linux/skbuff.h | 1 -
net/core/dev.c | 6 +-----
net/core/skbuff.c | 12 ------------
3 files changed, 1 insertion(+), 18 deletions(-)
From: Alexander Lobakin <hidden> Date: 2021-02-10 00:38:51
Eversince the introduction of __kmalloc_reserve(), "ip" argument
hasn't been used. _RET_IP_ is embedded inside
kmalloc_node_track_caller().
Remove the redundant macro and rename the function after it.
Signed-off-by: Alexander Lobakin <redacted>
---
net/core/skbuff.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)