kcp-go icon indicating copy to clipboard operation
kcp-go copied to clipboard

KCP 测速工具

Open ZezhongWang opened this issue 5 years ago • 19 comments

我自己用go语言实现了iperf测速工具, 实现了基本的一些测速功能, 最主要的就是加上了对于自定义应用层协议的扩展, 所以也可以用来对kcp进行更精确地测速.因为 Fast.com 之类的还是涉及到了3个点的传输。

然后一个有趣的现象就是, 对于我自身的网络情况:

  • PC: 广东某高校校园网, 时好时坏, 阿门~
  • Server: Location: LA Latency(Ping): 170ms 左右 搬瓦工 CN2, 号称百兆带宽
  • KCP 版本: May 8, 2019 最新版 ( go get 下来 )

在上行情况下,用适当的参数,确实 KCP 的表现会比 TCP 好一些,大约有40%-50%提升 :

// KCP 结果, 平均带宽在 17.69Mb/s 左右
>.\iperf-go.exe -c <server_ip> -proto kcp -sw 512  // KCP 发送窗口512, 不开启FEC, 加密  
09:55:23.790 0 ▶ INFO 001 Go-logging init finish
Iperf started:
addr:104.224.137.244    port:5201       proto:rudp      interval:1000   duration:10     NoDelay:false   burst:true      BlockSize:4096  StreamNum:1
RUDP settting: sndWnd:512       rcvWnd:512      writeBufSize:4096Kb     readBufSize:4096Kb      noCongestion:true       flushInterval:10
Connect to server 104.224.137.244:5201 succeed.
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)  Lost(%)  Early Retrans  Fast Retrans
[  0] 0.00-1.00 sec      3.73 MB        29.81 Mb/s       165.0ms          11    0.39%   0.39%   0.00%   0.00%
[  0] 1.00-2.00 sec      2.47 MB        19.78 Mb/s       169.0ms         600    31.84%  22.50%  9.34%   0.00%
[  0] 2.00-3.00 sec      2.50 MB        20.03 Mb/s       166.0ms         356    18.66%  13.31%  5.35%   0.00%
[  0] 3.00-4.00 sec      1.85 MB        14.78 Mb/s       169.0ms         844    59.94%  44.67%  15.27%  0.00%
[  0] 4.00-5.00 sec      2.32 MB        18.56 Mb/s       165.0ms         606    34.27%  17.53%  16.74%  0.00%
[  0] 5.00-6.00 sec      1.54 MB        12.34 Mb/s       174.0ms         922    78.41%  48.14%  30.28%  0.00%
[  0] 6.00-7.00 sec      1.93 MB        15.41 Mb/s       199.0ms         852    58.06%  51.17%  6.88%   0.00%
[  0] 7.00-8.00 sec      1.69 MB        13.50 Mb/s       167.0ms         499    38.80%  18.97%  19.83%  0.00%
[  0] 8.00-9.00 sec      2.45 MB        19.56 Mb/s       183.0ms         842    45.19%  28.33%  16.85%  0.00%
[  0] 9.00-10.04 sec     1.64 MB        13.16 Mb/s       181.0ms         996    79.48%  39.98%  39.50%  0.00%
- - - - - - - - - - - - - - - - SUMMARY - - - - - - - - - - - - - - - -
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)  Lost(%)  Early Retrans  Fast Retrans
[  0] 0.00-10.04 sec    22.12 MB        17.69 Mb/s       173.8ms        6528    38.73%  25.03%  13.71%  0.00%   [SENDER]

