背景和意義
隨着Internet的發展,網絡中出現了越來越多的高速和長距離鏈路,這些鏈路的特點是時延帶寬積(BDP=bandwith*RTT)很大,也就是說,這些鏈路所能容納的總數據量很大。
傳統TCP協議,例如TCP-Reno、TCP-NewReno、TCP-SACK中,每過一個RTT(Round Trip Times),窗口增加一個單位,這使得TCP的數據傳輸速度緩慢,遠不能充分利用網絡帶寬。當碰上高帶寬環境時,可能需要經歷很多個RTT,擁塞窗口才能接近於一個BDP。如果數據流很短,可能擁塞窗口還沒增長到一個BDP,數據流就已經結束了,這種情況的帶寬利用率就會非常低。
假設網絡帶寬是10Gbps,RTT是100ms,數據包是固定的1250字節,則此網絡所能容納的數據包總量是:\(\dfrac{10\times 10^9\times 0.1}{1250\times 8}=10^5\)個數據包。假設窗口從50000開始增長,也需要50000個RTT(1.4個小時)才能達到網路的滿負荷,如果一個TCP流在此之前結束(顯然往往如此),則未充分利用帶寬。
BIC-TCP
BIC-TCP采用二分搜索的方式來決定擁塞窗口的增長尺度,首先它會記錄擁塞窗口的一個最大值點,這個最大值就是TCP最近一次出現丟包時擁塞窗口的值;還會記錄一個最小值點,即在一個RTT周期內沒有出現丟包事件時窗口的大小。二分搜索就是取最小值和最大值的中間點,當擁塞窗口增長到這個中間值且沒有出現丟包的話,就說明網絡還可以容納更多的數據包。那么將這個中值設為新的最小值,在新的最小值和最大值間搜索中間值。當前擁塞窗口的值還遠沒有達到通道的容量時,其增長速度很快;相反,當擁塞窗口的值接近於通道的容量時,其擁塞窗口增長函數是一個簡化的對數凸函數。這個凸函數使擁塞窗口在飽和點或平衡點比凹函數或線性函數保持更長的時間,在飽和點處,凸函數和線性函數具有最大的窗口增量,因此在丟包發生時會出現大量的數據包被丟失。

BIC-TCP的主要特征是在前面說過的其獨特的窗口增長函數,圖1給出了BIC-TCP的窗口增長函數。當出現丟包事件時,BIC-TCP通過乘以因子 \(\beta\) 來縮小窗口,縮小之前的窗口大小被設置為最大值\(W_{\max}\) ,並且縮小之后的窗口大小被設置為最小值\(W_{\min}\) 。 然后,BIC-TCP使用這兩個參數執行二分搜索,擁塞窗口的下一個取值會是\(W_{\max}\) 和\(W_{\min}\) 之間的“中點” \(W_{mid}\) 。
為了防止擁塞窗口從\(W_{\min}\) 增長到 \(W_{\max}\) 的步長step太大,BIC-TCP還設置了一個常數\(S_{\max}\) ,當step>\(S_{\max}\) 時,BIC-TCP會取下一個增長點為 \(W_{\min}\)+\(S_{\max}\) 而不是 \(W_{mid}\) (加法增加階段),如果沒有出現丟包的話,再更新\(W_{\min}\) ,直到step< \(S_{\max}\) 為止。與此同時BIC-TCP還設置一個另一個控制參數\(S_{\min}\) ,當窗口增量小於 \(S_{\min}\) 時,BIC-TCP會將當前擁塞窗口值設為最大值。
如果窗口增長超過最大值,則說明當前窗口最大值還不是一個飽和點,網絡還可以容納更多的數據包,窗口還有增長的空間,一個新的窗口最大值需要被探索。於是BIC-TCP會進入一個新的階段,叫做最大值探索階段。最大探測使用一個與在加法增長和二分搜索階段完全對稱的窗口增長函數。圖1中給出了在最大探索階段期間的窗口增長函數。在最大探測期間,窗口最初緩慢地增長以發現附近新的最大值,經過一段時間的緩慢增長,如果沒有找到新的最大值(即沒出現包丟失),則它猜測新的最大值離得很遠,所以它給窗口大小增加一個大的固定增量,使用加法增加切換到更快的增加速度。BIC-TCP的良好性能來自 \(W_{\max}\) 附近的緩慢增加以及在加法增加和最大探測期間的線性增加。
BIC-TCP在高速網絡中具有良好的可擴展性、多個流競爭的公平性和低窗口振盪的穩定性。然而,BIC-TCP的增長功能對於TCP來說仍然過於激進,特別是在短RTT或低速網絡下。此外,窗口控制的幾個不同階段(二進制搜索增加、最大探測、\(S_{\max}\) 和 \(S_{\min}\) )增加了協議實現和性能分析的復雜性。
算法與推導
算法簡述
CUBIC是BIC-TCP的下一代版本。 它通過用三次函數(包含凹和凸部分)代替BIC-TCP的凹凸窗口生長部分,大大簡化了BIC-TCP的窗口調整算法, 該函數在保留BIC-TCP的優點(特別是其穩定性和可擴展性)的同時,簡化了窗口控制,增強了其TCP友好性。實際上,任何奇數階多項式函數都具有這種形狀。三次函數的選擇是偶然的,但是也是出於方便。CUBIC的關鍵特征是其窗口增長僅取決於兩個連續擁塞事件之間的時間。一個擁塞事件是指出現TCP快速恢復的時間。因此,窗口增長與RTT無關。 這個特性允許CUBIC流在同一個瓶頸中競爭,有相同的窗口大小,而不依賴於它們的RTT,從而獲得良好的RTT公平性。而且,當RTT較短時,由於窗口增長率是固定的,其增長速度可能比TCP標准慢。 由於TCP標准(例如,TCP-SACK)在短RTT下工作良好,因此該特征增強了協議的TCP友好性。
CUBIC窗口增長函數

