当前位置: 首页 > article >正文

使用rust加速python的tgz解压

使用rust加速python的tgz解压

背景

最近需要把大量的tag解压,python的解压速度上出现了瓶颈。阅读了python的源码后发现较难优化,打算利用rust编译一个库文件供python调用。加速python解压

原理

python 通过ctypes函数库通过c语言兼容类型调用rust编写的函数库
python -》ctypes (Python 的外部函数库) 》 C 》rust.dll/rust.so

实现过程

rust部分

  • rust安装
    window教程

  • 初始化rust lib项目

cargo new --lib rust_tar_library
  • 编写代码

编写rust解压程序并暴露c类型的函数(我是在GPT提示下完成)
需要注意:通过python传入的文件路径字符串需要使用unsafe{}关键字提示,rust不会此部分做垃圾回收处理

use std::alloc::{self, Layout};
use std::fs::File;
use std::io::{self, BufReader};
use flate2::read::GzDecoder;
use tar::Archive;
use std::ffi::{CStr, CString};
use std::ptr;

// c类型的函数
#[no_mangle]
pub extern "C" fn list_files_in_tgz(file_path: *const i8) -> *mut *mut i8 {
    if file_path.is_null() {
        return return_error("File path is null");
    }

    let c_str = unsafe { CStr::from_ptr(file_path) };
    let file_path_str = match c_str.to_str() {
        Ok(path) => path,
        Err(_) => return return_error("Failed to convert file path to UTF-8 string"),
    };

    match list_files_in_tgz_rust(file_path_str) {
        Ok(paths) => {
            let mut c_strings = Vec::new();
            for path in paths {
                let c_string = match CString::new(path) {
                    Ok(s) => s.into_raw(),
                    Err(_) => return return_error("Failed to convert Rust string to C string"),
                };
                c_strings.push(c_string);
            }

            c_strings.push(ptr::null_mut()); // Null-terminate the array
            let array_ptr = c_strings.as_mut_ptr();
            std::mem::forget(c_strings); // Prevent Rust from freeing memory
            array_ptr
        }
        Err(e) => return return_error(&format!("Error processing tar file: {}", e)),
    }
}

fn list_files_in_tgz_rust(file_path: &str) -> io::Result<Vec<String>> {
    let file = File::open(file_path)?;
    let reader = BufReader::new(file);
    let decompressed = GzDecoder::new(reader);
    let mut archive = Archive::new(decompressed);
    
    let mut paths = Vec::new();
    for entry in archive.entries()? {
        let entry = entry?;
        let path = entry.path()?;
        paths.push(path.display().to_string());
    }
    Ok(paths)
}

fn return_error(message: &str) -> *mut *mut i8 {
    let error_message = CString::new(format!("ERROR: {}", message)).unwrap();
    let mut c_array: Vec<*mut i8> = vec![error_message.into_raw(), ptr::null_mut()];
    let array_ptr = c_array.as_mut_ptr();
    std::mem::forget(c_array); // Prevent Rust from freeing memory
    array_ptr
}

#[no_mangle]
pub extern "C" fn free_string_array(ptr: *mut *mut i8) {
    if ptr.is_null() {
        return;
    }

    let mut index = 0;
    unsafe {
        while !(*ptr.add(index)).is_null() {
            let str_ptr = *ptr.add(index);
            if !str_ptr.is_null() {
                let _ = CString::from_raw(str_ptr);
            }
            index += 1;
        }
        alloc::dealloc(ptr as *mut u8, Layout::array::<*mut i8>(index).unwrap());
    }
}
  • rust编译
    常规编译构建
cargo build --release 
  • win7编译
    我的解压电脑是一个win7,win7只支持到rust1.77,建议使用rust1.77编译。

python部分

py调用rust

  • 先使用ctypes加载dll、so
  • 定义参数和返回值
  • 调用函数
  • 获取返回并清理内存
import ctypes
import os
import time

def tar_rust(file_path):
    # 判断当前操作系统
    if os.name == 'nt':  # Windows
        libname = ctypes.CDLL('/home/apple/code/py/backup-file-navigator/release/librust_tar_library.dll') 
    elif os.name == 'posix':  
        libname = ctypes.CDLL('/home/apple/code/py/backup-file-navigator/release/librust_tar_library.so')
    
    
    libname.list_files_in_tgz.argtypes = [ctypes.c_char_p]
    libname.list_files_in_tgz.restype = ctypes.POINTER(ctypes.POINTER(ctypes.c_char))
    libname.free_string_array.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_char))]

    result_ptr = libname.list_files_in_tgz(file_path.encode('utf-8'))
    if not result_ptr:
        print("Function execution failed: No data returned.")
        return

    filenames = []
    index = 0
    while True:
        c_str_ptr = result_ptr[index]
        if not c_str_ptr:
            break
        decoded = ctypes.cast(c_str_ptr, ctypes.c_char_p).value.decode('utf-8')
        if decoded.startswith("ERROR:"):
            print(f"Error: {decoded}")
            libname.free_string_array(result_ptr)
            return
        filenames.append(decoded)
        index += 1

    libname.free_string_array(result_ptr)
    return filenames

if __name__ == "__main__":

    file_path = "/home/apple/tmp/bigfile.tar.gz"  
    # file_path = "/home/apple/tmp/abctrest.tar.gz"  

    start_time = time.perf_counter()
    filenames = tar_rust(file_path)

    end_time = time.perf_counter()
    method1_time = end_time - start_time

    for filename in filenames:
        print(filename)
        
    print(f"Method  took {method1_time:.6f} seconds")

性能测试

最少快一倍,8G的压缩包从49s提升到26s
在这里插入图片描述

项目地址

BackupFileNavigator


http://www.kler.cn/a/466694.html

相关文章:

  • 如何利用人工智能算法优化知识分类和标签?
  • 【Vue】分享一个快速入门的前端框架以及如何搭建
  • 创建并配置华为云虚拟私有云
  • 【数据结构-堆】力扣2530. 执行 K 次操作后的最大分数
  • AI中的神经元与权重矩阵之间的关系;神经元连接角度看行和列的意义
  • 【Unity3D】UGUI Canvas画布渲染流程
  • Excel-vlookup 函数使用
  • 深入理解计算机系统—虚拟内存(2)
  • 数据库新建用户后(Host:%),报错:localhost无法连接
  • linux下安装tun模块详细教程
  • 基于FPGA的2FSK+帧同步系统verilog开发,包含testbench,高斯信道,误码统计,可设置SNR
  • 算法-大整数反转
  • UE4_用户控件_10_用图像来显示场景捕获的渲染目标
  • 企业三要素如何用PHP实现调用
  • IIS设置IP+端口号外网无法访问的解决方案
  • 【Python系列】Flask 与 FastAPI:两个 Python Web 框架的对比分析
  • 组合模式详解
  • 依赖冲突`npm install --no-audit --save @testing-library/jest-dom@^5.14.1...` failed
  • CTE与子查询的区别及运行效率比较
  • 使用Dockerfile构建镜像
  • centos8 部署 kubernetes集群
  • 网易云的ip归属地是什么意思?为什么变来变去
  • Segment Anything C++ 项目【Part2:修改源码+自动分割】
  • 我的博客年度之旅:感恩、成长与展望
  • MySQL 表结构在线变更:优雅地解决停机问题
  • 【Rust自学】10.2. 泛型