Rust与Go的对比

在各个领域,Rust 都已经成为一流的语言。最近,我们通过将服务的实现从 Go 切换到 Rust,极大地提升了该服务的性能。这里我阐述了重新实现服务为何是有价值的、该过程是如何实现的以及由此带来的性能提升。

Read States 服务

我们从 Go 切换到 Rust 的服务叫做“Read States”服务。它的唯一目的是跟踪用户阅读了哪些频道和信息。每当用户连接的时候,每当消息发送的时候,每当消息被读取的时候,都会访问 Read States。简而言之,Read States 处于最关键的位置。我们希望能够保证服务器始终让人感觉快捷无比,所以必须要确保 Read States 是非常快速的。

在 Go 的实现中,Read States 无法支持产品的需求。在大多数情况下,它都是很快速的,但是每几分钟我们就会看到很大的延迟峰值,这对于用户体验来说是很糟糕的。经过调查,我们确定峰值是由 Go 的核心特性引起的,也就是其内存模型和垃圾收集器(GC)。

为何 Go 无法满足我们的性能目标

为了阐述 Go 为什么无法满足我们的需求,我们首先需要讨论数据结构、规模、访问模式以及服务架构。

我们用来存储读取状态信息的数据结构被简便地称为“Read State”。服务器 有数十亿的 Read State。每个用户(User)的每个频道(Channel)都有一个 Read State。每个 Read State 都有多个计数器需要自动更新,并且经常会被重置为零。例如,其中有个计数器用来记录你某个频道中被提及了多少次。

为了快速获取原子计数器的更新,在每个 Read State 服务器中都保存了一个 Read State 的最近最少使用(LRU,Least Recently Used)的缓存。每个缓存中都有数百万的用户,每个缓存中又会有数千万的 Read State。每秒钟会有成千上万的缓存更新。

对于持久化来讲,我们使用 Cassandra 数据库集群作为缓存的支撑。在缓存键清除(eviction)的时候,我们会将 Read State 提交到数据库。每当 Read State 更新的时候,我们会将数据库提交调度到未来的 30 秒。每秒钟会有成千上万的数据库写入操作。

在下图中,我们可以看到 Go 服务的峰值采样时间帧的响应时间和 CPU(图表数据基于 Go 1.9.2。我们尝试了版本 1.8、1.9 和 1.10 版本,但没有任何改善。从 Go 到 Rust 的第一次切换是在 2019 年 5 月完成。)。正如我们所看到的,基本每两分钟就会出现延迟和 CPU 峰值。

为何每两分钟会出现峰值?

在 Go 中,当缓存键清除时,内存不会立即释放。相反,垃圾收集器每隔一定的时间就会运行一次,以便于查找不再被引用的内存并释放它。换句话说,Go 并不是在内存用完后立即释放,内存会挂起一段时间,直到垃圾收集器确定它真的是不再需要了。在垃圾收集的时候,Go 必须要做大量的工作来确认哪些内存是空闲的,这可能会降低程序的运行速度。

这些峰值看起来确实是垃圾收集器对性能的影响,但是我们所编写的 Go 代码已经非常高效了,内存分配很少。我们并没有制造太多的垃圾。

在深入研究了 Go 的源码之后,我们了解到至少每两分钟,Go 将强制运行一次垃圾收集。换句话说,如果垃圾收集器已经有两分钟没有运行了,不管堆增加了多少,Go 依然会强制运行垃圾收集。

我们认为可以优化垃圾收集器,使其运行地更加频繁,从而防止出现较大的峰值,因此我们在服务中实现了一个端点,在运行时修改垃圾收集器的 GC 百分比。令人遗憾的是,无论我们如何配置 GC 百分比,都不会发生任何变化。为什么会这样呢?事实证明,这是因为我们分配内存的速度不够快,从而导致无法强制垃圾收集频繁进行。

我们继续深入研究,发现出现如此大的峰值并不是因为有大量待释放的内存,而是因为垃圾收集器要扫描整个LRU 缓存,以便于确定内存是否完全没有被引用。鉴于此,我们认为更小的 LRU 缓存会更快,因为垃圾收集器要扫描的内容会更少。所以,我们在服务上添加了另外一项配置,允许修改 LRU 缓存的大小,并修改了架构,让每台服务器上能有许多的 LRU 缓存分区。

我们是正确的。LRU 缓存越小,垃圾回收的峰值越小。

但是,缩小 LRU 缓存的代价就是第 99 个百分位延迟时间的增长。这是因为,如果缓存比较小的话,用户的 Read State 在缓存中的几率就会降低。如果它不在缓存中,那么我们就需要进行数据库加载。

