cli3优化(使用 Rust 提高 Python S3 客户端性能)

cli3优化(使用 Rust 提高 Python S3 客户端性能)

admin 2025-10-14 社会资讯 14 次浏览 0个评论

更换 Boto3 以获得乐趣和利润

Python 因其易用性和性能而成为事实上的数据科学语言。但是性能的提高只是因为NumPy之类的库将矩阵乘法等计算量大的函数卸载到了优化的 C 代码中。数据科学工具和工作流程不断改进,数据集变得更大,GPU 变得更快。因此,随着 S3 等对象存储系统成为大型数据集的标准,从对象存储中检索数据已成为瓶颈。

缓慢的 S3 访问导致计算空闲,浪费昂贵的 CPU 和 GPU 资源。S3 中几乎所有基于 Python 的数据使用都利用了Boto3 库,这是一个支持灵活性但具有 Python 性能限制的 SDK。由于全局解释器锁(GIL) ,本机 Python 执行速度相对较慢,并且在利用多核方面尤其差。

还有其他项目,例如PyTorch 插件或通过PyArrow绑定利用 Apache Arrow ,旨在提高特定 python 应用程序的 S3 性能。我之前也写过关于 Python 中 S3 性能问题的文章:cli 工具速度、对象列表、pandas 数据加载和元数据请求。

这篇博客指出了解决 Python S3 性能问题的一个有希望的方向:用现代编译语言编写的等效功能替换 Boto3。我简单的 Rust 重新实现FastS3与 Boto3 相比,在大型对象检索和对象列表方面的性能提高了 2 到 3 倍。令人惊讶的是,这个结果对于像FlashBlade这样的快速全闪存对象存储以及像 AWS 的 S3 这样的传统对象存储都是一致的。

实验结果

Python 应用程序主要通过以下两种方式访问​对象存储数据:1) 对象存储特定的 SDK,如 Boto3;或 2) 文件系统兼容的包装器,如 s3fs 和 fsspec。Boto3 和 s3fs 都将与我基于 Rust 的最小 FastS3 代码进行比较,以 1)检索对象和 2)列出键。

S3fs是一个常用的围绕 Boto3 库的 Python 包装器,它提供了一个更像文件系统的接口来访问 S3 上的对象。开发人员受益,因为基于文件的 Python 代码可以适用于几乎不需要重写或无需重写的对象。Fsspec提供了一个更通用的接口,它为许多不同类型的后端存储提供了类似的类似文件系统的 API。我的 FastS3 库应该被视为迈向 fsspec 投诉替代基于 Python 的 s3fs 的第一步。

在 Boto3 中,有两种检索对象的方法:get_object和download_fileobj。Get_object 更容易使用,但对于大型对象来说速度较慢,并且 download_fileobj 是一种托管传输服务,如果对象大于配置的阈值,则使用并行范围 GET 。我的 FastS3 库反映了这个逻辑,在 Rust 中重新实现。S3fs 允许使用类似于标准 Python 文件打开和读取的模式从对象读取。

测试集中在两个常见的性能痛点:检索大对象和列出键。还有其他工作负载尚未实现或优化,例如小对象和上传。

所有测试均在具有 16 核和 64GB DRAM 的虚拟机上运行,​​并针对小型 FlashBlade 或 AWS S3 运行。

结果 1 — 获取大对象

第一个实验使用 FastS3、s3fs 和 Boto3 代码路径测量大型对象的检索 (GET) 时间。目标是尽可能快地将对象从 FlashBlade S3 检索到 Python 内存中。所有四个函数都随着对象大小的增加而线性扩展,基于 Rust 的 FastS3 分别比 sf3s-read/boto3-get 和 boto3-download 快 3 倍和 2 倍。

使用 Rust 提高 Python S3 客户端性能

从 128MB 到 4GB 的对象大小,FastS3 的相对加速是一致的。

结果 2 — FlashBlade 与 AWS 上的 GET

之前的结果侧重于针对高性能全闪存 FlashBlade 系统的检索性能。我还使用传统的对象存储和 AWS 的 S3 重复了这些实验,并发现了类似的性能提升。下图显示了 FastS3 和 Boto3 download() 的相对性能,值小于 1.0 表示 Boto3 比 FastS3 快。

对于大于 1-2 GB 的对象,基于 Rust 的 FastS3 后端在检索数据方面始终比 Boto3 的 download_fileobj 函数快 2 倍,无论是 FlashBlade 还是 AWS。回想一下,对于大型对象,download_fileobj 比基本的 Boto3 get_object 函数要快得多。因此,FastS3 至少比 Boto3 的 get_object 快 3 倍。该图将 FastS3 与 download_fileobj 进行了比较,因为它是 Boto3 最快的选项,但使用起来也最不方便。

使用 Rust 提高 Python S3 客户端性能

对于小于 128–256MB 的对象,FastS3 调用比 Boto3 慢,这表明我的 FastS3 代码中仍然缺少优化。FastS3 目前使用 128MB 作为下载块大小来控制并行性,这对大对象最有效,但对于较小的对象显然不理想。

结果 3 — 列出对象

元数据列表的性能通常是一个缓慢的 S3 操作。下一个测试比较了 ls() 的基于 Rust 的实现,即基于前缀和带前缀的分隔符列出键,与 Boto3 的 list_objects_v2() 和 s3fs 的 ls() 操作。目标是枚举具有给定前缀的 400k 对象。