// TCP 结果, 平均带宽在 12.10Mb/s 左右, 在发送端为windows系统的情况下拿不到 retrans 数据
>.\iperf-go.exe -c 104.224.137.244 -proto tcp  
09:56:23.161 0 ▶ INFO 001 Go-logging init finish
Iperf started:
addr:104.224.137.244    port:5201       proto:tcp       interval:1000   duration:10     NoDelay:false   burst:true      BlockSize:131072        StreamNum:1
Connect to server 104.224.137.244:5201 succeed.
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)
[  0] 0.00-1.00 sec      1.50 MB        12.00 Mb/s         0.0ms           0    0.00%
[  0] 1.00-2.00 sec      5.38 MB        43.00 Mb/s         0.0ms           0    0.00%
[  0] 2.00-3.01 sec      0.12 MB         1.00 Mb/s         0.0ms           0    0.00%
[  0] 3.01-4.00 sec      0.00 MB         0.00 Mb/s         0.0ms           0    NaN%
[  0] 4.00-5.00 sec      0.75 MB         6.00 Mb/s         0.0ms           0    0.00%
[  0] 5.00-6.00 sec      2.62 MB        21.00 Mb/s         0.0ms           0    0.00%
[  0] 6.00-7.00 sec      3.62 MB        29.00 Mb/s         0.0ms           0    0.00%
[  0] 7.00-8.01 sec      0.12 MB         1.00 Mb/s         0.0ms           0    0.00%
[  0] 8.01-9.00 sec      0.38 MB         3.00 Mb/s         0.0ms           0    0.00%
[  0] 9.00-10.27 sec     0.62 MB         5.00 Mb/s         0.0ms           0    0.00%
- - - - - - - - - - - - - - - - SUMMARY - - - - - - - - - - - - - - - -
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)
[  0] 0.00-10.27 sec    15.12 MB        12.10 Mb/s         NaNms           0    0.00%   [SENDER]

但是在下行的情况下(TCP有BBR支持),TCP的表现则好很多,快有 KCP 的一倍。并且我也调整过KCP的一些参数,均未获得较好的效果(例如调整snd_wnd, buffer之类)

// KCP 结果,大概在 8.05 Mb/s 左右(这还是经过几次尝试在比较好的结果下)

>.\iperf-go.exe -c 104.224.137.244 -proto rudp -sw 512 –R (客户端)
22:04:39.175 0 ▶ INFO 001 Go-logging init finish
Server listening on 5201
Accept connection from client: 221.4.34.225:42032
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)  Lost(%)  Early Retrans  Fast Retrans
[  0] 0.00-1.00 sec	 1.23 MB	 9.84 Mb/s	 172.0ms	 713	76.04%	7.47%	68.57%	0.00%
[  0] 1.00-2.00 sec	 0.99 MB	 7.91 Mb/s	 172.0ms	 956	126.94%	49.13%	77.81%	0.00%
[  0] 2.00-3.00 sec	 0.96 MB	 7.69 Mb/s	 172.0ms	 766	104.60%	21.99%	82.62%	0.00%
[  0] 3.00-4.00 sec	 0.84 MB	 6.75 Mb/s	 176.0ms	1201	186.79%	65.94%	120.84%	0.00%
[  0] 4.00-5.00 sec	 0.95 MB	 7.59 Mb/s	 174.0ms	1125	155.53%	33.87%	121.66%	0.00%
[  0] 5.00-6.00 sec	 1.30 MB	10.44 Mb/s	 174.0ms	 849	85.39%	36.21%	49.18%	0.00%
[  0] 6.00-7.00 sec	 1.09 MB	 8.69 Mb/s	 173.0ms	 863	104.29%	48.34%	55.95%	0.00%
[  0] 7.00-8.00 sec	 1.09 MB	 8.75 Mb/s	 172.0ms	 818	98.14%	34.91%	63.23%	0.00%
[  0] 8.00-9.00 sec	 0.47 MB	 3.75 Mb/s	 171.0ms	 911	255.03%	106.10%	148.93%	0.00%
[  0] 9.00-10.27 sec	 1.13 MB	 9.06 Mb/s	 180.0ms	1096	126.96%	65.10%	61.86%	0.00%
- - - - - - - - - - - - - - - - SUMMARY - - - - - - - - - - - - - - - -
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)  Lost(%)  Early Retrans  Fast Retrans
[  0] 0.00-10.27 sec	10.06 MB	 8.05 Mb/s	 173.6ms	9298	121.30%	42.56%	78.75%	0.00%	[SENDER]