对不同的缓存容量进行了大量的负载测试之后,我们发现了一个看起来还不错的设置。虽然这不能让人完全满意,但是也是可以接受的,而且当时还有更重要的事情要做,所以我们让服务就这样运行了很长一段时间。

在那段时间里,我们看到 Rust 在其他地方越来越成功,于是我们一致决定要完全基于 Rust 创建用于构建新服务所需的框架和库。这个服务是移植到 Rust 的最佳候选,因为它很小而且是自包含的,但是我们也希望 Rust 能够修复这些延迟峰值的问题。所以,我们接受了将 Read States 移植到 Rust 的任务,希望 Rust 是一门合格的服务语言并且提升用户体验(澄清一下,我认为,你们并不应该为了要使用 Rust,就将所有的服务使用 Rust 重写一遍)。

Rust 中的内存管理

Rust 非常快并且节省内存:它没有运行时和垃圾收集器,能够支撑性能关键型的服务、可以运行在嵌入式设备中并且能够很容易地与其他语言集成(引自 Rust 官网)。

Rust 没有垃圾收集,所以我们认为它不会有与 Go 相同的延迟峰值问题。

Rust 使用了一种比较独特的内存管理方法,其中包含了内存“所有权”的概念。简而言之,Rust 会跟踪谁能够读写内存。它知道程序什么时候使用内存,并在不再需要内存的时候立即释放它。它在编译时强制执行内存规则,这样它根本不可能出现运行时内存错误(当然,除非你使用 unsafe)。我们不需要手动跟踪内存,编译器会处理它。

因此,在 Read States 服务的 Rust 版本中,当用户的 Read State 从 LRU 缓存中清除时,它会立即从内存中释放。Read State 内存不会等待垃圾收集器来收集它。Rust 知道它不会再使用了,并立即释放它。在 Rust 中并没有运行时进程来确定是否应该释放它。

异步的 Rust

但是,Rust 生态系统有一个问题。在这个服务重新实现的时候,Rust 稳定版并没有很好的异步 Rust 功能。但是对于网络服务来说,异步编程是必需的。有一些社区库支持异步 Rust,但是它们需要大量的样板式处理,而且错误消息非常模糊不清。

幸运的是,Rust 团队正在努力使异步编程变得更加简单,并且该功能可以在 Rust 不稳定的 nightly 版本中使用。

我从来都不惧怕接受那些看起来很有前途的新技术。例如,我们是 Elixir、React、React Native 和 Scylla 的早期采用者。如果某项技术很有前途,并能够给我们带来好处,我们不介意处理其固有的困难和不稳定性。这也是我们在不到 50 名工程师的情况下能够快速达到 2.5 亿用户的方法之一。

接受 Rust nightly 版本的异步特性就是我们愿意拥抱新的、有前途的技术的另外一个佐证。作为一个工程团队,我们认为值得使用 Rust nightly 版本,并承诺为 nightly 版本做出提交贡献直到异步功能在稳定环境下得到完全支持。我们一起处理出现的各种问题,此后 Rust 稳定版支持了异步 Rust(参见该网址)。终于苦尽甘来。

实现、负载测试和发布

实际的重写相当简单。首先,我们有一个大致的转换,然后我们把它进行有意义的优化。例如,Rust 有一个很好的类型系统,对泛型提供了广泛的支持,因此我们可以抛弃那些仅仅因为缺少泛型而存在的 Go 代码。另外,Rust 的内存模型能够推断出线程之间的内存安全性,因此我们能够抛弃 Go 中所需要的跨 goroutine 的内存保护。

刚开始进行负载测试时,我们马上就对结果感到非常满意。Rust 版本的延迟和 Go 版本一样好,而且没有延迟峰值!

值得注意的是,在编写 Rust 版本时,我们只对性能优化进行了非常基本的思考。即使只是基本的优化,Rust 也能够超越手动调优的 Go 版本。这深切证明了相对于深入研究 Go,使用 Rust 编写高效的程序有多么的容易。

但我们并不满足于简单地匹配 Go 的性能。经过一些性能分析和性能优化之后,我们能够在每个性能指标上击败 Go。在 Rust 版本中,延迟、CPU 和内存指标都更好。

Rust 版本中的性能优化包括:

在 LRU 缓存中,更改为使用 BTreeMap 取代 HashMap 以优化内存占用。将最初的指标库替换为使用现代 Rust 并发功能的指标库。减少我们正在执行的内存副本的数量。对此感到满意之后,我们决定推出这项服务。

