一次数据包的丢失,也许并不代表网络发生拥塞。但是在经典的tcp拥塞控制条件下,丢包作为网络拥塞信号,tcp的窗口就要减半,发送端就要减少向网络中发送的数据包数量。
若是丢包是如下原因,降窗明显不合理。
- f might traverse a shallow buffer on a high-BDP link, with the loss due to bad luck in statistical multiplexing rather than high link utilization.
- There may be a higher-rate competing flow.
- There may be random non-congestion loss somewhere along the path such as noise packet loss.
关键的就是PCC的速率控制机制了,这个机制和后来出现的BBR算法几乎相同,增加发送速率对网络带宽进行探测。PCC按照监测周期(MI,Monitor Interval)更改发送速率,每四个MI形成两个对。在每个监测对中,分别按照速率
(
1
+
ϵ
)
r
(1+\epsilon)r
(1+ϵ)r,
(
1
−
ϵ
)
r
(1-\epsilon)r
(1−ϵ)r向网络中发包。四个监测周期后,速率更改为
r
r
r,发送端等待反馈的ack,分别计算相应监测周期的效用函数
(
U
i
+
,
U
i
−
,
i
=
1
,
2
)
(U_i^+,U_i^-,i=1,2)
(Ui+,Ui−,i=1,2)。如果
U
i
+
>
U
i
−
,
∀
i
∈
{
1
,
2
}
U_i^+>U_i^-,\forall i\in\{1,2\}
Ui+>Ui−,∀i∈{1,2},则将发送速率调整为
(
1
+
ϵ
)
r
(1+\epsilon)r
(1+ϵ)r;如果
U
i
+
<
U
i
−
,
∀
i
∈
{
1
,
2
}
U_i^+<U_i^-,\forall i\in\{1,2\}
Ui+<Ui−,∀i∈{1,2},则将发送速率调整为
(
1
−
ϵ
)
r
(1-\epsilon)r
(1−ϵ)r;若是其他情况,则保持当前的速率。
其采用的效用函数如下:
u
i
(
x
)
=
T
i
(
x
)
s
i
g
m
o
i
d
(
L
i
(
x
)
−
0.05
)
−
x
i
(
t
)
L
i
(
t
)
(1)
u_i(x)=T_i(x)sigmoid(L_i(x)-0.05)-x_i(t)L_i(t)\tag{1}
ui(x)=Ti(x)sigmoid(Li(x)−0.05)−xi(t)Li(t)(1)
x
i
(
t
)
x_i(t)
xi(t)为数据包发送速率,
L
i
(
t
)
L_i(t)
Li(t)为丢包率,
T
i
(
x
)
=
x
i
(
t
)
(
1
−
L
i
(
x
)
)
T_i(x)=x_i(t)(1-L_i(x))
Ti(x)=xi(t)(1−Li(x))为吞吐量,当丢包率超过0.05,
u
i
(
x
)
u_i(x)
ui(x)为负,可以认为此时的网络状况极其糟糕。
这个算法,有缺点,带宽小的数据流竞争带宽慢,收敛速度较慢。但是保证了链路的利用率高。另外一个缺点,就是发送速率不太稳定,就是因为它的速率总是为效用函数取得最大的值,这个看下论文[3]中的图例。
我把[4]的代码,移植到ns3仿真平台,对算法性能性能进行测试。
只使用一条链路,两个点,链路配置(5Mbps, 50ms单向时延,5Mbps*100ms的缓冲区)。一个测试五条数据流,各个数据流进入网络中的时刻不同。
各个数据流的带宽变化情况:
带宽公平性有保证,发送速率抖动明显。
接收数据包的单向传输时延变化,仅画出数据流1:
我的拥塞控制分析文章,一般是在3M带宽的链路中进行测试,使用三条数据流。这次例外,因为,我在3M链路上测试时,发现第一条流的发送速率超出了链路带宽很多,这个算法有不稳定的情况。可能是效用函数的定义问题,懒得花时间去分析具体原因了,没有收益。
[4]代码中的效用函数的实现同论文中的定义有很大不同。
double PccUtilityFunction::CalculateUtility(const MonitorInterval* interval) const{
// The caller should guarantee utility of this interval is available.
QUIC_BUG_IF(interval->first_packet_sent_time ==
interval->last_packet_sent_time);
// Add the transfer time of the last packet in the monitor interval when
// calculating monitor interval duration.
double interval_duration =
static_cast<double>((interval->last_packet_sent_time -
interval->first_packet_sent_time +
interval->sending_rate
.TransferTime(kMaxV4PacketSize))
.ToMicroseconds());
double rtt_ratio =
static_cast<double>(interval->rtt_on_monitor_start.ToMicroseconds()) /
static_cast<double>(interval->rtt_on_monitor_end.ToMicroseconds());
if (rtt_ratio > 1.0 - interval->rtt_fluctuation_tolerance_ratio &&
rtt_ratio < 1.0 + interval->rtt_fluctuation_tolerance_ratio) {
rtt_ratio = 1.0;
}
double latency_penalty =
1.0 - 1.0 / (1.0 + exp(kRTTCoefficient * (1.0 - rtt_ratio)));
double bytes_acked = static_cast<double>(interval->bytes_acked);
double bytes_lost = static_cast<double>(interval->bytes_lost);
double bytes_sent = static_cast<double>(interval->bytes_sent);
double loss_rate = bytes_lost / bytes_sent;
double loss_penalty =
1.0 - 1.0 / (1.0 + exp(kLossCoefficient * (loss_rate - kLossTolerance)));
return (bytes_acked / interval_duration * loss_penalty * latency_penalty -
bytes_lost / interval_duration) * 1000.0;
}
这个不正常的仿真结果(链路带宽3Mbps),也放在这里吧。
这里的仿真结果,大致看一下吧。PCC里面的思想还是不错的,不知道后续的BBR的拥塞控制是不是也从PCC中获取了一定的灵感。
测试下PCC Vivace[5]在3M链路中的表现吧。效用函数以及相关参数的实现来自webrtc的实现。
Vivace数据流的发送速率变化情况:
Vivace数据流的接收数据包单向传输时延变化:
也可以参考斯坦福学生,在课程cs244,对PCC的评测,有同学把它翻译成了中文[6]
[1] PCC: Re-architecting Congestion Control for Consistent High Performance
[2] 拥塞控制算法分类
[3] statistical learning based congestion control for real-time video communication
[4] PCC code
[5] PCC Vivace: Online-Learning Congestion Control
[6] PCC-244 翻译