// TCP BBR结果, 大概在20.40Mb/s左右
>.\iperf-go.exe -c 104.224.137.244 –R  (客户端)
22:04:17.793 0 ▶ INFO 001 Go-logging init finish
Server listening on 5201
Accept connection from client: 221.4.34.225:22567
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)
[  0] 0.00-1.00 sec	 1.50 MB	12.00 Mb/s	 172.6ms	   0	0.00%
[  0] 1.00-2.00 sec	 3.12 MB	25.00 Mb/s	 169.1ms	   0	0.00%
[  0] 2.00-3.00 sec	 2.75 MB	22.00 Mb/s	 205.3ms	 378	19.14%
[  0] 3.00-4.00 sec	 2.00 MB	16.00 Mb/s	 164.8ms	1490	103.73%
[  0] 4.00-5.00 sec	 4.75 MB	38.00 Mb/s	 162.9ms	 873	25.59%
[  0] 5.00-6.00 sec	 1.25 MB	10.00 Mb/s	 163.3ms	   0	0.00%
[  0] 6.00-7.00 sec	 2.50 MB	20.00 Mb/s	 163.3ms	   0	0.00%
[  0] 7.00-8.00 sec	 2.62 MB	21.00 Mb/s	 163.5ms	   6	0.32%
[  0] 8.00-9.00 sec	 2.38 MB	19.00 Mb/s	 162.7ms	   0	0.00%
[  0] 9.00-10.19 sec	 2.50 MB	20.00 Mb/s	 163.1ms	   0	0.00%
- - - - - - - - - - - - - - - - SUMMARY - - - - - - - - - - - - - - - -
[ ID]    Interval        Transfer        Bandwidth        RTT      Retrans   Retrans(%)
[  0] 0.00-10.19 sec	25.50 MB	20.40 Mb/s	 169.1ms	2747	0.15%	[SENDER]

通过其他参数可发现, KCP无论上行还是下行情况下丢包率都异常地高(30%,40%以上),再加上early resend的策略的话在下行的report中可以发现,重传的包量甚至已经超过了本身的包量(也就是说平均下来每个包都发了两次多)

这么来看在 UDP 上搭建可靠传输还是值得商榷的,这么高的丢包率可能与ISP对UDP流量的管制有关。

这里还想讨论的就是,

  • 为什么上行下行丢包率会有这么大的差距?(KCP表现不好基本是由于丢包引起的)
  • 对于这样的网络状况有没有什么优化策略?
  • 然后我自己在 KCP 的基础上实现了 BBR 拥塞控制,包含了 pacing 和 cwnd 的调整策略,效果相较 KCP 有略微的提升(在丢包率和带宽上),但是依旧有较高的丢包率,感觉要摊手了 Orz

iperf-go 测试工具: https://github.com/ZezhongWang/iperf-go 欢迎使用!

ZezhongWang avatar May 09 '19 02:05 ZezhongWang

然后我自己在 KCP 的基础上实现了 BBR 拥塞控制

能参考下代码么? 没找到

ycchildcoder avatar Jun 06 '19 09:06 ycchildcoder

代码未公开(因为基本用的别人的代码),可直接参考kernel的 tcp_bbr.c 部分,基本是简要照搬的。

ZezhongWang avatar Jun 10 '19 07:06 ZezhongWang

@ZezhongWang 根據我的測試,正確實現BBR式的bottleneck bandwidth estimation,通過它來設定cwnd和pacing,是可以達到很好的效果的。我這裏能搭建的最垃圾網絡(香港->塞爾維亞->美國東海岸)可以在丟包率<10%的情況下填滿帶寬,利用率遠超任何標準的TCP(包括BBR)。不垃圾的網絡上和TCP差不多。我發現目前的kcp-go有兩個問題導致丟包率超高:

  • 沒有任何pacing,packet一股腦的往外送。很容易把骨幹網的buffer擠爆,一般給UDP的buffer很少,因爲buffer都留給tcp reno等靠buffer吃飯的協議。這樣導致平均速率沒有填滿還大量丟包
  • 沒有開啓congestion control時,不管帶寬是多少,瘋狂發包,反而大大降低速率。我寫了一個類似「不公平BBR」的算法,儘量填滿帶寬但仍然限制cwnd,才能減少丟包。

