kcp-go
kcp-go copied to clipboard
KCP 测速工具
我自己用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 欢迎使用!
然后我自己在 KCP 的基础上实现了 BBR 拥塞控制
能参考下代码么? 没找到
代码未公开(因为基本用的别人的代码),可直接参考kernel的 tcp_bbr.c 部分,基本是简要照搬的。
@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 期待
@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 的参数用定时器来发包呢?
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为什么如此不好。
测试怎么样, LOL这个名字,哈哈
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()
第二点的原版minrto我不知道你说的是kcp.c的还是kcp-go的,这个不会有影响,RTO的计算方式的一个下界,实际RTO是远大于“等待几毫秒”的。
https://github.com/xtaci/kcp-go/tree/auxdata 这个是做过数据结构优化的,去掉了整个队列的polling遍历,但我测过,没什么用,如同上面的数据分析一样,在队列大小只有1K这个级别时,甚至cpu使用率还比用朴素队列方法高。
@xtaci 我的第二点还真不知道技术上是为什么,可能不是min定的不好。但是不管是为什么,原版经常重发没有丢的包,手动调高RTO就会避免。特别是长延迟,高带宽,低丢包的连接(如世界两端的两个服务器之间)。原版指kcp-go
我的fork里其实没有改数据结构,只是在所有queue都是空的时候会暂停每X毫秒的polling,一旦有新的packet则重新开始。这样解决了idle时费电的问题。不知道有没有什么弊端,是不是只要queue都是空的就可以安全的从updater的heap里删除。
显然不是啊,flush里有很多控制的,比如silly window syndrome的处理,需要定时触发。
@nullchinchilla 你说的minrto的问题,可能是我昨天修正的一个bug, flush中的的if else顺序和interval区间的关系,会导致RTO发过的包也被fastack,这样就产生了不必要重传,我昨天调整了下if else顺序。 https://github.com/xtaci/kcp-go/releases/tag/v5.4.9
你调高了RTO,只是把这个问题掩盖了而已。
@xtaci 确实改善了一点点,但是调高RTO还是降低重发。会不会是时钟稍微有点不准导致的?
另外我的Idle机制是连续几秒queue都是空的,并且flush没有改动任何变量时,暂停polling。目前使用着似乎没有问题。
如果queue是空的,那遍历snd_buf也不需要消耗时间啊,那又何必修改polling呢,系统每秒的中断多了去了。
在传输量大的时候,改善很多,我实测应该降低了+40%的不必要的fastack,你需要考虑不同丢包率和抖动的环境,并且丢包率还在变动的情况。 @nullchinchilla
https://github.com/xtaci/kcp-go/releases/tag/v5.4.10 你可以试下这个,看在你的环境下有没有改善,修复了一个潜在的bug。 @nullchinchilla
@xtaci 在安卓系统上,屏幕关了,进入到deep sleep确实就是好多秒才中断一次CPU的频率。手机上的省电机制非常依赖极低的interrupt,而且不断的polling其实也会被系统suspend。实测上如果不去掉polling又费电又有时卡顿。
我会测一侧改动的
嗯,可以试试