由于我们进行了负载测试,所以发布过程相当顺利。我们把它放到一个金丝雀部署的节点上,查找到一些缺失的边缘情况,并修复了它们。不久之后,我们就把它推广到整个环境之中。

以下是测试的结果,Go 是紫色的线,Rust 是蓝色的线。

提高缓存的容量

在服务成功运行了几天之后,我们决定重新提高 LRU 的缓存容量。如上所述,在 Go 版本中,提高 LRU 缓存上限会导致更长的垃圾收集时间。现在,我们不再需要处理垃圾收集,因此我们认为可以提高缓存的上限并能够获得更好的性能。我们增加了内存容量,优化了数据结构以使用更少的内存 (仅仅为了好玩),并将缓存容量增加到 800 万条 Read States。

下面的结果不言自明。注意,现在平均时间以微秒计算,获取提及数的最大耗时以毫秒计算。

生态系统的演化

最后,Rust 的另一个好处是它有一个快速演化的生态系统。最近,tokio(我们使用的异步运行时) 发布了 0.2 版。我们进行了升级,它免费带来了 CPU 方面的优化。下面你可以看到 CPU 在 16 号左右开始就一直很低。

最后的思考

现在,我在其软件栈的许多地方都在使用 Rust。我们将它用于游戏 SDK、Go Live 的视频捕获和编码、Elixir NIFs 以及其他几个后端服务等等。

当开始一个新项目或软件组件时,我们都会考虑使用 Rust。当然,我们只在有意义的地方使用它。

除了性能之外,Rust 对于工程团队还有许多好处。例如,如果产品需求发生了变化,或者发现了关于该语言的新知识,Rust 的类型安全性和借用检查器(borrow checker )使代码重构变得非常容易。除此之外,Rust 的生态系统和工具都是非常优秀的,它们背后有强大的驱动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/801667.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

01 Php学习:导学篇

Php是什么? PHP 是服务器端脚本语言。 PHP(Hypertext Preprocessor)是一种通用开源脚本语言,主要用于服务器端开发。PHP脚本在服务器端执行,生成动态网页内容或执行服务器端任务。PHP可以嵌入到HTML中,也…

千视携 NDI 6 轻量化媒体方案亮相北京CCBN展会

展会简介 第30届中国国际广播电视网络技术展览会(CCBN)将于4月24至26日在北京首钢会展中心举行。此次展会将汇集全球各大数字媒体、广播电视单位以及IT、通信技术厂商。展会重点关注数字化转型、智能媒体、融媒体等主题,并展示最新的5G、4K/8…

【运输层】TCP 的可靠传输是如何实现的?

目录 1、发送和接收窗口(滑动窗口) (1)滑动窗口的工作流程 (2)滑动窗口和缓存的关系 (3)滑动窗口的注意事项 2、如何选择超时重传时间 (1)加权平均往返…

5.网络编程-socker(golang版)

目录 一、什么是socket? 二、Golang中使用TCP TCP服务端 TCP客户端​​​​​​​ 三、TCP黏包,拆包 1.什么是粘包,拆包? 2.为什么UDP没有粘包,拆包? 3.粘包拆包发生场景 4.TCP黏包 黏包服务端 …

解决 IDEA每次打开新的项目都要重新设置maven问题

目录 一、当前项目设置maven 如下图: 二、设置打开的新项目的maven 如下图:​ 一、当前项目设置maven 对于当前项目我们都知道设置maven的配置要在 File -- Settings -- Build -- Maven 中设置 如下图: 二、设置打开的新项目的maven F…

整理的微信小程序日历(单选/多选/筛选)

一、日历横向多选&#xff0c;支持单日、双日、三日、工作日等选择 效果图 wxml文件 <view class"calendar"><view class"section"><view class"title flex-box"><button bindtap"past">上一页</button&…

stm32与esp8266WIFI模块

硬件介绍 WIFI模块ESP-01S 使用AT指令控制1-ESP8266-AT指令初试化及部分基础知识_ch_pd-CSDN博客 项目需求 通过ESP-01SWIFI模块控制LED状态模拟插座 串口1用于与ESP8266通讯&#xff0c;串口2连接PC&#xff0c;用于打印log&#xff0c;查看系统状态 项目接线 将WIFI模块的…

智慧农场物联网系统:重塑农业的未来

随着科技的进步&#xff0c;物联网技术正在逐渐改变我们的生活。在农业领域&#xff0c;物联网系统也正在发挥着越来越重要的作用&#xff0c;为智慧农场的发展提供了新的可能。本文将深入探讨智慧农场物联网系统的优势、应用场景、技术实现以及未来发展趋势。 一、智慧农场物…

