我为什么将机器学习主力语言从Python转到Rust

news2025/7/22 7:22:30

我为什么将机器学习主力语言从Python转到Rust

在这里插入图片描述

文章目录

    • 写在前面
    • Python的痛点
      • 猴子补丁(Monkey Patch)
      • 缺乏参数类型校验
      • 允许跨作用域访问
      • 运行缓慢
      • 太多隐含规则
    • Rust之剑
      • 猴子补丁
      • 参数类型
      • 作用域
      • 运行速度
      • 隐含规则
    • 结论

写在前面

首先要声明一下:Python依然是我最喜欢的编程语言,也是我日常使用最多的编程语言。自从10年前我转向人工智能和机器学习领域以来,Python迅速战胜C++和Java,成为我的主力编程语音。用Python编程让我感受到前所未有的“自由”和高效。

另一方面,在机器学习领域,我需要用到大量与机器学习和数据分析相关的包,例如:pyTorch, numpy, pandas, scikit-learn, jupyter…等。这些包极大地简化了机器学习和数据挖掘的开发工作,非常方便好用。

然而,现在我却想从Python转向Rust,因为在大型项目中Python始终显得力不从心。
∗ ∗ ∗ \ast \ast \ast

Python的痛点

猴子补丁(Monkey Patch)

猴子补丁的意思是说动态语言可以在不改动源代码的情况下扩展或修改运行时代码。

Python的这一特性给与我们很大的自由和灵活性,让我们可以通过一些奇技淫巧来让程序跑起来。举个例子:很多代码用到 import json,后来发现 ujson 性能更高,如果觉得把每个文件的 import json 改成 import ujson as json 成本较高,或者只是单纯地想测试一下用 ujson 替换json 是否符合预期,只需要在入口加上:

import json
import ujson
def monkey_patch_json():
  json.__name__ = 'ujson'
  json.dumps = ujson.dumps
  json.loads = ujson.loads
monkey_patch_json()

但是这个特性太灵活,一旦我们在编码过程中不仔细或协作过程中缺乏沟通就很容易带来未知错误,且这种错误往往还很难定位。例如:代码中同一变量赋2个不同类型的值。这是很糟糕的编程习惯,但是随着代码量的增长,项目中几乎不可避免地会出现,更糟糕地是没人记得住所有他们用过的变量是什么类型。当新人接手程序后,他们会疑惑“为什么这里要给变量赋一个不同类型的值”。

缺乏参数类型校验

这点跟上面的猴子补丁类似,都是由语言的动态性带来的。例如,我们有一个函数,将传入的两个整数相加:

def add(num1: int, num2: int) -> int:
    return num1 + num2

但当我们调用它时,Python解释器不会检查参数类型,typing hints形同虚设。我们可以这样调用上面的函数

add("2", 3)

很明显,执行上面的代码会报错。为了让程序更加健壮,我们需要在返回前加入额外的判断逻辑

def add(num1: int, num2: int) -> int:
    if type(num1) != int or type(num2) != int:
        # 参数类型不匹配就抛出错误!
        raise IntNumberError()
	return num1 + num2

现在代码看起来好很多。但是事情远没有结束–由于我们在代码里加入了if分支,这就意味着我们的测试用例也要随之更新,至少要加入2个测试用例,一个num1非整数,另一个num2非整数。这些后续工作经常被忘记,以至于测试覆盖不完整。

允许跨作用域访问

请看下面的代码

for i in range(3):
    pass
print(i)

运行后控制台会输出2。但变量i理应只在for循环作用域下有效。这种作用域错误让代码变得很难维护和debug。

Python的这个设计让我非常不理解,印象中没有任意一门其他语言会这样。在我10年的Python开发生涯中,经常见到有人在if子句中定义变量,然后在if-else子句外使用它。这种写法就让很多Python新手无法理解,增加了代码的阅读和维护成本。

运行缓慢

Python运行慢是公认的。尤其是当项目庞大且复杂时,Python明显比其他主流编程语言要慢。当然我们可以用PyPynumba等工具提升Python程序的执行速度,但是相比起来还是杯水车薪。

太多隐含规则

Python中有很多反直觉的设定。比如下面的代码:

def change(lst, st):
    lst.append(4)
    st = "new string"
    
x = [1, 2, 3]
s = "old string"
print(x, s)
change(x, s)
print(x, s)

我们将一个list和一个string传入函数,但两个参数却有不同的行为。输出结果是:

[1, 2, 3] old string
[1, 2, 3, 4] old string

我们发现list的值变了,而string的值却不变。这是因为Python在传递参数时隐性地传递了list的引用,而string传递的却是拷贝。这就是为什么两个参数在函数中的行为不同。

Python的动态性确实非常灵活且强大,但确实也带来了很多“坑”。这些隐形的“坑”对一个Python新手来说太过隐晦,无形中增加了开发者的心智负担和dubug的成本。为此我专门总结了Python中常见的“坑”,大家可以阅读我写的 《Python避坑指南》 和 《Python避坑指南(续)》

Rust之剑

说完Python我们再来看看Rust。

Rust语言诞生于2010年,是一种多范式、系统级、高级通用编程语言,旨在提高性能和安全性,特别是无畏并发。

Rust从语法和编译器层面帮我们消除了C/C++语言编程中常见的空指针和悬垂指针问题。编译器还会自动帮助我们检查变量生命周期和所有者。在大多数情况下,如果我们的Rust代码能够编译通过,那么我们的代码80%~90%不会存在内存安全问题。

除了内存安全外,Rust还解决了上面提到的Python的5个痛点。

猴子补丁

Rust是一门静态编程语言。这就决定了Rust不能使用猴子补丁。

  • 首先,Rust中的变量都有严格的数据类型,我们不能将不同数据类型的数据赋给同一变量。
  • 其次,变量默认是不可变的,如果我们想定义可变的变量,必须显式的用关键字mut声明。

参数类型

函数参数也跟上面保持一致的原则。Rust是静态语言,传递函数参数时也需要指明参数类型。如果传递的参数类型不匹配,编译器在编译时就会检查出来。这就意味着编译器会帮我们检查潜在的类型错误,我们再也不必写额外的if子句来做类型检查,相应的后续单元测试也可以省了。这让我们的测试可以聚焦于算法和逻辑,而不必为类型检测等细枝末节浪费时间。

作用域

Rust没有GC。当变量离开其作用域时,其生命周期结束,Rust会自动释放它。(这里的解释并不严谨,因为Rust中生命周期和作用域是两个概念,但在大多数代码中两者可以划等号。)

因此,上面Python的示例代码改写成Rust代码的话,在编译时就会报错。

for i in 0..3 {
    println!("{}", i);
}
println!("{}", i); // 编译时这里会报错

运行速度

我在上一篇文章Rust让科学计算速度提升200倍中详细对比了Python、Rust和C在科学计算上的效率。测试结果Rust比Python快200倍!类似性能比较的博文网上还有很多,这里我就不再深入比较了。结论是Rust几乎跟C/C++一样快。这里温馨提示一下,编译Rust程序时千万别忘了加--release,release编译和debug编译出来的程序性能差距很大。

隐含规则

Rust也有很多隐含规则,但是跟Python不同,这些隐含规则都是在编译器层面。相反Rust在语言层面一致性相当高。Rust的编译器非常的强大且对开发者友好,他被设计成开发者的伙伴,专门帮开发者发现潜在错误。

上面提到的Python语言的错误在Rust中绝对不会出现:

  1. 在定义Rust函数时我们就要声明参数的类型。这里的类型不仅指数据类型,还包括传递方式。如果我们想传指针(Rust中叫引用),我们需要在参数前加&
  2. 如果我们想让传递的参数可修改,就必须在显式地加上mut
  3. 如果我们传递给函数的不是引用,那么变量的所有权也会一同传递进函数,这意味着当函数结束,变量就会被丢弃,无法再使用。

总之,Python中的此类问题在Rust中都不存在。我们清晰地知道数据在函数间是如何传递和使用的,程序的一切都在我们的掌控之中。
∗ ∗ ∗ \ast \ast \ast

结论

与Python相比,Rust还年轻。很多库还在开发中,但Rust社区非常活跃并且增长迅猛。很多大厂都是Rust基金会的成员,都在积极地用Rust重构底层基础设施和关键系统应用。