過幾天我測好了會發佈代碼

nullchinchilla avatar Sep 16 '19 13:09 nullchinchilla

@nullchinchilla 期待

exceptionplayer avatar Sep 17 '19 06:09 exceptionplayer

@ZezhongWang 根據我的測試,正確實現BBR式的bottleneck bandwidth estimation,通過它來設定cwnd和pacing,是可以達到很好的效果的。我這裏能搭建的最垃圾網絡(香港->塞爾維亞->美國東海岸)可以在丟包率<10%的情況下填滿帶寬,利用率遠超任何標準的TCP(包括BBR)。不垃圾的網絡上和TCP差不多。我發現目前的kcp-go有兩個問題導致丟包率超高:

  • 沒有任何pacing,packet一股腦的往外送。很容易把骨幹網的buffer擠爆,一般給UDP的buffer很少,因爲buffer都留給tcp reno等靠buffer吃飯的協議。這樣導致平均速率沒有填滿還大量丟包
  • 沒有開啓congestion control時,不管帶寬是多少,瘋狂發包,反而大大降低速率。我寫了一個類似「不公平BBR」的算法,儘量填滿帶寬但仍然限制cwnd,才能減少丟包。

過幾天我測好了會發佈代碼

你是在 KCP 之上实现的吗,我之前也实现过没有达到预期效果。 如果使用BBR的话应该是不用把 cwnd 和 pacing 作为参数进行调整的啊,都是根据模型进行计算才对。 如果要自行调整的话那我为什么不直接在 kcp 上加个 pacing 的参数用定时器来发包呢?

ZezhongWang avatar Sep 17 '19 07:09 ZezhongWang

https://github.com/geph-official/geph2/blob/master/libs/kcp-go/kcp.go 和原版KCP有点出入,不能直接pull。(主要在于我正在试图完全去掉每x毫秒的polling,不然在手机上简直是费电大王)。值得注意的地方:

  • 两个congestion control方法,一个是CUBIC的原型BIC,是比较传统的丢包就减慢速度的算法,但是比原版的tahoe效率高太多了,可以和内核的默认tcp媲美。另外一个是LOL,算是一个缺胳膊短腿的BBR吧,可以基本填满带宽而不丢太多的包,使用LOL时tx.go会用pacing。追求低丢包,与现代TCP公平用BIC,追求带宽用LOL。没有什么情况需要关掉congestion control,关掉反而会因为大量丢包重发降低throughput。
  • 原版KCP定min_rto时非常严格,网络稍微有一点jitter就疯狂地重发,虽然再等几毫秒ACK就到了。我让rto乘以1.5,大大地减少重发。我debug了太长时间才发现这是在坏网络情况下疯狂重发的病根,根本不是真的丢包率有多高。rto太长不会真的多影响性能,因为有fast retransmit机制。
  • pacing不是特别重要,可以在好网络上把丢包率从10降到3之类的。坏网络上没什么区别,怎么着都20多

第二点非常重要。我不太清楚原版定rto为什么如此不好。

nullchinchilla avatar Sep 22 '19 20:09 nullchinchilla

测试怎么样, LOL这个名字,哈哈

ycchildcoder avatar Sep 23 '19 03:09 ycchildcoder

poll vs epoll。 需要两个数据结构:一个按sn排序,另一个按照timeout排序,这样才能既满足fastack/ack的需要,也能满足RTO的需要。但timeout如果用离散存储结构,根据测试:

BenchmarkLoopSlice-4   	2000000000	         0.39 ns/op
BenchmarkLoopList-4    	100000000	        54.6 ns/op