ATAM方法架构评估实践

用ATAM方法评估软件体系结构&#xff0c;其工作分为4个基本阶段&#xff0c;即演示、调查和分析、测试和报告ATAM&#xff08;如图1所示&#xff09;。接下来分别就每个阶段的实践进行详细介绍。 图1 ATAM方法的评估实践阶段划分 1.阶段1——演示&#xff08;Presentation&…

转化延迟预估

转化数据延迟久&#xff0c;但实时反馈至关重要涉及到模型预估准度&#xff0c;进而影响客户成本。 现状&#xff1a;超过12h的转化被视为负例&#xff0c;12h以内的为正例&#xff0c;这样会导致模型低估。公示如下&#xff1a; P ( 转化 ∣ 点击 ) 延迟 ≤ 12 h 的转化 未转…

什么是人工智能?人工智能、机器学习、深度学习三者之间有什么关系吗?

深度学习是机器学习的一个分支。深度学习是机器学习的一部分&#xff0c;与机器学习的其他分支学科&#xff0c;以及统计学、人工智能等学科都有着紧密的联系。深度学习、机器学习、人工智能、统计学之间的关系如图1-4所示。 图1-4 深度学习、机器学习、人工智能、统计学之间的…

【C语言】指针篇(指针数组,数组指针,函数指针,一级、二级指针)

文章目录 一、指针基础1.什么是指针2.指针的定义和初始化3.指针的解引用4.野指针和空指针5.指针的类型6.指针的大小7.指针的运算8.指针和数组9.指针和字符串10.二级指针 二、指针数组和数组指针1.指针数组2.数组指针3.练习 三、数组传参和指针传参1.一维数组传参2.二维数组传参…

Maven POM元素解析

这是对Maven中使用的Maven项目描述符的引用。 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/…

GEE:基于CHIRPS数据集的累积降水量影像下载

作者:CSDN @ _养乐多_ 本文将介绍在 Google Earth Engine(GEE)平台上使用“UCSB-CHG/CHIRPS/DAILY”数据集计算某一段时期(某年/某个季节/某月)的累积降雨量图像,并下载。 结果如下图所示, 文章目录 一、核心函数二、代码链接三、完整代码一、核心函数 .sum() // 对影…

02 Php学习:变量

Php 变量声明 Php 变量赋值 在PHP中&#xff0c;变量赋值是指将一个值赋给一个变量。变量赋值是 PHP 中最基本和常见的操作之一&#xff0c;以下是关于变量赋值的详细说明和示例&#xff1a; 变量赋值语法&#xff1a; $variable value;$variable&#xff1a;要赋值的变量名…

【MATLAB源码-第38期】基于OFDM的块状导频和梳状导频误码率性能对比,以及LS/LMMSE两种信道估计方法以及不同调制方式对比。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 块状导频和梳状导频都是用于无线通信系统中信道估计的方法。 块状导频&#xff1a; 定义&#xff1a; 在频域上&#xff0c;块状导频是连续放置的一组导频符号。这意味着所有的导频符号都集中在一个短的时间段内发送。 优点…

php站长在线工具箱源码优化版

环境要求 PHP > 7.4MySQL > 5.6fileinfo扩展使用Redis缓存需安装Redis扩展 源码下载地址&#xff1a;php站长在线工具箱源码优化版.zip

RISC-V特权架构 - 模式切换与委托

RISC-V特权架构 - 模式切换与委托 1 导致模式切换的常见动作2 异常处理规则3 异常处理时模式切换3.1 在U模式下&#xff0c;发生异常3.2 在S模式下&#xff0c;发生异常3.3 在M模式下&#xff0c;发生异常 4 系统调用时模式切换5 中断处理时模式切换 本文属于《 RISC-V指令集基…

PaddleVideo:onnx模型导出

本文节介绍 PP-TSM 模型如何转化为 ONNX 模型&#xff0c;并基于 ONNX 引擎预测。 1&#xff1a;环境准备 安装 Paddle2ONNX python -m pip install paddle2onnx 安装 ONNXRuntime # 建议安装 1.9.0 版本&#xff0c;可根据环境更换版本号 python -m pip install onnxrunti…

GEE:样本点的样式设置

作者:CSDN @ _养乐多_ 本文将介绍在Google Earth Engine (GEE)平台上为样本点设置样式的方法和代码,样本点可以设置成任何颜色,以及7种形状,以便更直观了解数据的分布和特征。 文章目录 一、统一设置样式1.1 示例代码1.2 示例代码链接二、每一类一个样式2.1 示例代码2.2…