令人惊讶的是,FastS3 在列出对象方面比 Boto3 快得多,尽管 FastS3 无法利用并发性。FastS3 列表在 FlashBlade 上比 Boto3 快 4.5 倍,在 AWS S3 上快 2.7 倍。

使用 Rust 提高 Python S3 客户端性能

与直接使用 boto3 list_objects_v2 相比,ls() 的 s3fs 实现还引入了 4-8% 的轻微开销。

代码演练

FastS3 的所有代码都可以在github上找到,包括 Rust 实现和 Python 基准程序。

我利用Pyo3 库在我的 Rust 函数和 Python 之间创建绑定。我还使用官方AWS SDK for Rust,在撰写本文时,它仍处于 0.9.0 版的技术预览版中。Rust 代码使用Tokio 运行时向 S3 发出并发请求。

使用maturin构建 Rust-FastS3 库,它将 Rust 代码和 pyo3 绑定打包到 Python 轮中。

成熟构建--发布

生成的轮子可以像任何 Python 轮子一样安装。

python3 -m pip install fasts3/target/wheels/*.whl

Boto3 和 FastS3 的初始化逻辑同样简单,仅使用 endpoint_url 来指定 FlashBlade 数据 VIP 或 AWS 的空字符串。访问密钥凭证由 SDK 自动找到,例如作为环境变量或凭证文件。

导入 boto3导入 fasts3s3r = boto3.resource('s3', endpoint_url=ENDPOINT_URL) # boto3 s = fasts3.FastS3FileSystem(endpoint=ENDPOINT_URL) # fasts3 (rust)

然后 FastS3 在某些情况下使用起来更加简单。

# boto3 download_fileobj() bytes_buffer = io.BytesIO() s3r.meta.client.download_fileobj(Bucket=BUCKET, Key=SMALL_OBJECT, Fileobj=bytes_buffer)# fasts3 get_objects内容 = s.get_objects([BUCKETPATH])

FastS3 要求将对象路径指定为“bucketname/key”,它映射到 s3fs 和 fsspec API,并将对象存储视为更通用的类似文件的后端。

该库的 Rust 代码可以在单个文件中找到;我是 Rust 的新手,所以这段代码不是“写得很好”或惯用的 Rust,只是示范性的。为了理解 Rust 代码的流程,有三个函数充当 Python 和 Rust 之间的互连:new()、ls() 和 get_objects()。

pub fn new(端点:字符串)-> FastS3FileSystem

此函数是一个简单的工厂函数,用于使用应指向对象存储端点的端点参数创建 FastS3 对象。

pub fn ls(&self, path: &str) -> PyResult<Vec<String>>

ls() 函数返回在给定路径中找到的键的 Python 列表 []。该实现是对分页 list_objects_v2 的直接使用。此实现中没有并发;1000 个键的每一页都按顺序返回。因此,此实现的任何性能优势都严格归功于 Rust 相对于 Python 的性能提升。

pub fn get_objects(&self, py: Python, paths: Vec<String>) -> PyResult<PyObject>

get_objects 函数获取路径列表并同时下载所有对象,返回 Python 中的 Bytes 对象列表。在内部,该函数首先向所有对象发出 HEAD 请求以获得它们的大小,然后为每个对象分配 Python 内存。最后,该函数同时开始检索所有对象,将大对象分成 128MB 的块。

一个关键的实现细节是首先使用 PyByteArray 为 Python 空间中的对象分配内存,然后使用 Rust 将下载的数据复制到该内存中,这避免了需要内存副本来在 Rust 和 Python 管理的内存之间移动对象数据。

作为旁注,将内存缓冲区划分为块以便可以并行写入数据确实迫使我更好地理解 Rust 的借用检查器!

小物体呢?

所呈现的结果中明显缺乏的是小对象检索时间。我写的 FastS3 库对于小对象来说并不比 Boto3 快(有时慢)。但我很高兴推测这与语言选择无关,主要是因为我的代码到目前为止只针对大型对象进行了优化。具体来说,我的代码在开始并行下载之前执行 HEAD 请求以检索对象大小,而对于小对象,在单个远程调用中获取整个数据更有效。显然,这里有优化的机会。

概括

Python 在数据科学机器学习中的突出地位持续增长。访问对象存储数据和计算硬件 (GPU) 之间的性能不匹配继续扩大。需要更快的对象存储客户端库来为现代处理器提供数据。该博客表明,显着提高性能的一种方法是将原生 Python Boto3 代码替换为已编译的 Rust 代码。正如 NumPy 使 Python 中的计算变得高效一样;新库需要使 S3 访问更高效。

虽然我的代码示例显示在加载大对象和元数据列表方面比 Boto3 有了显着改进,但在小对象 GET 操作和更多 API 方面仍有改进的余地需要重新实现。我基于 Rust 的 Fasts3 库的目标是展示 2x - 3x 的改进规模,以鼓励在这个问题上进行更多的开发。

转载请注明来自海坡下载,本文标题:《cli3优化(使用 Rust 提高 Python S3 客户端性能)》

每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,14人围观)参与讨论

还没有评论,来说两句吧...