我用Rust重写了马尔可夫链蒙特卡罗(MCMC)方法解结构方程的程序,整个过程让我非常享受。尤其是最后爆发出的性能提升让我深感惊喜。

今天,我依然会用Python或其他语言来完成某些工作,这主要是因为某些库Rust上还没有。不过机器学习常用的库比如numpypandasscikit-learn, pytorch在Rust上都有类似替代的库。可以说目前用Rust可以方便地完成80%Python的功能。后面几章我会专注介绍Python常用机器学习库的Rust替代方案,内容如下表:

Python库Rust替代方案教程
numpyndarrayRust机器学习之ndarray
pandasPolars Rust机器学习之Polars
scikit-learnLinfaRust机器学习之Linfa
pytorchtch-rsRust机器学习之tch-rs
networkspetgraphRust机器学习之petgraph
matplotlibplottersRust机器学习之plotters

敬请大家关注。

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

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

相关文章

S5PV210的启动过程

一、内存 SRAM 静态内存 特点就是容量小、价格高,优点是不需要软件初始化直接上电就能用。DRAM 动态内存 特点就是容量大、价格低,缺点就是上电后不能直接使用,需要软件初始化后才可以使用。 单片机中:内存需求量小,而…

SpringBoot SpringBoot 开发实用篇 6 监控 6.7 自定义端点

SpringBoot 【黑马程序员SpringBoot2全套视频教程,springboot零基础到项目实战(spring boot2完整版)】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇6 监控6.7 自定义端点6.7.1 问题引入6.7.2 自定义端点6.7.3 小结6.7.…

20221125使用PR2023自动识别obs-studio录屏生成的MKV视频的字幕