CUBIC的窗口增長函數是一個三次函數,非常類似於BIC-TCP的窗口增長函數,CUBIC的函數圖像如圖2所示。CUBIC的詳細運行過程如下,當出現丟包事件時,CUBIC同BIC-TCP一樣,會記錄這時的擁塞窗口大小作為\(W_{\max}\),接着通過常數因子\(\beta\) 執行擁塞窗口的乘法減小,這里 \(\beta\) 是一個窗口降低常數,並進行正常的TCP快速恢復和重傳。從快速恢復階段進入擁塞避免后,使用三次函數的凸函數增加窗口。三次函數設置在 \(W_{\max}\) 處達到穩定點,如果存在新的最大窗口(網絡帶寬發生變化), 然后使用三次函數的凹函數開始探索新的最大窗口。
CUBIC的窗口增長函數公式如下所示:

\(C\)是一個CUBIC的參數, \(t\)是從窗口上次降低開始到現在的時間,是一個彈性值,而 \(K\) 是上述函數在沒有進一步丟包的情況下將當前的擁塞窗口 \(W\) 增加到 \(W_{\max}\) 經歷的時間。
\(K\)計算公式如下:

在擁塞避免階段每收到一個ACK,CUBIC都會使用方程(1)計算在下個RTT的窗口增長速率。CUBIC使用$W\left( t+RTT \right) $作為擁塞窗口的候選值,假設當前擁塞窗口大小為 \(cwnd\)。根據 \(cwnd\)的值,CUBIC有三種運行模式。在linux內核實現中,\(K\)值立方根求取使用得是牛頓迭代法,這是因為牛頓迭代法的性能優於二分法。
如果\(cwnd\) 小於(標准)TCP在上次丟包事件之后 \(t\) 時刻到達的窗口大小,那么CUBIC處於TCP模式(我們將在下面描述如何根據時間確定標准TCP的窗口大小)。
如果\(cwnd\) 小於\(W_{\max}\) ,那么CUBIC在三次函數的凸函數區域。
如果\(cwnd\) 大於\(W_{\max}\) ,那么CUBIC處於三次函數的凹區域。
TCP友好型區域
標准TCP協議在網絡時延帶寬積小或者RTT小的情況下表現仍不錯,CUBIC被設計為在這兩種情況下可以很好的兼容標准TCP協議。算法執行過程中,每收到一個ACK后都會判斷當前是否處於標准TCP階段,即TCP友好域,以此來更好的兼容TCP。因此需要通過TCP的AIMD(加法增、乘法減)特性並使用加法因子 $\alpha $ 和乘法因子 \(\beta\) 去估算在TCP傳統擁塞算法下的擁塞窗口大小。

\(W_{\max}\left( 1-\beta \right)\)是當發生擁塞事件時減少后的初始 \(cwnd\), \(3\frac{\beta}{2-\beta}\)為線性增長的大小(斜率), \(\frac{t}{RTT}\)為從發生擁塞窗口到至今需要經歷RTT的個數。如果當前的 \(cwnd\) 小於\(W_{tcp\left( t \right)}\) ,則處於TCP模式,因此每次接受ACK時,都會將 \(cwnd\)設置為\(W_{tcp\left( t \right)}\) 。
凸區域
當在擁塞避免階段收到一個ACK,如果協議不處於TCP模式,且\(cwnd\)小於\(W_{\max}\),那么協議就處於凸區域,在這個區域,\(cwnd\) 的增量為:

凹區域
當前的\(cwnd\) 大於\(W_{\max}\) 時,協議就會進入凹區域。由於 \(cwnd\)大於先前的飽和點\(W_{\max}\) ,這表明自上次擁塞事件以來,網絡條件可能受到干擾,這可能意味着在一些競爭流離開后,可用帶寬會增加。由於網絡是高度異步的,可用帶寬的波動總是存在的。凹區域使得窗口在開始時增長非常緩慢,並逐漸增加其增長率。由於CUBIC正在搜索一個新的\(W_{\max}\) ,我們也將此階段稱為最大探測階段。由於沒有修改凹區域的窗口增長函數,因此兩個區域的窗口增長函數保持不變。因此 \(cwnd\)的增量同樣為:

乘法降低
當出現數據包丟失時,CUBIC會通過乘法因子 $\beta $ 來降低擁塞窗口,這里取\(\beta =0.2\) 。雖然自適應性的設置 $\beta $ 會導致更快的收斂,但是會使協議的分析變得更加困難,並影響協議的穩定性。
快速收斂機制
新的流量加入網絡時,網絡中的現有流量需要放棄其部分帶寬份額,以使新流量有一定的增長空間。在發生丟包前,CUBIC會記錄一個最大窗口值\(W_{\max}\) 。當發生丟包后,在降低窗口前,CUBIC又會記錄當前的窗口值作為新的\(W_{\max}\) ,為了不至於混淆,可以將之前記錄的\(W_{\max}\) 記為\(W_{last\_max}\)。當發生丟包時,CUBIC會比較$ W_{last_max}$ 和\(W_{\max}\) 的大小,如果\(W_{\max}\) 小於$ W_{last_max}$ ,這表明由於可用帶寬的變化,該流的窗口飽和點正在降低。這種情況下,CUBIC的做法是通過進一步的減小\(W_{\max}\) 來釋放更多的可用帶寬,使得新加入的流量有一定的增長空間。
偽碼示例

對所需要的參數進行初始化,其中tcp_friendliness決定TCP友好型區域是否開啟,默認為開啟狀態;fast_convergence決定快速收斂機制是否開啟,默認為開啟狀態;其余的參數值需要放入cubic_reset()函數中,因為這些參數在每次發生擁塞時間后,都需要進行重置。其中 \(W_{last\_max}\) 為上一次發生擁塞窗口時所記錄的窗口值;epoch_start會記錄每次發生擁塞的時間;origin_point則記錄\(W_{last\_max}\) 和當前的 \(cwnd\) 之間的最大值;dMin記錄最小的RTT(往返傳輸時間); \(W_{tcp}\)為TCP模式時的計算得出的窗口值; \(K\)是在沒有進一步丟包的情況下將當前的擁塞窗口 \(W\)增加到\(W_{\max}\) 經歷的時間;ack_cnt 記錄接收到ACK的數量。

每當接受到一個ACK時,dMin會去記錄自身與當前RTT的最小值,保證dMin記錄着最小的RTT,這樣做的原因是由於網絡的不確定性因素,導致每個ACK對應的RTT可能也會有增加,因此需要找到最接近實際的RTT。ssthresh判斷當前狀態為慢啟動還是擁塞避免階段的閾值,如果當前的cwnd小於或等於ssthresh,那么進入慢啟動階段,每接收到一個ACK,cwnd相應自加一,直到cwnd大於ssthresh,則需要進入擁塞避免階段,cubic_update()函數會計算得出一個cnt值,與cwnd_cnt進行比較,以此判斷cwnd是否需要增長。

當包丟失(擁塞事件)時,通過快速恢復階段,接下來就會重新進入擁塞避免。因此epoch_start需要置為0。如果發生擁塞事件時的cwnd小於上一次發生擁塞事件的\(W_{last\_max}\) ,並且快速收斂機制為開啟狀態,即意味着是由於新流的加入,導致cwnd小於\(W_{last\_max}\) ,因此需要將當前的cwnd需要讓出一部分帶寬給新流;否則就是由於網絡的波動(帶寬減少或增加)導致cwnd小於或大於\(W_{last\_max}\) ,那么只需要將當前的cwnd記錄到\(W_{last\_max}\) 即可。當cwnd和ssthresh進行乘法減少后,重新開始擁塞避免階段(即凸函數區域)

