DPDK QoS 框架 - 3. 分级调度模块的实现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DPDK QoS 框架 - 3. 分级调度模块的实现相关的知识,希望对你有一定的参考价值。
参考技术A下图展示了内部数据结构的细节。
在不同的CPU核上处理同一个发送端口的入队/出队操作可能会对调度器的性能造成重大影响,因此不建议这样做。
端口入队/出队操作需要共享对以下数据结构的访问:
以下操作会造成预期的性能下降:
因此,调度程序的入队和出队操作必须在同一个线程中运行,从而允许队列和Bitmap操作可以是非线程安全的,并保持调度程序的数据结构在同一个CPU核内部。 尽量将数据结构局部化,避免跨线程共享。
增加NIC端口的数量仅仅需要按比例增加用于流量调度的CPU核的数量。
包处理步骤如下:
需要注意的是,这些步骤之间有很强的依赖关系,在步骤1和步骤2的结果可用之前,步骤2和步骤3不能启动,因此没法利用处理器乱序执行引擎提供的任何性能优化。
当有大量的数据包和队列需要处理时,很有可能处理当前包所需要的数据结构不在L1和L2缓存中,因此上述3个步骤对内存的访问结果会造成L1/L2 cache miss。考虑到性能因素,每个数据包都造成3次L1/L2 cache miss是不可接受的。
解决方案是提前预取需要的数据结构。预取操作有执行延迟,在此期间处理器不应该尝试访问当前正在预取的数据结构,因此处理器应该执行其他工作。唯一可做的其他工作是对其他输入包执行入队操作流程的不同步骤,从而以流水线的方式处理入队操作。
图2演示了入队操作的流水线实现,其中有4个流水线阶段,每个阶段处理2个不同的输入包。在给定的时间内,任何输入包都不能成为多个流水线阶段的一部分。
上面描述了非常基本的入队流水线拥塞管理方案:将数据包塞入队列,当队列被填满时,所有到达同一队列的数据包将被丢弃,直到有数据包通过出队操作被消费掉,才可以在队列中塞入新的数据包。如果用RED/WRED改进入队流水线,就可以通过查看队列占用率和包的优先级,为特定的包做出入队/丢弃决策(而不是不加选择地将所有包入队或者丢弃)。
在当前管道调度处理下一个包的步骤为:
以上操作涉及到的数据结构(pipe, queue, queue array, mbufs)可以在被访问之前预取进缓存,从而避免cache miss。在对当前管道(管道A)的数据发出预取操作后,可以立即切换到另一个管道(管道B)进行处理,等到管道A的预取操作完成,再切换回去,从而利用预取本身的时延同步处理多个管道的数据。
出队状态机利用处理器缓存机制,尽可能同时处理多个活跃管道,从而发送更多的数据包。
输出端口就是一个被调度器不断的用待传输数据填充的队列。对于10 GbE,每秒有12.5亿个字节需要被端口调度器填充。如果调度器的速度不够快,不能填满队列(假设有足够的待传输数据包和credit),那么必然有一些带宽会被闲置,从而产生浪费。
原则上,分层调度器出队操作应该由网卡发送器触发。通常情况下,一旦网卡发送队列的输入流量低于一个预定义的阈值,端口调度程序将被唤醒(基于中断或轮询的方式,通过持续监控队列占用决定是否被唤醒),填充更多的数据包进入队列。
调度器需要跟踪随着时间推移而变化的credit信息,这需要基于时间进行credit的更新(例如,子端口和管道流量整形、流量组上限的强制执行,等等)。
每次调度器决定向网卡发送器传递数据包时,调度器将相应地调整其内部时间信息。因为在物理接口上每发送一个字节所需的时间是固定的,所以可以以字节为单位作为内部时间的参考值,这样处理起来也会比较方便。当一个包准备传输时,时间以(n + h)的形式递增,其中n是以字节为单位的包长度,h是每个包的帧数据字节数(包括帧头、CRC等)。
调度器需要将其内部时间参考值与端口速率对齐,从而确保调度器不会填充超过网卡发送速率的数据,从而防止由于网卡发生队列已满而发生的丢包。
调度器需要读取每次出队调用的当前时间。CPU时间戳可以通过读取TSC寄存器(Time Stamp Counter)或HPET寄存器(High Precision Event Timer)来获得。当前CPU时间戳需要通过以下公式将CPU时钟转换为字节数:time_bytes = time_cycles / cycles_per_byte, cycles_per_byte是传输一个字节所消耗的CPU周期(例如,10GbE的端口以2GHz的CPU频率传输数据,cycles_per_byte = 2G/(10G/8) = 1.6)。
调度器会维护针对网卡时间的内部时间引用,网卡时间将随着被调度的数据包的长度(包含帧开销)的增加而增加。在每次出队调用时,调度程序会根据当前时间检查其对网卡时间的内部引用:
调度器往返延迟(SRTD,Scheduler Round Trip Delay)是调度器连续两次检查同一管道之间的时间(以CPU周期为单位)。
为了跟上发送端口的速率(即避免网卡带宽的浪费),调度器应该能够比网卡发送器实际传输速度更快地调度数据包。
假设没有发生端口带宽超卖,调度器需要根据配置的令牌桶,跟踪每个管道的速率。这意味着管道的令牌桶的大小应该设置得足够高,以防止它因较大的SRTD而溢出,从而导致管道的可用带宽损失。
当以下所有条件都满足时,调度器可以为(子端口S、管道P、流量组TC、队列Q)做出发送下一个包的合理调度决策:
如果以上条件都满足,则可以选择一个数据包进行传输,并从子端口S、子端口S的流量组TC、管道P、管道P的流量组TC中减去必要的credit。
由于所有数据包长度的最大公约数是一个字节,因此以字节作为credit的计量单位。传输n字节的数据包所需的credit数等于(n+h),其中h等于每个数据包的帧开销(以字节为单位)。
子端口和管道的流量整形是通过令牌桶来实现的,每个令牌桶使用一个饱和计数器(saturated counter)来实现,通过这个计数器跟踪credit的数值。
令牌桶泛型参数和操作如下表所示:
为了实现上面描述的令牌桶通用操作,当前设计使用下表中给出的持久数据结构:
可以用以下公式计算桶速率(单位为字节/秒):
式中,r = 端口线速(单位为字节/秒)。
令牌桶操作的实现如下表所示:
同一管道内TC的优先级是严格定义的,其调度是由管道出队状态机实现的,该状态机按照优先级升序排列选择队列。因此,队列0(与TC 0相关,优先级最高的TC)在队列1(优先级低于TC 0的TC 1)之前被处理,队列1在队列2(优先级低于TC 1的TC 2)之前被处理,并继续下去,直到除最低优先级TC以外的所有TC队列都被处理。最后,队列12-15(尽力而为TC, 最低优先级TC)被处理。
管道和子端口级别的TC不支持流量整形(traffic shaping),所以在这两个级别的上下文里没有维护令牌桶。管道和子端口级别的TC上限被强制限定为周期性回填的credit,每次在管道/子端口级别处理包的时候,credit就会被消费掉,如下两表所示。
子端口/管道TC强制上限的持久化数据结构:
最低优先级TC(尽力而为TC,best effort TC)的WRR设计方案从简单到复杂的演变如下表所示。
子端口流量组X的超售是一个配置问题,当子端口的成员管道为流量组X分配的带宽大于在上层子端口级别为同一流量组分配的带宽时发生。
特定子端口和流量组的超售完全是管道和子端口级配置的结果,而不是由于运行时流量负载的动态演化(如拥塞)造成的。
如果当前子端口对流量组X的总体需求较低,超售的存在并不代表问题,因为所有成员管道对流量组X的需求已经完全可以满足。然而,当所有子端口的成员管道的流量组X的需求合在一起超过了在子端口级别配置的限制时,这将不再能够实现。
下表总结了处理这个问题的一些可能的方案,当前实现是基于第三种方案。
通常,子端口TC超售只对最低优先级的流量组启用,这通常用于尽力而为(best effort)的流量,管理平面需要防止其他(更高优先级)的TC发生这种情况。
为了便于实现,还假设子端口的最低优先级TC的上限设置为子端口速率的100%,并且对于所有子端口的成员管道,管道最低优先级TC的上限设置为管道速率的100%。
该算法会先计算一个容量(watermark),容量会根据子端口的成员管道当前的需求定期更新,其目的是限制每个管道允许发送的最低优先级(best effort)TC的流量。在每个TC上限计算周期开始时,在子端口级别上计算该容量,并在当前实施周期内在所有子端口的成员管道上使用相同的容量值。下面说明在每个周期开始时作为子端口级别计算的容量如何传播到所有子端口的成员管道的。
当前计算周期开始(和上一个计算周期结束的时间相同)时,容量值需要基于上一周期开始时分配给尽力而为TC但没有被子端口的成员管道消费掉的带宽值做出调整。
如果子端口有尽力而为TC带宽没用掉,就增加当前时段的容量值,以鼓励子端口的成员管道消耗更多带宽。否则,降低容量值,以强制子端口成员管道的尽力而为TC带宽的消耗相等。
容量值的增加或减少会以很小的增量进行,因此可能需要几个执行周期才能达到平衡状态。由于子端口成员管道对尽力而为TC的需求的变化,这种状态可以在任何时候发生改变,例如,由于需求增加(当需要降低容量值时)或需求减少(当可以增加容量值时)。
在需求较低时,为防止子端口成员管道占用太多带宽,需要设置高容量值。容量的最高值被选为子端口成员管道配置的最高速率。下表说明了容量操作。
每个TC流量限制执行周期开始时,从子端口级别到成员管道的容量传递:
容量值计算:
为了调度一个报文,调度器必须在多个队列里查看报文和credit,当调度器需要查看的队列越多,其性能就越低。
调度器维护了活跃队列的Bitmap,从而可以直接跳过非活跃队列,但是为了检测特定的管道是否有足够的credit,必须使用管道发送状态机进行探测,无论最终调度结果如何(没有报文或者至少产生一个报文),总会消耗CPU时间。
这个场景强调了策略对调度器性能的重要性:如果管道没有足够的credit,其数据包应该尽快丢弃(在触发分层调度器之前),从而可以将管道队列标识为不活跃,发送端就可以跳过这个管道而不用消耗资源去查看管道credit。
端口调度器的性能针对大量队列进行了优化。但如果队列的数量很少,那么对于相同级别的活动流量,端口调度器的性能预期会比一小组消息传递队列的性能更差。
动规(13)-数据包的调度机制&山区建小学(试改成轮换数组)
目录
一、数据包的调度机制
描述
随着 Internet的迅猛发展,多媒体技术和电子商务应用日益广泛,Internet上的服务质量
(QoS,Qualityof Service)问题已越来越受到重视。网络中采用的数据包调度机制与网络的服务质量 QoS 有着密切的关系。研究表明传统的基于队列的调度机制已不能满足网络服务质量QoS 的需求。服务质量 QoS 取决于数据包的延迟。每一个数据包都有一个延迟惩罚值。由于数据包承载的数据不同,不同数据包的延迟惩罚值也可能不同。此外,数据包的延迟也和它的发送顺序有关。如果一个数据包被第K个发送,假设它的延迟惩罚值是D,则这个数据包的最终延迟是 (K - 1) * D。北京大学2012 级信息学院的同学在程序设计课堂上,设计了一种新的基于栈的数据包的调度算法。同学们通过栈的先进后出(Last in First out)的原理,改变数据包的发送顺序,以减小数据包的延迟总值。给定N 个等待调度的数据包,起始这N 个数据包排成一个队列等待发送。接着,这些数据包按序进栈,调度算法可以控制数据包的出栈顺序。因此通过栈,可以将后面的数据包先于前面的数据包发送出去。请你实现一个调度算法使N 个数据包的延迟总值最小。
输入
标准的输入包含若干组测试数据。输入第一行是整数T(1 <= T <= 1000),表明有T组测试数据。紧接着有T组连续的测试。每一组测试数据的第1行是 N(N <= 100),表述数据包的个数。接着的 N 行,每一行是一个整数,第i 行表示数据包i的延迟惩罚值( <=50 )。
输出
对于每组测试数据,输出最小的延迟总值。
样例输入
1
5
5
4
3
2
2
样例输出
24
#include <iostream>
using namespace std;
int n, k, t, ans;
int v[105], sum[105], f[105][105];
int main()
cin >> t;
while (t--)
cin >> n;
for (int i = 1; i <= n; i++)
cin >> v[i], sum[i] = sum[i - 1] + v[i];
for (int len = 2; len <= n; len++)
for (int i = 1; i + len - 1 <= n; i++)
f[i][len] = 0x7fffffff;
for (int k = i; k <= i + len - 1; k++)
f[i][len] = min(f[i][len], f[i][k - i] + v[k] * (len - 1) + f[k + 1][i + len - k - 1] + (sum[i + len - 1] - sum[k]) * (k - i));
//对于进栈的三段,出栈有132、123、312、321以上四种合并都包括,上面是132形式!
cout << f[1][n] << endl; // 231这种形式在栈中不可能出现
return 0; // 3 19 6 7结果为19
题解
由于是栈,由栈的特点我们可以看出如果在某个区间中最后一个出栈的元素为k,那么必然是它前面的元素进来全部出去(或者不进来直接出),k进栈,然后后面的元素再进栈,出栈,k出栈。
首先解释预处理部分,常见的区间dp预处理,前缀和循环部分,第一层循环,枚举区间长度,相信在区间dp中并不陌生
第二层循环,枚举区间的开始元素
第三层循环,枚举该区间内所有可能最后一个出栈的元素状态数组,f[i][len]代表以i下标为开头,len为长度的一个区间的最小值状态转移方程:由于k为最后一个出栈的元素,那么f[i][len]=f[i][k-i]+C[k]+f[k+1][i+len-1];由dp特点我们可以得到,f[i][k-i]我们是已经计算过的,直接加就好了。C[k]是什么呢?C[k]代表k元素的延迟值,由于k是最后一个出栈的,那么在这个区间里C[k]=v[k]*(len-1);那么后面的同理加上f[k+1][i+len-k-1](即后半部分的延迟最小值)。
此时我们又遇到一个问题,我们并不能确定该区间是否为第一个进行操作的,怎么办呢?前缀和!它们的延迟值最终答案只要再加上该区间所有元素和S*p,(p为区间在整个序列的相对位置)。S即为(sum[i+len-1]-sum[k]),p即为(k-i);
特别要注意的是,f[i][len]一开始的赋值要尽可能大,不够大的话很容易就出错了= =....
二、山区建小学(试改成轮换数组)
描述
政府在某山区修建了一条道路,恰好穿越总共m个村庄的每个村庄一次,没有回路或交叉,任意两个村庄只能通过这条路来往。已知任意两个相邻的村庄之间的距离为di(为正整数),其中,0 < i < m。为了提高山区的文化素质,政府又决定从m个村中选择n个村建小学(设 0 < n < = m < 500 )。请根据给定的m、n以及所有相邻村庄的距离,选择在哪些村庄建小学,才使得所有村到最近小学的距离总和最小,计算最小值。
输入
第1行为m和n,其间用空格间隔
第2行为(m-1) 个整数,依次表示从一端到另一端的相邻村庄的距离,整数之间以空格间隔。
例如
10 3
2 4 6 5 2 4 3 1 3
表示在10个村庄建3所学校。第1个村庄与第2个村庄距离为2,第2个村庄与第3个村庄距离为4,第3个村庄与第4个村庄距离为6,...,第9个村庄到第10个村庄的距离为3。
输出
各村庄到最近学校的距离之和的最小值。
样例输入
10 2
3 1 3 1 1 1 1 1 3
样例输出
18
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n, m, x;
int dp[501][501], s[501], sum[501][501];
int main()
scanf("%d%d", &n, &m);
memset(dp, 0x7f, sizeof(dp));
for (int i = 2; i <= n; i++)
scanf("%d", &s[i]);
s[i] = s[i - 1] + s[i];
//前缀和
for (int i = 1; i < n; i++)
for (int j = i + 1; j <= n; j++)
sum[i][j] = sum[i][j - 1] + s[j] - s[(i + j) / 2]; // c处理出任意两点间建一所小学所需路程和(中点原理)
for (int i = 1; i <= n; i++)
dp[1][i] = sum[1][i];
for (int k = 2; k <= m; k++)
for (int i = k; i <= n; i++)
for (int j = k - 1; j < i; j++)
dp[k][i] = min(dp[k][i], sum[j + 1][i] + dp[k - 1][j]);
printf("%d\\n", dp[m][n]); //最后区间dp
return 0;
以上是关于DPDK QoS 框架 - 3. 分级调度模块的实现的主要内容,如果未能解决你的问题,请参考以下文章
[4G&5G专题-96]:MAC层- 调度 - 下行调度的原理过程与算法