首页 > 编程语言 >Rust std fs 比 Python 慢!真的吗!?

Rust std fs 比 Python 慢!真的吗!?

时间:2023-11-30 16:07:10浏览次数:57  
标签:std fs Python +- python read ms test

作者:Xuanwo

Databend Labs 成员,数据库研发工程师

https://github.com/xuanwo

20231129-185943.png

我即将分享一个冗长的故事,从 OpenDALop.read()开始,以一个意想不到的转折结束。这个过程对我来说非常有启发性,我希望你也能感受到。我会尽力重现这个经历,并附上我一路学到的教训。让我们开始吧!

所有的代码片段和脚本都可以在 Xuanwo/when-i-find-rust-is-slow 中找到。

OpenDAL Python 绑定比 Python 慢?

OpenDAL 是一个数据访问层,允许用户以统一的方式从各种存储服务中轻松高效地获取数据。我们通过 pyo3 为 OpenDAL 提供了 python 绑定。

有一天,@beldathas 在 discord 向我报告了一个案例,即 OpenDAL 的 python 绑定比 python 慢:

import pathlib
import timeit

import opendal

root = pathlib.Path(__file__).parent
op = opendal.Operator("fs", root=str(root))
filename = "lorem_ipsum_150mb.txt"

def read_file_with_opendal() -> bytes:
    with op.open(filename, "rb") as fp:
        result = fp.read()
    return result

def read_file_with_normal() -> bytes:
    with open(root / filename, "rb") as fp:
        result = fp.read()
    return result

if __name__ == "__main__":
    print("normal: ", timeit.timeit(read_file_with_normal, number=100))
    print("opendal: ", timeit.timeit(read_file_with_opendal, number=100))

结果显示

(venv) $ python benchmark.py
normal:  4.470868484000675
opendal:  8.993250704006641
    

Emmm, 的假设:

  • Python 是否有内部缓存可以重复使用相同的内存?

  • Python 是否拥有加速文件读取的一些技巧?

  • PyO3 是否引入了额外的开销?

我将代码重构如下:

python-fs-read

with open("/tmp/file", "rb") as fp:
    result = fp.read()
assert len(result) == 64 * 1024 * 1024

python-opendal-read

import opendal

op = opendal.Operator("fs", root=str("/tmp"))

result = op.read("file")
assert len(result) == 64 * 1024 * 1024

结果显示,Python 比 OpenDAL 快得多:

Benchmark 1: python-fs-read/test.py
  Time (mean ± σ):      15.9 ms ±   0.7 ms    [User: 5.6 ms, System: 10.1 ms]
  Range (min … max):    14.9 ms …  21.6 ms    180 runs
  
Benchmark 2: python-opendal-read/test.py
  Time (mean ± σ):      32.9 ms ±   1.3 ms    [User: 6.1 ms, System: 26.6 ms]
  Range (min … max):    31.4 ms …  42.6 ms    85 runs
  
Summary
  python-fs-read/test.py ran
    2.07 ± 0.12 times faster than python-opendal-read/test.py

OpenDAL 的 Python 绑定似乎比 Python 本身运行得更慢,这并不是个好消息。让我们来探究其背后的原因。

OpenDAL Fs 服务比 Python 慢?

这个谜题涉及到许多元素,如 rust、opendal、python、pyo3 等。让我们集中精力尝试找出根本原因。

我在 rust 中通过 opendal fs 服务实现了相同的逻辑:

rust-opendal-fs-read

use std::io::Read;
use opendal::services::Fs;
use opendal::Operator;

fn main() {
    let mut cfg = Fs::default();
    cfg.root("/tmp");
    let op = Operator::new(cfg).unwrap().finish().blocking();

    let mut bs = vec![0; 64 * 1024 * 1024];

    let mut f = op.reader("file").unwrap();
    let mut ts = 0;
    loop {
        let buf = &mut bs[ts..];
        let n = f.read(buf).unwrap();
        let n = n as usize;
        if n == 0 {
            break
        }
        ts += n;
    }

    assert_eq!(ts, 64 * 1024 * 1024);
}

然而,结果显示即使 opendal 是用 rust 实现的,它的速度仍然比 python 慢:

Benchmark 1: rust-opendal-fs-read/target/release/test
  Time (mean ± σ):      23.8 ms ±   2.0 ms    [User: 0.4 ms, System: 23.4 ms]
  Range (min … max):    21.8 ms …  34.6 ms    121 runs
 
Benchmark 2: python-fs-read/test.py
  Time (mean ± σ):      15.6 ms ±   0.8 ms    [User: 5.5 ms, System: 10.0 ms]
  Range (min … max):    14.4 ms …  20.8 ms    166 runs
 
Summary
  python-fs-read/test.py ran
    1.52 ± 0.15 times faster than rust-opendal-fs-read/target/release/test

虽然  rust-opendal-fs-read 的表现略优于 python-opendal-read,这暗示了在绑定和 pyo3 中有改进的空间,但这些并非核心问题。我们需要进一步深入探究。

啊,opendal fs 服务比 python 慢。

Rust std fs 比 Python 慢?

OpenDAL 通过 std::fs 实现文件系统服务。OpenDAL 本身会产生额外的开销吗?

我使用 std::fs 在 Rust 中实现了相同逻辑:

rust-std-fs-read

use std::io::Read;
use std::fs::OpenOptions;

fn main() {
    let mut bs = vec![0; 64 * 1024 * 1024];
    let mut f = OpenOptions::new().read(true).open("/tmp/file").unwrap();
    let mut ts = 0;
    loop {
        let buf = &mut bs[ts..];
        let n = f.read(buf).unwrap();
        let n = n as usize;
        if n == 0 {
            break
        }
        ts += n;
    }

    assert_eq!(ts, 64 * 1024 * 1024);
}

但是:

Benchmark 1: rust-std-fs-read/target/release/test
  Time (mean ± σ):      23.1 ms ±   2.5 ms    [User: 0.3 ms, System: 22.8 ms]
  Range (min … max):    21.0 ms …  37.6 ms    124 runs
 
Benchmark 2: python-fs-read/test.py
  Time (mean ± σ):      15.2 ms ±   1.1 ms    [User: 5.4 ms, System: 9.7 ms]
  Range (min … max):    14.3 ms …  21.4 ms    178 runs

Summary
  python-fs-read/test.py ran
    1.52 ± 0.20 times faster than rust-std-fs-read/target/release/test

哇,Rust 的 std fs 比 Python 还慢?这怎么可能呢?无意冒犯,但是这怎么可能呢?

Rust std fs 比 Python 还慢?真的吗!?

我无法相信这个结果:Rust std fs 的速度竟然比 Python 还要慢。

我尝试学会了如何使用 strace 进行系统调用分析。strace是一个 Linux 系统调用追踪器,它让我们能够监控系统调用并理解其过程。

strace 将包含程序发出的所有系统调用。我们应该关注与/tmp/file 相关的方面。每一行 strace 输出都以系统调用名称开始,后跟输入参数和输出。

比如:

openat(AT_FDCWD, "/tmp/file", O_RDONLY|O_CLOEXEC) = 3

这意味着我们使用参数 AT_FDCWD"/tmp/file"O_RDONLY|O_CLOEXEC调用 openat系统调用。这将返回输出 3 ,这是在后续的系统调用中引用的文件描述符。

好了,我们已经掌握了 strace。让我们开始使用它吧!

rust-std-fs-read 的 strace:

> strace ./rust-std-fs-read/target/release/test
...
mmap(NULL, 67112960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f290dd40000
openat(AT_FDCWD, "/tmp/file", O_RDONLY|O_CLOEXEC) = 3
read(3, "\tP\201A\225\366>\260\270R\365\313\220{E\372\274\6\35\"\353\204\220s\2|7C\205\265\6\263"..., 67108864) = 67108864
read(3, "", 0)                          = 0
close(3)                                = 0
munmap(0x7f290dd40000, 67112960)        = 0
...

python-fs-read 的 strace:

> strace ./python-fs-read/test.py
...
openat(AT_FDCWD, "/tmp/file", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=67108864, ...}, AT_EMPTY_PATH) = 0
ioctl(3, TCGETS, 0x7ffe9f844ac0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
lseek(3, 0, SEEK_CUR)                   = 0
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=67108864, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 67112960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13277ff000
read(3, "\tP\201A\225\366>\260\270R\365\313\220{E\372\274\6\35\"\353\204\220s\2|7C\205\265\6\263"..., 67108865) = 67108864
read(3, "", 1)                          = 0
close(3)                                = 0
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK, sa_restorer=0x7f132be5c710}, {sa_handler=0x7f132c17ac36, sa_mask=[], sa_flags=SA_RESTORER|SA_ONSTACK, sa_restorer=0x7f132be5c710}, 8) = 0
munmap(0x7f13277ff000, 67112960)        = 0
...