Cubic_update()函數為核心算法的實現。當epoch_start為零時,意味着重新進入擁塞避免后階段,如果cwnd小於\(W_{last\_max}\),需要計算cwnd到達\(W_{last\_max}\) 的時間K;反之如果cwnd大於\(W_{last\_max}\) ,意味着網絡的帶寬增加了,需要進行窗口最大值探索(凹函數區域)。時間\(t\)記錄的是以擁塞避免階段為起點時間到當前的時間之差,而后與dMin之和,因而去計算出下一個dMin所需要到達的目標窗口值target。如果target大於當前窗口值cwnd,需要增加窗口的增長率;反之,意味着當前窗口值已經到達平穩期,需要去降低窗口的增長率。

同時,也需要計算如果是在傳統的TCP擁塞控制算法下的窗口值\(W_{tcp}\) ,然后\(W_{tcp}\) 與在CUBIC擁塞控制算法下的cwnd進行比較,以此判斷是否進入tcp友好型區域。這樣通過與TCP傳統擁塞控制算法的組合,能夠得到更高的網絡質量。
總體流程

原理推導
CUBIC窗口增長函數的推導
從作者的角度來看,是如何從通過改進BIC算法得到CUBIC算法,最主要的還是窗口增長函數的變化。

從上圖,可以看出該函數的斜率是從高到低,而后又慢慢增大。因此,可以直觀假設其導數為二次曲線函數,以\(g\left( x \right) =3x^2-2x\)為例,它的圖像如下圖所示。

由此,可以得到它的原函數為\(f\left( x \right) =x^3-x^2+1\) ,即它的圖像為

以上僅僅是個例子,目的在於確定CUBIC的曲線形狀,上面的圖像和BIC的窗口探測曲線很像,在確定了曲線的形狀之后,最終要確定曲線的參數,而曲線的通用方程應該是:
對於參數的確定,需要聯系CUBIC擁塞控制算法的過程,\(W_{\min}\)實際上就是上次擁塞事件發生后最大窗口( \(W_{\max}\) 記錄着擁塞事件發生時所測量出的對應的最大窗口值,即為已知)乘法減少后的值,因此 \(W_{\min}=W_{\max}\times beta\),再者由於函數的對稱特性,能夠計算得出最高點值為$W_{\max}+W_{\max}\left( 1-beta \right) $ 。

因此,可以得出以下的圖像,由於這個函數增長圖像對應着一次擁塞避免狀態的重新開始,因此P1點處的時間為零,而由P1到P0的時間假設為r已知,根據對稱的特性,因而P2處的時間也為2r。

由於曲線本身是關於P0(x=r, y=\(W_{\max}\) )這個點對稱的,因此可以確定曲線方程的常數因子為\(W_{\max}\) ,所以能夠將曲線方程寫成如下的形式:
現在的目標就是求h(x),再由於P1和P2點已知:
由0、1、3式可得:
根據4式以及f(x) ,即h(x)為三次函數的特性,可以假設:
其中C為一常量,因此我們需要將5式代入6式中,可得:
綜上可得:
TCP友好型區域\(W_{\max}\)窗口值公式的推導

