低级别I/O基准测试。C, C , Rust, Golang, Java, Python

比较不同语言如何处理网络I/O,检查Rust是否保持其高性能的承诺

Eugene Retunsky 6分钟阅读

这篇文章是《衡量网络服务性能》的延续。

当我的电脑没有互联网连接时,我发现我可以用它做的事情不多。事实上,我们大多使用笔记本电脑和智能手机来访问存储或产生于其他地方的信息。甚至很难想象,如果没有网络通信,非面向用户的应用程序的效用。虽然I/O操作与数据处理的比例可能不同,但这种操作对服务的延迟的贡献很可能是实实在在的。

有许多编程语言用于实现后端服务。正因为如此,人们对比较这些语言的性能有着天然的兴趣,而且有各种基准。例如,有Benchmarks Game,它比较了不同语言解决不同的离线任务。另一套基准,TechEmpower,测量网络框架的性能。这些测量是很有用的,可以对语言的性能能力有一个大致的了解。然而,它们通常是测量一些特定的用例和操作集,这不一定具有代表性。

所有这些都让我对不同平台上裸露的I/O的不可减少的成本感到好奇。对TCP代理的基准测试可能是最简单的情况。没有数据处理,只是处理传入/传出连接和转发原始字节数据。微服务几乎不可能比TCP代理更快,因为它不能做得更少。它只能做得更多。任何其他功能都是建立在这个基础上的--解析、验证、遍历、打包、计算等等。

目前正在比较以下解决方案。

  • HAProxy-在TCP-代理模式下。与一个用C语言编写的成熟解决方案进行比较:http://www.haproxy.org/
  • draft-http-tunnel - 一个简单的C++解决方案,具有非常基本的功能(trantor)(在TCP模式下运行):https://github.com/cmello/draft-http-tunnel/(感谢Cesar Mello,他的编码使这个基准成为可能)。
  • http-tunnel - 一个用Rust(tokio)编写的简单的HTTP-tunnel/TCP-proxy(在TCP模式下运行):https://github.com/xnuter/http-tunnel/(你可以在这里阅读更多关于它的信息)。
  • tcp-proxy - 一个Golang解决方案:https://github.com/jpillora/go-tcp-proxy
  • NetCrusher - 一个Java解决方案(Java NIO)。以JDK 11为基准,使用G1: https://github.com/NetCrusherOrg/NetCrusher-java/
  • pproxy - 一个基于asyncio的Python解决方案(在TCP代理模式下运行):https://pypi.org/project/pproxy/

上述所有的解决方案都使用非阻塞式I/O。在我之前的文章中,我试图说服读者,如果你需要低延迟和大吞吐量的高可用服务,这是处理网络通信的最佳方式。

简单说明一下--我试图在Golang、Java和Python中挑选最好的解决方案,但如果你知道有更好的替代品,请随时联系我。

实际的后端是Nginx,它被配置为在HTTP模式下提供10kb的数据。

基准结果被分成两组。

  • 基线,C,C++,Rust--高性能语言。
  • Rust, Golang, Java, Python - 内存安全的语言。

是的,Rust属于两个世界。

方法的简要描述

  • 两个核心分配给TCP代理(使用cpuset)。
  • 两个核心分配给后端(Nginx)。
  • 请求率从10k开始,逐步提高到每秒25k个请求(rps)。
  • 连接被重复使用50个请求(每个请求10kb)。
  • 基准测试在同一个虚拟机上运行,以避免任何网络噪音。
  • 虚拟机实例类型是计算优化的(独家拥有所有分配的CPU),以避免 '嘈杂的邻居 '问题。
  • 延迟测量的分辨率为微秒(µs)。

我们将比较以下统计数据。

  • 百分比(从p50到p99)--关键统计数据。
  • 异常值(p99.9和p99.99)--对大型分布式系统的组件至关重要(了解更多)。
  • 最大的延迟--最坏的情况也不应该被忽视。
  • 修剪 后的平均值tm99.9--不含最佳/最差0.1%的平均值,以掌握中心趋势(不含离群值)。
  • 标准偏差 - 评估延迟的稳定性。

