From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:46:16
I've been working on this for a bit now figured its time for a v2 RFC. As
usual any comments, suggestions, observations, musings, etc are appreciated.
Latest round of lockless qdisc patch set with performance metric primarily
using pktgen to inject pkts into the qdisc layer. Some simple netperf tests
below as well but those need to be done correctly.
This v2 RFC version fixes a couple flaws in the original series. The first
major one was that the per_cpu accounting of qlen is not correct with respect
to the qdisc bypass. Using per cpu counters for qlen allows a flow to be
enqueuing on the packets into the qdisc and then get scheduled on another
core and bypass the qdisc completely if that core is not in use. I've reworked
the logic to use an atomic which is _correct_ now but unfortunately costs
a lot in performance. With a single pfifo_fast and 12 threads of pktgen
I still see a ~200k pps improvement even with atomic accounting so it is
still a win but nothing like the +1Mpps without the atomic accounting.
On the mq tests it atomic vs per cpu seems to be in the noise I believe
because mq qdisc is already aligned with a pfifo_fast qdisc per core
with the XPS setup I'm running mapping 1:1.
Any thoughts around this would be interesting to hear. My general thinking
around this is to submit the atomic version for inclusion and then start
to improve it with a few items listed below.
Additionally I've added a __netif_schedule() to the bad_skb_tx path
otherwise I observed a pkt getting stuck on the bad_txq_cpu path on
the pointer and sitting in the qdisc structure until it was kicked again
from another pkt or netif_schedule. And on the netif_schedule() topic
to support per cpu handling of gso and bad_txq_cpu we have to allow
the netif_schedule() logic to fire on a per cpu model as well.
Otherwise a bunch of small stylistic changes were made and I still need
to do another pass to catch checkpatch warnings/errors and try to do a bit
more cleanup around the statistics if/else branching. This series also
has both the atomic qlen code and the per cpu qlen code as I continue
to think up some scheme around the atomic qlen issue.
But this series seems to be working.
Future work is the following,
- convert all qdiscs over to per cpu handling and cleanup the
rather ugly if/else statistics handling. Although a bit of
work its mechanical and should help some.
- I'm looking at fq_codel to see how to make it "lockless".
- It seems we can drop the TX_HARD_LOCK on cases where the
nic exposes a queue per core now that we have enqueue/dequeue
decoupled. The idea being a bunch of threads enqueue and per
core dequeue logic runs. Requires XPS to be setup.
- qlen improvements somehow
- look at improvements to the skb_array structure. We can look
at drop in replacements and/or improving it.
Below is the data I took from pktgen,
./samples/pktgen/pktgen_bench_xmit_mode_queue_xmit.sh -t $NUM -i eth3
I did a run of 4 each time and took the total summation of each
thread. There are four different runs for the mq and pfifo_fast
cases. "without qlen atomic" uses per queue qlen values and allows
bypassing the qdisc via bypass flag, this is incorrect but shows
the impact of having an atomic in the mix. "with qlen atomic" shows
the correct implementation with atomics and bypass enabled. And
finally "without qlen atomic and no bypass" uses per cpu qlen
values and disables bypass to ensure ooo packets are not created.
To be clear the submitted patches here are the "with qlen atomic"
metrics.
nolock pfifo_fast (without qlen atomic)
1: 1440293 1421602 1409553 1393469 1424543
2: 1754890 1819292 1727948 1797711 1743427
4: 3282665 3344095 3315220 3332777 3348972
8: 2940079 1644450 2950777 2922085 2946310
12: 2042084 2610060 2857581 3493162 3104611
nolock pfifo_fast (with qlen atomic)
1: 1425231 1417176 1402862 1432880
2: 1631437 1633398 1630867 1628816
4: 1704383 1709900 1706274 1710198
8: 1348672 1344343 1339072 1334288
12: 1262988 1280724 1262237 1262615
nolock pfifo_fast (without qlen atomic and no bypass)
1: 1435796 1458522 1471855 1455658
2: 1880642 1876359 1872879 1884578
4: 1922935 1914589 1912832 1912116
8: 1585055 1576887 1577086 1570236
12: 1479273 1450706 1447056 1466330
lock (pfifo_fast)
1: 1471479 1469142 1458825 1456788 1453952
2: 1746231 1749490 1753176 1753780 1755959
4: 1119626 1120515 1121478 1119220 1121115
8: 1001471 999308 1000318 1000776 1000384
12: 989269 992122 991590 986581 990430
nolock (mq with per cpu qlen)
1: 1435952 1459523 1448860 1385451 1435031
2: 2850662 2855702 2859105 2855443 2843382
4: 5288135 5271192 5252242 5270192 5311642
8: 10042731 10018063 9891813 9968382 9956727
12: 13265277 13384199 13438955 13363771 13436198
nolock (mq with qlen atomic)
1: 1558253 1562285 1555037 1558422
2: 2917449 2952852 2921697 2892313
4: 5518243 5375300 5625724 5219599
8: 10183153 10169389 10163161 10202530
12: 13877976 13459987 13081520 13996757
nolock (mq with !bypass and per cpu qlen)
1: 1369110 1379992 1359407 1397014
2: 2575546 2557471 2580782 2593226
4: 4632570 4871850 4830725 4968439
8: 8974135 8951107 9134641 9084347
12: 12982673 12737426 12808364
lock (mq)
1: 1448374 1444208 1437459 1437088 1452453
2: 2687963 2679221 2651059 2691630 2667479
4: 5153884 4684153 5091728 4635261 4902381
8: 9292395 9625869 9681835 9711651 9660498
12: 13553918 13682410 14084055 13946138 13724726
######################################################
A few arbitrary netperf sessions... (TBD lots of sessions, etc).
nolock (mq with !bypass and per cpu qlen)
root@john-Precision-Tower-5810:~# netperf -H 22.1 -t TCP_RR -- -s 128K -S 128K -b 0
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 22.1 () port 0 AF_INET : demo : first burst 0
q Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
262144 262144 1 1 10.00 19910.37
262144 262144
nolock (pfifo_fast with !bypass and per cpu qlen)
root@john-Precision-Tower-5810:~# netperf -H 22.1 -t TCP_RR -- -s 128K -S 128K -b 0
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 22.1 () port 0 AF_INET : demo : first burst 0
fgLocal /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
262144 262144 1 1 10.00 20358.90
262144 262144
nolock (mq with qlen atomic)
root@john-Precision-Tower-5810:/home/john/git/kernel.org/master# netperf -H 22.1 -t TCP_RR -- -s 128K -S 128K -b 0
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 22.1 () port 0 AF_INET : demo : first burst 0
kLocal /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
262144 262144 1 1 10.00 20202.38
262144 262144
nolock (pfifo_fast with qlen_atomic)
root@john-Precision-Tower-5810:/home/john/git/kernel.org/master# netperf -H 22.1 -t TCP_RR -- -s 128K -S 128K -b 0
MIGRATED TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 22.1 () port 0 AF_INET : demo : first burst 0
Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
262144 262144 1 1 10.00 20059.41
262144 262144
lock (mq)
TBD
lock (pfifo_fast)
TBD
---
John Fastabend (13):
net: sched: allow qdiscs to handle locking
net: sched: qdisc_qlen for per cpu logic
net: sched: provide per cpu qstat helpers
net: sched: provide atomic qlen helpers for bypass case
net: sched: a dflt qdisc may be used with per cpu stats
net: sched: per cpu gso handlers
net: sched: support qdisc_reset on NOLOCK qdisc
net: sched: support skb_bad_tx with lockless qdisc
net: sched: helper to sum qlen
net: sched: lockless support for netif_schedule
net: sched: pfifo_fast use alf_queue
net: sched: add support for TCQ_F_NOLOCK subqueues to sch_mq
net: sched: add support for TCQ_F_NOLOCK subqueues to sch_mqprio
include/net/gen_stats.h | 3
include/net/pkt_sched.h | 4
include/net/sch_generic.h | 127 ++++++++++++++
net/core/dev.c | 60 +++++--
net/core/gen_stats.c | 9 +
net/sched/sch_api.c | 21 ++
net/sched/sch_generic.c | 404 +++++++++++++++++++++++++++++++++++----------
net/sched/sch_mq.c | 25 ++-
net/sched/sch_mqprio.c | 61 ++++---
9 files changed, 577 insertions(+), 137 deletions(-)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:34:45
This is a bit interesting because it means sch_direct_xmit will
return a positive value which causes the dequeue/xmit cycle to
continue only when a specific cpu has a qlen > 0.
However checking each cpu for qlen will break performance so
its important to note that qdiscs that set the no lock bit need
to have some sort of per cpu enqueue/dequeue data structure that
maps to the per cpu qlen value.
Signed-off-by: John Fastabend <redacted>
---
include/net/sch_generic.h | 8 ++++++++
1 file changed, 8 insertions(+)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:34:45
This patch adds a flag for queueing disciplines to indicate the stack
does not need to use the qdisc lock to protect operations. This can
be used to build lockless scheduling algorithms and improving
performance.
The flag is checked in the tx path and the qdisc lock is only taken
if it is not set. For now use a conditional if statement. Later we
could be more aggressive if it proves worthwhile and use a static key
or wrap this in a likely().
Signed-off-by: John Fastabend <redacted>
---
include/net/pkt_sched.h | 4 +++-
include/net/sch_generic.h | 1 +
net/core/dev.c | 32 ++++++++++++++++++++++++++++----
net/sched/sch_generic.c | 26 ++++++++++++++++----------
4 files changed, 48 insertions(+), 15 deletions(-)
@@ -3897,19 +3918,22 @@ static void net_tx_action(struct softirq_action *h)while(head){structQdisc*q=head;-spinlock_t*root_lock;+spinlock_t*root_lock=NULL;head=head->next_sched;-root_lock=qdisc_lock(q);-spin_lock(root_lock);+if(!(q->flags&TCQ_F_NOLOCK)){+root_lock=qdisc_lock(q);+spin_lock(root_lock);+}/* We need to make sure head->next_sched is read*beforeclearing__QDISC_STATE_SCHED*/smp_mb__before_atomic();clear_bit(__QDISC_STATE_SCHED,&q->state);qdisc_run(q);-spin_unlock(root_lock);+if(!(q->flags&TCQ_F_NOLOCK))+spin_unlock(root_lock);}}}
@@ -170,7 +170,8 @@ int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,intret=NETDEV_TX_BUSY;/* And release qdisc */-spin_unlock(root_lock);+if(!(q->flags&TCQ_F_NOLOCK))+spin_unlock(root_lock);/* Note that we validate skb (GSO, checksum, ...) outside of locks */if(validate)
@@ -183,10 +184,13 @@ int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,HARD_TX_UNLOCK(dev,txq);}else{-spin_lock(root_lock);+if(!(q->flags&TCQ_F_NOLOCK))+spin_lock(root_lock);returnqdisc_qlen(q);}-spin_lock(root_lock);++if(!(q->flags&TCQ_F_NOLOCK))+spin_lock(root_lock);if(dev_xmit_complete(ret)){/* Driver sent out skb successfully or skb was consumed */
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:34:58
The per cpu qstats support was added with per cpu bstat support which
is currently used by the ingress qdisc. This patch adds a set of
helpers needed to make other qdiscs that use qstats per cpu as well.
Signed-off-by: John Fastabend <redacted>
---
include/net/sch_generic.h | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:35:45
The qlen is used by the core/dev.c to determine if a packet
can skip the qdisc on qdiscs with bypass enabled. In these
cases a per cpu qlen value can cause one cpu to bypass a
qdisc that has packets in it.
To avoid this case use the simplest solution I could come
up with for now and add an atomic qlen value to the qdisc
to use in these cases.
Signed-off-by: John Fastabend <redacted>
---
include/net/sch_generic.h | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
@@ -254,8 +260,11 @@ static inline int qdisc_qlen_cpu(const struct Qdisc *q)staticinlineintqdisc_qlen(conststructQdisc*q){+/* current default is to use atomic ops for qdisc qlen when+*runningwithTCQ_F_NOLOCK.+*/if(q->flags&TCQ_F_NOLOCK)-returnqdisc_qlen_cpu(q);+returnqdisc_qlen_atomic(q);returnq->q.qlen;}
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:35:45
Enable dflt qdisc support for per cpu stats before this patch a
dflt qdisc was required to use the global statistics qstats and
bstats.
Signed-off-by: John Fastabend <redacted>
---
net/sched/sch_generic.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
@@ -645,18 +645,34 @@ struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue,structQdisc*sch;if(!try_module_get(ops->owner))-gotoerrout;+returnNULL;sch=qdisc_alloc(dev_queue,ops);if(IS_ERR(sch))-gotoerrout;+returnNULL;sch->parent=parentid;-if(!ops->init||ops->init(sch,NULL)==0)+if(!ops->init)returnsch;-qdisc_destroy(sch);+if(ops->init(sch,NULL))+gotoerrout;++/* init() may have set percpu flags so init data structures */+if(qdisc_is_percpu_stats(sch)){+sch->cpu_bstats=+netdev_alloc_pcpu_stats(structgnet_stats_basic_cpu);+if(!sch->cpu_bstats)+gotoerrout;++sch->cpu_qstats=alloc_percpu(structgnet_stats_queue);+if(!sch->cpu_qstats)+gotoerrout;+}++returnsch;errout:+qdisc_destroy(sch);returnNULL;}EXPORT_SYMBOL(qdisc_create_dflt);
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:36:16
The net sched infrastructure has a gso ptr that points to skb structs
that have failed to be enqueued by the device driver.
This can happen when multiple cores try to push a skb onto the same
underlying hardware queue resulting in lock contention. This case is
handled by a cpu collision handler handle_dev_cpu_collision(). Another
case occurs when the stack overruns the drivers low level tx queues
capacity. Ideally these should be a rare occurrence in a well-tuned
system but they do happen.
To handle this in the lockless case use a per cpu gso field to park
the skb until the conflict can be resolved. Note at this point the
skb has already been popped off the qdisc so it has to be handled
by the infrastructure.
Signed-off-by: John Fastabend <redacted>
---
include/net/sch_generic.h | 39 +++++++++++++++++++++++++
net/sched/sch_api.c | 7 ++++
net/sched/sch_generic.c | 71 ++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 112 insertions(+), 5 deletions(-)
@@ -744,6 +750,23 @@ static inline struct sk_buff *qdisc_peek_dequeued(struct Qdisc *sch)returnsch->gso_skb;}+staticinlinestructsk_buff*qdisc_peek_dequeued_cpu(structQdisc*sch)+{+structgso_cell*gso=this_cpu_ptr(sch->gso_cpu_skb);++if(!gso->skb){+structsk_buff*skb=sch->dequeue(sch);++if(skb){+gso->skb=skb;+qdisc_qstats_cpu_backlog_inc(sch,skb);+qdisc_qstats_cpu_qlen_inc(sch);+}+}++returngso->skb;+}+/* use instead of qdisc->dequeue() for all qdiscs queried with ->peek() */staticinlinestructsk_buff*qdisc_dequeue_peeked(structQdisc*sch){
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:36:41
The qdisc_reset operation depends on the qdisc lock at the moment
to halt any additions to gso_skb and statistics while the list is
free'd and the stats zeroed.
Without the qdisc lock we can not guarantee another cpu is not in
the process of adding a skb to one of the "cells". Here are the
two cases we have to handle.
case 1: qdisc_graft operation. In this case a "new" qdisc is attached
and the 'qdisc_destroy' operation is called on the old qdisc.
The destroy operation will wait a rcu grace period and call
qdisc_rcu_free(). At which point gso_cpu_skb is free'd along
with all stats so no need to zero stats and gso_cpu_skb from
the reset operation itself.
Because we can not continue to call qdisc_reset before waiting
an rcu grace period so that the qdisc is detached from all
cpus simply do not call qdisc_reset() at all and let the
qdisc_destroy operation clean up the qdisc. Note, a refcnt
greater than 1 would cause the destroy operation to be
aborted however if this ever happened the reference to the
qdisc would be lost and we would have a memory leak.
case 2: dev_deactivate sequence. This can come from a user bringing
the interface down which causes the gso_skb list to be flushed
and the qlen zero'd. At the moment this is protected by the
qdisc lock so while we clear the qlen/gso_skb fields we are
guaranteed no new skbs are added. For the lockless case
though this is not true. To resolve this move the qdisc_reset
call after the new qdisc is assigned and a grace period is
exercised to ensure no new skbs can be enqueued. Further
the RTNL lock is held so we can not get another call to
activate the qdisc while the skb lists are being free'd.
Finally, fix qdisc_reset to handle the per cpu stats and
skb lists.
Signed-off-by: John Fastabend <redacted>
---
net/sched/sch_generic.c | 45 +++++++++++++++++++++++++++++++++++----------
1 file changed, 35 insertions(+), 10 deletions(-)
@@ -812,10 +826,6 @@ struct Qdisc *dev_graft_qdisc(struct netdev_queue *dev_queue,root_lock=qdisc_lock(oqdisc);spin_lock_bh(root_lock);-/* Prune old scheduler */-if(oqdisc&&atomic_read(&oqdisc->refcnt)<=1)-qdisc_reset(oqdisc);-/* ... and graft new one */if(qdisc==NULL)qdisc=&noop_qdisc;
@@ -986,20 +1004,27 @@ void dev_deactivate_many(struct list_head *head)&noop_qdisc);dev_watchdog_down(dev);-sync_needed|=!dev->dismantle;}/* Wait for outstanding qdisc-less dev_queue_xmit calls.*Thisisavoidedifalldevicesareindismantlephase:*Callerwillcallsynchronize_net()forus*/-if(sync_needed)-synchronize_net();+synchronize_net();/* Wait for outstanding qdisc_run calls. */-list_for_each_entry(dev,head,close_list)+list_for_each_entry(dev,head,close_list){while(some_qdisc_is_busy(dev))yield();++/* The new qdisc is assigned at this point so we can safely+*unwindstaleskblistsandqdiscstatistics+*/+netdev_for_each_tx_queue(dev,dev_qdisc_reset,NULL);+if(dev_ingress_queue(dev))+dev_qdisc_reset(dev,dev_ingress_queue(dev),NULL);+}+}voiddev_deactivate(structnet_device*dev)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:37:08
Similar to how gso is handled skb_bad_tx needs to be per cpu to handle
lockless qdisc with multiple writer/producers.
Signed-off-by: John Fastabend <redacted>
---
include/net/sch_generic.h | 7 +++
net/sched/sch_api.c | 6 +++
net/sched/sch_generic.c | 95 +++++++++++++++++++++++++++++++++++++++++----
3 files changed, 99 insertions(+), 9 deletions(-)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:37:32
Reporting qlen when qlen is per cpu requires aggregating the per
cpu counters. This adds a helper routine for this.
Signed-off-by: John Fastabend <redacted>
---
include/net/sch_generic.h | 15 +++++++++++++++
net/sched/sch_api.c | 3 ++-
2 files changed, 17 insertions(+), 1 deletion(-)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:38:05
netif_schedule uses a bit QDISC_STATE_SCHED to tell the qdisc layer
if a run of the qdisc has been scheduler. This is important when
tearing down qdisc instances. We can rcu_free an instance for example
if its possible that we might have outstanding references to it.
Perhaps more importantly in the per cpu lockless case we need to
schedule a run of the qdisc on all qdiscs that are enqueu'ing packets
and hitting the gso_skb requeue logic or else the skb may get stuck
on the gso_skb queue without anything to finish the xmit.
This patch uses a reference counter instead of a bit to account for
the multiple CPUs.
---
include/net/sch_generic.h | 1 +
net/core/dev.c | 32 +++++++++++++++++++++++---------
net/sched/sch_api.c | 5 +++++
net/sched/sch_generic.c | 16 +++++++++++++++-
4 files changed, 44 insertions(+), 10 deletions(-)
@@ -3925,15 +3931,23 @@ static void net_tx_action(struct softirq_action *h)if(!(q->flags&TCQ_F_NOLOCK)){root_lock=qdisc_lock(q);spin_lock(root_lock);-}-/* We need to make sure head->next_sched is read-*beforeclearing__QDISC_STATE_SCHED-*/-smp_mb__before_atomic();-clear_bit(__QDISC_STATE_SCHED,&q->state);-qdisc_run(q);-if(!(q->flags&TCQ_F_NOLOCK))++/* We need to make sure head->next_sched is read+*beforeclearing__QDISC_STATE_SCHED+*/+smp_mb__before_atomic();+clear_bit(__QDISC_STATE_SCHED,&q->state);++qdisc_run(q);+spin_unlock(root_lock);+}else{+unsignedlong*s=this_cpu_ptr(q->cpu_state);++smp_mb__before_atomic();+clear_bit(__QDISC_STATE_SCHED,s);+__qdisc_run(q);+}}}}
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:38:33
This converts the pfifo_fast qdisc to use the alf_queue enqueue and
dequeue routines then sets the NOLOCK bit.
This also removes the logic used to pick the next band to dequeue from
and instead just checks each alf_queue for packets from top priority
to lowest. This might need to be a bit more clever but seems to work
for now.
Signed-off-by: John Fastabend <redacted>
---
net/sched/sch_generic.c | 133 +++++++++++++++++++++++++++--------------------
1 file changed, 77 insertions(+), 56 deletions(-)
@@ -654,24 +646,53 @@ nla_put_failure:staticintpfifo_fast_init(structQdisc*qdisc,structnlattr*opt){-intprio;+unsignedintqlen=qdisc_dev(qdisc)->tx_queue_len;structpfifo_fast_priv*priv=qdisc_priv(qdisc);+intprio;++/* guard against zero length rings */+if(!qlen)+return-EINVAL;-for(prio=0;prio<PFIFO_FAST_BANDS;prio++)-__skb_queue_head_init(band2list(priv,prio));+for(prio=0;prio<PFIFO_FAST_BANDS;prio++){+structskb_array*q=band2list(priv,prio);+interr;++err=skb_array_init(q,qlen,GFP_KERNEL);+if(err)+return-ENOMEM;+}++atomic_set(&qdisc->qlen_atomic,0);/* Can by-pass the queue discipline */qdisc->flags|=TCQ_F_CAN_BYPASS;+qdisc->flags|=TCQ_F_NOLOCK;+qdisc->flags|=TCQ_F_CPUSTATS;+return0;}+staticvoidpfifo_fast_destroy(structQdisc*sch)+{+structpfifo_fast_priv*priv=qdisc_priv(sch);+intprio;++for(prio=0;prio<PFIFO_FAST_BANDS;prio++){+structskb_array*q=band2list(priv,prio);++skb_array_cleanup(q);+}+}+structQdisc_opspfifo_fast_ops__read_mostly={.id="pfifo_fast",.priv_size=sizeof(structpfifo_fast_priv),.enqueue=pfifo_fast_enqueue,.dequeue=pfifo_fast_dequeue,-.peek=pfifo_fast_peek,+.peek=qdisc_peek_dequeued_cpu,.init=pfifo_fast_init,+.destroy=pfifo_fast_destroy,.reset=pfifo_fast_reset,.dump=pfifo_fast_dump,.owner=THIS_MODULE,
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:38:57
The sch_mq qdisc creates a sub-qdisc per tx queue which are then
called independently for enqueue and dequeue operations. However
statistics are aggregated and pushed up to the "master" qdisc.
This patch adds support for any of the sub-qdiscs to be per cpu
statistic qdiscs. To handle this case add a check when calculating
stats and aggregate the per cpu stats if needed.
Also exports __gnet_stats_copy_queue() to use as a helper function.
Signed-off-by: John Fastabend <redacted>
---
include/net/gen_stats.h | 3 +++
net/core/gen_stats.c | 9 +++++----
net/sched/sch_mq.c | 25 ++++++++++++++++++-------
3 files changed, 26 insertions(+), 11 deletions(-)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:39:37
The sch_mqprio qdisc creates a sub-qdisc per tx queue which are then
called independently for enqueue and dequeue operations. However
statistics are aggregated and pushed up to the "master" qdisc.
This patch adds support for any of the sub-qdiscs to be per cpu
statistic qdiscs. To handle this case add a check when calculating
stats and aggregate the per cpu stats if needed.
Signed-off-by: John Fastabend <redacted>
---
net/sched/sch_mqprio.c | 61 +++++++++++++++++++++++++++++++-----------------
1 file changed, 39 insertions(+), 22 deletions(-)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:46:45
On 16-08-17 12:37 PM, John Fastabend wrote:
netif_schedule uses a bit QDISC_STATE_SCHED to tell the qdisc layer
if a run of the qdisc has been scheduler. This is important when
tearing down qdisc instances. We can rcu_free an instance for example
if its possible that we might have outstanding references to it.
Perhaps more importantly in the per cpu lockless case we need to
schedule a run of the qdisc on all qdiscs that are enqueu'ing packets
and hitting the gso_skb requeue logic or else the skb may get stuck
on the gso_skb queue without anything to finish the xmit.
This patch uses a reference counter instead of a bit to account for
the multiple CPUs.
---
oops the commit message is incorrect here it actually uses a per cpu
state bitmask to track this.
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 19:51:16
On 16-08-17 12:38 PM, John Fastabend wrote:
The sch_mq qdisc creates a sub-qdisc per tx queue which are then
called independently for enqueue and dequeue operations. However
statistics are aggregated and pushed up to the "master" qdisc.
This patch adds support for any of the sub-qdiscs to be per cpu
statistic qdiscs. To handle this case add a check when calculating
stats and aggregate the per cpu stats if needed.
Also exports __gnet_stats_copy_queue() to use as a helper function.
Signed-off-by: John Fastabend <redacted>
---
From: Eric Dumazet <hidden> Date: 2016-08-17 22:33:03
On Wed, 2016-08-17 at 12:33 -0700, John Fastabend wrote:
quoted hunk
This patch adds a flag for queueing disciplines to indicate the stack
does not need to use the qdisc lock to protect operations. This can
be used to build lockless scheduling algorithms and improving
performance.
The flag is checked in the tx path and the qdisc lock is only taken
if it is not set. For now use a conditional if statement. Later we
could be more aggressive if it proves worthwhile and use a static key
or wrap this in a likely().
Signed-off-by: John Fastabend <redacted>
---
include/net/pkt_sched.h | 4 +++-
include/net/sch_generic.h | 1 +
net/core/dev.c | 32 ++++++++++++++++++++++++++++----
net/sched/sch_generic.c | 26 ++++++++++++++++----------
4 files changed, 48 insertions(+), 15 deletions(-)
For a lockless qdisc, do you believe TCQ_F_CAN_BYPASS is still a gain ?
For the benchmarks from pktgen it appears to be a win or mute to just
drop the TCQ_F_CAN_BYPASS (just taking a look at one sample below)
nolock & nobypass locked (current master)
----------------------------------------------
1: 1435796 1471479
2: 1880642 1746231
4: 1922935 1119626
8: 1585055 1001471
12: 1479273 989269
The only thing would be to test a bunch of netperf RR sessions to be
sure.
Also !qdisc_qlen(q) looks racy anyway ?
Yep its racy unless you make it an atomic but this hurts performance
metrics. There is a patch further in the stack here that adds the
atomic variants but I tend to think we can just drop the bypass logic
in the lockless case assuming the netperf tests look good.
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 22:49:33
On 16-08-17 03:33 PM, Eric Dumazet wrote:
On Wed, 2016-08-17 at 12:33 -0700, John Fastabend wrote:
quoted
This patch adds a flag for queueing disciplines to indicate the stack
does not need to use the qdisc lock to protect operations. This can
be used to build lockless scheduling algorithms and improving
performance.
The flag is checked in the tx path and the qdisc lock is only taken
if it is not set. For now use a conditional if statement. Later we
could be more aggressive if it proves worthwhile and use a static key
or wrap this in a likely().
Signed-off-by: John Fastabend <redacted>
---
include/net/pkt_sched.h | 4 +++-
include/net/sch_generic.h | 1 +
net/core/dev.c | 32 ++++++++++++++++++++++++++++----
net/sched/sch_generic.c | 26 ++++++++++++++++----------
4 files changed, 48 insertions(+), 15 deletions(-)
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 22:59:49
On 16-08-17 03:53 PM, Eric Dumazet wrote:
On Wed, 2016-08-17 at 12:36 -0700, John Fastabend wrote:
quoted
The qdisc_reset operation depends on the qdisc lock at the moment
to halt any additions to gso_skb and statistics while the list is
free'd and the stats zeroed.
From: Eric Dumazet <hidden> Date: 2016-08-17 23:04:18
On Wed, 2016-08-17 at 12:38 -0700, John Fastabend wrote:
The sch_mq qdisc creates a sub-qdisc per tx queue which are then
called independently for enqueue and dequeue operations. However
statistics are aggregated and pushed up to the "master" qdisc.
This patch adds support for any of the sub-qdiscs to be per cpu
statistic qdiscs. To handle this case add a check when calculating
stats and aggregate the per cpu stats if needed.
Also exports __gnet_stats_copy_queue() to use as a helper function.
Looks like this patch should be happening earlier in the series ?
From: Eric Dumazet <hidden> Date: 2016-08-17 23:08:10
On Wed, 2016-08-17 at 12:36 -0700, John Fastabend wrote:
The qdisc_reset operation depends on the qdisc lock at the moment
to halt any additions to gso_skb and statistics while the list is
free'd and the stats zeroed.
ah dang thats leftover from trying to resolve a skb getting stuck on the
bad_txq_cell from qdisc_enqueue_skb_bad_txq(). You'll notice I added
a __netif_schedule(skb) call in qdisc_enqueue_skb_bad_txq() which
resolves this and the set_thread_flag() here can then just be removed.
.John
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-17 23:18:50
On 16-08-17 04:04 PM, Eric Dumazet wrote:
On Wed, 2016-08-17 at 12:38 -0700, John Fastabend wrote:
quoted
The sch_mq qdisc creates a sub-qdisc per tx queue which are then
called independently for enqueue and dequeue operations. However
statistics are aggregated and pushed up to the "master" qdisc.
This patch adds support for any of the sub-qdiscs to be per cpu
statistic qdiscs. To handle this case add a check when calculating
stats and aggregate the per cpu stats if needed.
Also exports __gnet_stats_copy_queue() to use as a helper function.
Looks like this patch should be happening earlier in the series ?
hmm yep patches 12 and 13 should come before 11 to avoid introducing
a bug and subsequently fixing them.
ah dang thats leftover from trying to resolve a skb getting stuck on the
bad_txq_cell from qdisc_enqueue_skb_bad_txq(). You'll notice I added
a __netif_schedule(skb) call in qdisc_enqueue_skb_bad_txq() which
resolves this and the set_thread_flag() here can then just be removed.
On Wed, 17 Aug 2016 12:38:10 -0700
John Fastabend [off-list ref] wrote:
This converts the pfifo_fast qdisc to use the alf_queue enqueue and
^^^^^^^^^
dequeue routines then sets the NOLOCK bit.
This also removes the logic used to pick the next band to dequeue from
and instead just checks each alf_queue for packets from top priority
^^^^^^^^^
to lowest. This might need to be a bit more clever but seems to work
for now.
You need to fix the description, as you are no longer using my
alf_queue implementation but instead are using the skb_array/ptr_ring
queue (by MST).
--
Best regards,
Jesper Dangaard Brouer
MSc.CS, Principal Kernel Engineer at Red Hat
Author of http://www.iptv-analyzer.org
LinkedIn: http://www.linkedin.com/in/brouer
From: John Fastabend <john.fastabend@gmail.com> Date: 2016-08-19 15:45:04
On 16-08-19 03:13 AM, Jesper Dangaard Brouer wrote:
On Wed, 17 Aug 2016 12:38:10 -0700
John Fastabend [off-list ref] wrote:
quoted
This converts the pfifo_fast qdisc to use the alf_queue enqueue and
^^^^^^^^^
quoted
dequeue routines then sets the NOLOCK bit.
This also removes the logic used to pick the next band to dequeue from
and instead just checks each alf_queue for packets from top priority
^^^^^^^^^
quoted
to lowest. This might need to be a bit more clever but seems to work
for now.
You need to fix the description, as you are no longer using my
alf_queue implementation but instead are using the skb_array/ptr_ring
queue (by MST).
Yep I forgot to change this even though iirc you may have had the same
comment in the last rev. Thanks! I'll get it fixed up this time.
.John
This might be the time to group all these per cpu allocations to a
single one, to help data locality and decrease overhead of having XX
pointers.
Sounds like a good idea to me. I'll go ahead and add a patch to the
front to consolidate the stats and then add these there.
Actually this turned out to be not so trivial. To do it reasonably
requires changes in how gnet stats work and such. I'm going to propose
pushing this into a series after the initial lockless set otherwise the
patch set is going to start pushing 20+ patches.
Also a follow on series to make all the qdiscs support per cpu stats
would be nice and allows us to remove a lot of the annoying if/else
cases around stats. Its a bit tedious to go and change all the qdiscs
but mostly mechanical.
.John