上圖為在TCP傳統擁塞控制算法(New-Reno)下,窗口值隨時間的變化過程,其中W對應着發生擁塞事件時的窗口值(最大窗口值),由於上圖具有周期性,因此只需對第一個周期的圖像進行分析即可,在發生擁塞事件后,需要對\(W\)進行乘法減少,即\(\left( 1-b \right) W\)(起點),而后通過TCP的累計確認機制以固定的速率a去進行窗口值的增長,直到到達W。
顯然,通過等差數列公式,可以得出:
又該等差數列應該從零開始,所以
\(\left( 1-b \right) W\)需要n次RTT才能到達\(W\) ,因此在這一個周期內的平均窗口值\(S\),應該拿窗口值總和,除以迭代增長的次數\(n\)。
假設RTT為R時間(second),因此每秒的發包速率為:
假設在這個一個周期內,發送窗口的總和為P,則
因此丟包率(丟一個包時)p為
由此可得
然后,再將8式代入5式得到每秒的平均發包速率為
又在TCP傳統得擁塞控制算法中,\(\alpha =1\) \(\beta =0.5\) , \(\hat{T}=\frac{1}{R}\sqrt{\frac{3}{2}\frac{1}{p}}\) 。因此在CUBIC算法中保持這種關系,只有當\(\alpha =\frac{3\beta}{2-\beta}\) 時滿足,且\(\alpha\) 為TCP擁塞控制算法窗口增加的加法因子,所以在CUBIC中計算 \(W_{tcp}\)的公式為:
源碼分析
/*
* Compute congestion window to use.
*/ //從快速恢復退出並進入擁塞避免狀態之后,更新cnt
static inline void bictcp_update(struct bictcp *ca, u32 cwnd)
{
u64 offs;//時間差|t - K|
//delta是cwnd差,bic_target是預測值,t為預測時間
u32 delta, t, bic_target, max_cnt;
ca->ack_cnt++; /*ack包計數器加1 count the number of ACKs */
if (ca->last_cwnd == cwnd && //當前窗口與歷史窗口相同
(s32)(tcp_time_stamp - ca->last_time) <= HZ / 32)//時間差小於1000/32ms
return; //直接結束
ca->last_cwnd = cwnd;//記錄進入擁塞避免時的窗口值
ca->last_time = tcp_time_stamp;//記錄進入擁塞避免時的時刻
if (ca->epoch_start == 0) {//丟包后,開啟一個新的時段
ca->epoch_start = tcp_time_stamp; /*新時段的開始 record the beginning of an epoch */
ca->ack_cnt = 1; /*ack包計數器初始化 start counting */
ca->tcp_cwnd = cwnd; /*同步更新 syn with cubic */
//取max(last_max_cwnd , cwnd)作為當前Wmax飽和點
if (ca->last_max_cwnd <= cwnd) {
ca->bic_K = 0;
ca->bic_origin_point = cwnd;
} else {
/* Compute new K based on
* (wmax-cwnd) * (srtt>>3 / HZ) / c * 2^(3*bictcp_HZ)
*/
ca->bic_K = cubic_root(cube_factor
* (ca->last_max_cwnd - cwnd));
ca->bic_origin_point = ca->last_max_cwnd;
}
}
/* cubic function - calc*/
/* calculate c * time^3 / rtt,
* while considering overflow in calculation of time^3
* (so time^3 is done by using 64 bit)
* and without the support of division of 64bit numbers
* (so all divisions are done by using 32 bit)
* also NOTE the unit of those veriables
* time = (t - K) / 2^bictcp_HZ
* c = bic_scale >> 10 == 0.04
* rtt = (srtt >> 3) / HZ
* !!! The following code does not have overflow problems,
* if the cwnd < 1 million packets !!!
*/
/* change the unit from HZ to bictcp_HZ */
t = ((tcp_time_stamp + (ca->delay_min>>3) - ca->epoch_start)
<< BICTCP_HZ) / HZ;
//求| t - bic_K |
if (t < ca->bic_K) // 還未達到Wmax
offs = ca->bic_K - t;
else
offs = t - ca->bic_K;//已經超過Wmax
/* c/rtt * (t-K)^3 */ //計算立方,delta =| W(t) - W(bic_K) |
delta = (cube_rtt_scale * offs * offs * offs) >> (10+3*BICTCP_HZ);
//t為預測時間,bic_K為新Wmax所對應的時間,
//bic_target為cwnd預測值,bic_origin_point為當前Wmax飽和點
if (t < ca->bic_K) /* below origin*/
bic_target = ca->bic_origin_point - delta;
else /* above origin*/
bic_target = ca->bic_origin_point + delta;
/* cubic function - calc bictcp_cnt*/
if (bic_target > cwnd) {// 相差越多,增長越快,這就是函數形狀由來
ca->cnt = cwnd / (bic_target - cwnd);//
} else {//目前cwnd已經超出預期了,應該降速
ca->cnt = 100 * cwnd; /* very small increment*/
}
/* TCP Friendly —如果bic比RENO慢,則提升cwnd增長速度,即減小cnt
* 以上次丟包以后的時間t算起,每次RTT增長 3B / ( 2 - B),那么可以得到
* 采用RENO算法的cwnd。
* cwnd (RENO) = cwnd + 3B / (2 - B) * ack_cnt / cwnd
* B為乘性減少因子,在此算法中為0.3
*/
if (tcp_friendliness) {
u32 scale = beta_scale;
delta = (cwnd * scale) >> 3; //delta代表多少ACK可使tcp_cwnd++
while (ca->ack_cnt > delta) { /* update tcp cwnd */
ca->ack_cnt -= delta;
ca->tcp_cwnd++;
}
if (ca->tcp_cwnd > cwnd){ /* if bic is slower than tcp */
delta = ca->tcp_cwnd - cwnd;
max_cnt = cwnd / delta;
if (ca->cnt > max_cnt)
ca->cnt = max_cnt;
}
}
ca->cnt = (ca->cnt << ACK_RATIO_SHIFT) / ca->delayed_ack;
if (ca->cnt == 0) /* cannot be zero */
ca->cnt = 1; //此時代表cwnd遠小於bic_target,增長速度最大
}
巨人的肩膀
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.153.3152&rep=rep1&type=pdf
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.37.7442&rep=rep1&type=pdf