请随时了解更多关于方法和为什么选择这些统计数字的信息。为了收集数据,我使用了perf-gauge。

好吧,让我们来谈谈结果吧!

比较高性能语言。C、C++、Rust

我经常听说Rust在性能方面与C/C++不相上下。让我们来看看在处理网络I/O方面,'不相上下 '到底意味着什么。

请看下面的四张图,顺序如下。基线、C、C++、Rust。

绿色-p50,黄色-p90,蓝色-p99,单位为µs(在左边)。橙色-速率(在右边

下面,你会看到每个统计数字在后台的基础上增加了多少微秒。下面的数字是在最大请求率下的时间间隔的平均值(不包括爬升的时间)。

开销,µs

相对而言(管理费占基线的百分比)。

间接费用占基线统计的%。

有趣的是,虽然用C++编写的代理在p99.9级别上比HAProxy和Rust都要快一点,但在p99.99和最大级别上却更差。然而,这可能是一个实现的属性,在这里是非常基本的(通过回调实现,而不是处理期货)。

此外,还测量了CPU和内存消耗。你可以在这里看到这些。

总之,用C、C++和Rust编写的所有三个TCP代理都表现出类似的性能:精简和稳定。

比较内存安全的语言。Rust, Golang, Java, Python

现在,我们来比较一下内存安全的语言。不幸的是,Java和Python的解决方案无法在仅有的两个核心上处理25,000 rps,因此Java以15,000 rps为基准,Python以10,000 rps为基准进行测试。

请看下面四张图,顺序如下。Rust, Golang, Java, Python。

绿色-p50,黄色-p90,蓝色-p99,单位为µs(在左边)。橙色-速率(在右边

那么,现在我们看到了一个巨大的差异。在以前的图表中,在新的比例中,Rust似乎很稳定。另外,注意到Java的冷启动峰值。下面的数字是在最大请求率下的间隔的平均值(不包括上升期)。

开销,µs

正如你所看到的,Golang在p50/p90水平上有一定的可比性。然而,在更高的百分位数上,差异会急剧增长,这一点从标准差中可以看出。但是,看看Java的数字吧!

值得一看的是离群(p99.9和p99.99)的百分位数。很容易看出,与Rust的差异是巨大的。

绿色--p99.9,蓝色--p99.99,单位是μs(在左边)。橙色 - 速率(在右边

相对而言(与基线Nginx的百分比)。

间接费用占基线统计的%。

总之,Rust的延迟差异比Golang、Python,尤其是Java低得多。Golang在p50/p90延迟水平上与Rust相当。

最大吞吐量

另一个有趣的问题--每个代理能够处理的最大请求率是多少?同样,你可以阅读完整的基准以获得更多信息,但现在,这里是一个简短的总结。

Nginx能够处理60,000rps多一点的数据。如果我们在客户端和后端之间放一个TCP代理,会降低吞吐量。正如你在下面看到的,C、C++、Rust和Golang达到了Nginx直接服务的70%-80%左右,而Java和Python表现更差。

左轴:延时开销。右轴:吞吐量

  • 蓝线是尾部延迟(Y轴在左边)--越低越好。
  • 灰色条形图是吞吐量(Y轴在右边)--越高越好。

总结

这些基准并不全面,目的是为了比较不同语言的裸露I/O。

然而,与Benchmarks Game和TechEmpower一起,他们表明,如果可预测的性能对你的服务至关重要,那么Rust可能是Golang、Java或Python的更好选择。另外,在开始用C或C++编写一个新的服务之前,值得考虑Rust。因为它的性能和C/C++一样,而且在此基础上。

  • 内存安全
  • 无竞争的数据
  • 易于编写富有表现力的代码。
  • 在许多方面,它和Python一样易于使用和灵活。
  • 它迫使工程师们在生产中启动之前制定一个前期设计和对数据所有权的理解。
  • 它有一个奇妙的社区和一个庞大的组件库。
  • 那些尝试过的人,都很喜欢它!
(0)

相关推荐