从分析strace来看,很明显 python-fs-read 的系统调用比 rust-std-fs-read 多,两者都利用了mmap。那为什么 Python 要比 Rust 更快呢?

标签:std,fs,Python,+-,python,read,ms,test
From: https://blog.51cto.com/u_15491149/8630573

相关文章

  • python开发之个微群聊机器人开发
    请求URL:http://域名地址/inviteChatRomMember请求方式:POST请求头Headers:Content-Type:application/jsonAuthorization:login接口返回参数:参数名必选类型说明wId是string登录实例标识chatRomI是String群userList是String群成员微信id,多个已","分割返回数据:参数名类型说明codestring1......
  • python提取图片中文字
    一.安装tesseract-ocr1.1tesseract-ocr下载下载地址:Indexof/tesseract(uni-mannheim.de)1.2完成tesseract-ocr安装,记住安装路径用于配置环境变量1.3配置环境变量将tesseract-ocr的安装路径添加到环境变量的系统变量(PATH)增加一个TESSDATA_PREFIX变量名,变量值还是安装路......
  • Kubernetes 部署 NFS server
    环境要求kubernetes:1.21+部署NFSserver下载nfsserver部署清单#wgethttps://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/example/nfs-provisioner/nfs-server.yaml添加nsnfsapiVersion:v1kind:Namespacemetadata:name:nfs---......
  • 代码随想训练营第四十四天(Python)| 完全背包、518. 零钱兑换 II 、377. 组合总和 Ⅳ
    [完全背包]有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。1、先遍历物品再遍历背包defall_bag(weight,value,bag_weight):dp=[0]*......
  • python flask下载功能
    前言flask下载功能一、约定要下载文件绝对路径:/tmp/flask_web/download/test.tar.gzpy主程序:/tmp/flask_web/main.py二、main.py内容@app.route("/down/<path:filename>",methods=['GET','POST'])defdownload_file(filename):try:#......
  • Rust std fs 比 Python 慢!真的吗!?
    作者:XuanwoDatabendLabs成员,数据库研发工程师https://github.com/xuanwo我即将分享一个冗长的故事,从OpenDAL的op.read()开始,以一个意想不到的转折结束。这个过程对我来说非常有启发性,我希望你也能感受到。我会尽力重现这个经历,并附上我一路学到的教训。让我们开始吧!......
  • python图像中如何 绘制矩形,编辑文案,保存结果图片等操作
    python版opencv函数学习笔记-cv.rectangle()全参数理解cv2.rectangle(img,pt1,pt2,color,thickness=None,lineType=None,shift=None)以下来自官方文档和自己的理解img:指定一张图片,在这张图片的基础上进行绘制;pt1:矩形的一个顶点;pt2:与pt1在对角线上相对的矩形的顶点;......
  • 【5.0】Python面向对象之组合
    【一】什么是组合在一个类中以另外一个类的对象作为数据属性,称为类的组合。【二】组合的使用组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;而组合则是一种“有”的关系,比如老......
  • 【8.0】Python面向对象之反射
    【一】反射【1】什么是反射反射是一种程序可以访问、检测和修改其本身状态或行为的能力。在Python中,反射主要指通过字符串的形式操作对象的属性。【2】Python中的反射通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)【二】反射方法......
  • 【7.0】Python面向对象之绑定方法与非绑定方法
    【一】绑定方法与非绑定方法介绍【1】绑定方法绑定给谁,谁来调用就自动将它本身当作第一个参数传入(1)绑定到类的方法用classmethod装饰器装饰的方法。为类量身定制类.boud_method(),自动将类当作第一个参数传入(其实对象也可调用,但仍将类当作第一个参数传入)(2)绑定......