20221125使用PR2023自动识别obs-studio录屏生成的MKV视频的字幕 2022/11/25 19:07 01 obs.png obs studio (64bit) 02 obs 设置.png 03 obs 输出.png 04 obs默认为MKV.png 05 obs改mkv为MP4.png 警告:如果文件无法完成(例如&…

供应Alkyne-PEG-Biotin,Alk-PEG-Biotin,炔烃-聚乙二醇-生物素

炔烃-聚乙二醇-生物素是一种化学PEG试剂其英文名为Alkyne-PEG-Biotin(Alk-PEG-Biotin),它所属分类为Alkyne PEG Biotin PEG。 peg试剂的分子量均可定制,有:生物素-聚乙二醇5-炔烃、生物素-PEG 20-炔烃 、Biotin-PEG 2…

【kafka】九、kafka消费者分区分配策略

消费者分区分配策略 分区分配策略 一个consumer group中有个多个topic,一个topic有多个partition,所以必然会涉及到partition的分配问题,即确定哪个partition由哪个消费者进行消费。 kafka有两种分配策略,RoundRobin和Range Ro…

JAVA实训第三天

目录 方法引用 示例 接口 类 测试类 Stream ​编辑 Stream 的操作三个步骤 创建 Stream 的 4 种方法 常见Stream接口的继承关系 Stream的中间操作 中间操作常用方法 Stream的终止操作 Stream的终止操作-collect() 示例代码演示 作业 方法引用 在Lamda新特性的支持下&…

电商商家速看 这些TikTok选品玩法你知道多少?

调查报告显示,有3成的商家在TiTok平台上运营电商,谋求TikTok变现增长。在海内外文化习惯、市场环境存在较大差异的情况下,如何 TikTok选品是他们的主要困难。李先生是具有丰富经验的TikTok电商商家,他表示想要实现TikTok变现增长&…

【RuoYi-Vue-Plus】学习笔记 44 - XSS 过滤器以及 @Xss 注解简单分析

文章目录前言参考目录关于 XSS 攻击框架集成配置说明测试方法一:通过过滤器测试方法二:通过 Xss 注解功能调用流程分析XSS 过滤器启动初始化Form 表单请求过滤JSON 对象请求过滤Xss 注解校验前言 之前在对接口进行传参时发现富文本包含的标签全部被过滤…

成功解决 java.lang.NumberFormatException

急于查看问题原因的小伙伴,直接跳到 问题原因 标题。 问题背景: 今天在写条件查询时遇到这么一个错: ### Error querying databasecause: java.lang.NumberFormatException: For input string: "M ### Cause: iava.lang.NumberFormatE…

Ros驱动Ur5e过程 | 手把手教程 | Ros驱动真实机器人Ur5e | Ros与Ur5e建立通讯 | Ubuntu20.04驱动Ur5e机器人

目录 UR5e连接过程 安装ROS 安装moveit 电脑端UR机器人驱动安装 UR实体机器人-软件安装与通信建立 urcap软件安装 电脑端ip问题 需要指定临时ip的情况 不需指定临时ip UR机器人IP 机器人的启动 驱动UR机器人 电脑-ip : 192.168.56.1 ur5e-ip :192.168.5…

【STM32CubeMX】NRF24L01模块实现“1对1“及“1对多“无线通信

大家好,我是小政。本篇文章我将针对NRF24L01模块实现"1对1"及"1对多"无线通信的STM32CubeMX配置过程进行详细的讲解,让准备学习HAL库的小伙伴能够更好的理解STM32CubeMX如何配置。 NRF24L01模块实现"1对1"及"1对多&q…

【OpenCV-Python】教程:3-9 轮廓(5)轮廓层级

OpenCV Python 轮廓层次 【目标】 学习轮廓的层次关系 在前几个课程里面,学习了 cv2.findContours() 函数, 传递了参数 Contour Retrieval Mode . 通常是 cv.RETR_LIST or cv.RETR_TREE 工作的很好,但是他们是什么意思呢? hierarchy 到底是…

基于DMF推荐算法的推荐系统 代码+数据 (可作为毕设)

案例知识点 推荐系统任务描述:通过用户的历史行为(比如浏览记录、购买记录等等)准确的预测出用户未来的行为;好的推荐系统不仅如此,而且能够拓展用户的视野,帮助他们发现可能感兴趣的却不容易发现的item;同时将埋没在长尾中的好商品推荐给可能感兴趣的用户。DMF推荐方法…

C语言既然可以自动为变量分配内存,为什么还要用动态分配内存呢?

一、前言 不知道大家在学习C语言动态分配内存的时候有没有过这样的疑问,既然系统可以自动帮我们分配内存,为什么还需要我们程序员自己去分配内存呢? 如果想要弄清楚这些问题,我们首先就要了解静态内存和动态内存有什么区别&…

python学习笔记

第一个python程序 pycharm使用 pycharm下载链接: https://www.jetbrains.com/zh-cn/pycharm/download/#sectionwindows 1. 新建项目 右键test—> 点击new 输入文件名 运行 结果 2. 调节字体大小快捷键设置 右键添加鼠标快捷键 按住ctrl鼠标向上滑动 放…

计算机网络 应用层

概述 为特定应用程序提供数据服务,例如HTTP、FTP(File Transfer Protocol , 文件传输协议)、DNS (Domain Name System,域名系统)等。 域名系统 DNS DNS 全名叫 Domain Name Server,中文俗称“域名服务器” 在 Internet 上域名…

Docker搭建Nginx实现SpringBoot+Nginx集群

1,首先聊聊什么Nginx? Nginx 是一个高性能的HTTP和反向代理web服务器,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。 Nginx是一款轻量级的Web 服务器/反向代理服务器及电…

linux三剑客awk、sed、grep与cut的总结

在Linux中,一切皆文件,对Linux的操作就是对文件的处理。对文件操作处理最重要的三个命令是grep、sed、awk,它们在业界被称为“三剑客”。 三剑客的功能非常强大,但它们各自有分别擅长的功能: grep擅长对文件或字符串进…

【操作系统】2.3 进程同步与互斥

这一节大概是操作系统中最难的一节了。 2.3.1 进程的同步与互斥 2.3.1 进程的同步与互斥_StudyWinter的博客-CSDN博客_进程同步思维导图 进程同步:在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之…

C#界面里Form.Language 属性的使用

C#界面里Form.Language 属性的使用 现在面向全球化的应用软件、应用系统越来越多。 比如游戏正在走向全球化时代,很多游戏的服务器也会安装到各个国家去,也会请当地人来管理游戏的服务器。 这时开发的软件,就需要面向各种语言,比如英语、日语、阿拉佰语等等。 如果你正在…