如果用离散内存结构,那么必须 54.6* logN > 0.39*N 的时候,才是一个划算的改动。(假设你的数据结构查询是O(logN).

polling的问题我可以尝试优化一下,但我估计不会有大的提升,假如包的到达在时域上是均匀的,那么每个timeout在时域上也是均匀分布的,也就是说,timeout的时钟触发也是均匀的,比如在一个200ms的时延的网络上存在1024个等待确认的包,且20%丢包率。下一个timeout 的平均触发时延应该就是 (200ms/1024) * (1/20%) = 0.9765625ms,1/20% = 5即下一个丢包的位置。实际可能会更加频繁的触发flush()

xtaci avatar Sep 23 '19 04:09 xtaci

第二点的原版minrto我不知道你说的是kcp.c的还是kcp-go的,这个不会有影响,RTO的计算方式的一个下界,实际RTO是远大于“等待几毫秒”的。

xtaci avatar Sep 23 '19 05:09 xtaci

https://github.com/xtaci/kcp-go/tree/auxdata 这个是做过数据结构优化的,去掉了整个队列的polling遍历,但我测过,没什么用,如同上面的数据分析一样,在队列大小只有1K这个级别时,甚至cpu使用率还比用朴素队列方法高。

xtaci avatar Sep 23 '19 12:09 xtaci

@xtaci 我的第二点还真不知道技术上是为什么,可能不是min定的不好。但是不管是为什么,原版经常重发没有丢的包,手动调高RTO就会避免。特别是长延迟,高带宽,低丢包的连接(如世界两端的两个服务器之间)。原版指kcp-go

我的fork里其实没有改数据结构,只是在所有queue都是空的时候会暂停每X毫秒的polling,一旦有新的packet则重新开始。这样解决了idle时费电的问题。不知道有没有什么弊端,是不是只要queue都是空的就可以安全的从updater的heap里删除。

nullchinchilla avatar Sep 23 '19 17:09 nullchinchilla

显然不是啊,flush里有很多控制的,比如silly window syndrome的处理,需要定时触发。

xtaci avatar Sep 23 '19 23:09 xtaci

@nullchinchilla 你说的minrto的问题,可能是我昨天修正的一个bug, flush中的的if else顺序和interval区间的关系,会导致RTO发过的包也被fastack,这样就产生了不必要重传,我昨天调整了下if else顺序。 https://github.com/xtaci/kcp-go/releases/tag/v5.4.9

你调高了RTO,只是把这个问题掩盖了而已。

xtaci avatar Sep 24 '19 01:09 xtaci

@xtaci 确实改善了一点点,但是调高RTO还是降低重发。会不会是时钟稍微有点不准导致的?

另外我的Idle机制是连续几秒queue都是空的,并且flush没有改动任何变量时,暂停polling。目前使用着似乎没有问题。

nullchinchilla avatar Sep 24 '19 03:09 nullchinchilla

如果queue是空的,那遍历snd_buf也不需要消耗时间啊,那又何必修改polling呢,系统每秒的中断多了去了。

xtaci avatar Sep 24 '19 04:09 xtaci

在传输量大的时候,改善很多,我实测应该降低了+40%的不必要的fastack,你需要考虑不同丢包率和抖动的环境,并且丢包率还在变动的情况。 @nullchinchilla

xtaci avatar Sep 24 '19 04:09 xtaci

https://github.com/xtaci/kcp-go/releases/tag/v5.4.10 你可以试下这个,看在你的环境下有没有改善,修复了一个潜在的bug。 @nullchinchilla

xtaci avatar Sep 24 '19 05:09 xtaci

@xtaci 在安卓系统上,屏幕关了,进入到deep sleep确实就是好多秒才中断一次CPU的频率。手机上的省电机制非常依赖极低的interrupt,而且不断的polling其实也会被系统suspend。实测上如果不去掉polling又费电又有时卡顿。

我会测一侧改动的

nullchinchilla avatar Sep 24 '19 12:09 nullchinchilla

嗯,可以试试

xtaci avatar Sep 24 '19 15:09 xtaci