分类: Rust

  • 在Rust中使用mimalloc

    mimalloc是一个高效的内存分配器,它被设计用于在多核系统上提供高吞吐量和低延迟。它的特点是快速、节省内存、可扩展和线程安全。mimalloc还支持内存对齐和内存池等高级特性。

    要在Rust项目中使用mimalloc,需要使用mimalloc-sys crate。mimalloc-sys是一个Rust绑定,它提供了对mimalloc的C接口的访问。

    首先,在Cargo.toml文件中添加以下依赖项:

    [dependencies]
    mimalloc-sys = "0.1.18"

    然后,在Rust代码中,使用以下代码初始化mimalloc:

    extern crate mimalloc_sys;
    
    use std::alloc::{GlobalAlloc, Layout};
    use std::ptr::null_mut;
    
    struct Mimalloc;
    
    unsafe impl GlobalAlloc for Mimalloc {
        unsafe fn alloc(&self, layout: Layout) -*mut u8 {
            mimalloc_sys::mi_malloc(layout.size()) as *mut u8
        }
    
        unsafe 
  • Rust调用C++库

    Rust调用C++库

    Rust基于FFI可以调用其他语言,这个支持对于C是很完备的,对于C++有一些限制。常用的工具有cxx和autocxx,前者可控简单,后者强大易用。我一般喜欢使用autocxx,可以少写一些代码。

    我们这里用Rust调用V8为例子。

    打印固定字符串

    首先引入autocxx

    [dependencies]
    autocxx = "0.25.0"
    cxx = "1.0"
    
    [build-dependencies]
    autocxx-build = "0.25.0"

    我们先尝试查询一下V8的版本号。

    新建input文件,定义一个方法version,先返回一个固定字符串。

    inline std::string version() {
        return "0.0.1";
    }

    main.rs方法添加代码

    use autocxx::prelude::*;
    include_cpp! {
        #include "../include/input.h"
        safety!(unsafe_ffi)
        generate!("version")
    }
    
    fn main() {
        let rv8 = 
  • 在铁威马Nas上运行Rust多语言混合项目

    在铁威马Nas上运行Rust多语言混合项目

    手上有一台铁威马Nas,4盘位双核CPU,X86架构的机器。铁威马的硬件还可以,性价比比较足,但是生态很一般,对于相册的管理方面,官方的不怎么满意,在外网找了几个要么功能太简陋,要么内存占用太高,带不动。

    搞来搞去还是自己弄一个,考虑到性能和开销问题,最后选择的是Rust语言,想着横竖都能编译到机器上运行,实在不行Nas还支持Docker,也能解决一部分问题。由于照片太多,所以使用了Tensorflow框架配合MobileNetV2模型进行照片分类和人脸识别,模型都是S规模的,开销可控。

    在开发机器上开发好以后直接打包成Docker准备试试,由于使用了Vue作为前端,所以打包分两部分,先构建前端,然后构建后端,最后产出最终镜像。Dockerfile文件如下:

    FROM node:16 as front
    WORKDIR /usr/src/pototd
    COPY ./webapp ./webapp
    RUN cd webapp && npm install && npm run build
    
    FROM rust:1.62.1 as builder
    COPY --from=front /usr/src/pototd/webapp/dist /usr/src/pototd/webapp/dist
    
    RUN apt update && apt install -y libssl-dev musl-tools libsqlite3-dev 
  • 在Rust中使用TensorFlow

    TensorFlow是一个开源的机器学习框架,上手简单,且在大量商业场景使用,可靠性高。

    机器学习常用的语言主要是python、c,官方也提供了Java版本sdk,示例也很丰富。对于Rust语言,虽然官方文档没有写,其实官方也提供了rust版本的绑定,项目名称就是rust。

    在Cargo.toml中配置版本,这里的0.18.0对应TensorFlow 2.8.0

    tensorflow = "0.18.0"

    这个依赖中的-sys模块会根据实际情况进行处理,如果本地没有Tensorflow的库文件,就会自动下载。如果没有预编译好的库文件,或者用户指定要从源码编译,那就会在本地编译。

    我这里使用的是frozen graph,在rust代码中新建一个Graph进行加载即可。

    let mut graph = Graph::new();
            let model_file = MODEL_DIR
                .get_file("mobilenet/mobilenet_v2_1.4_224_frozen.pb")
                .unwrap();
            let label_file = MODEL_DIR.get_file("mobilenet/label.txt").unwrap();
            graph
                .import_graph_def(model_file.contents(), &ImportGraphDefOptions::new())
                .unwrap();

    为了最后打包方便,这里使用了include_dir包,会把模型和最终产物打包到一起。

    这里的模型是一个mobilenet的图片分类模型,输入为224像素。图片的处理使用image包。

    let img = image::open(photo.get_store_path())?;
            let resized = image::imageops::thumbnail(&img, 224, 
  • Rust自动生成Dart API供Flutter使用

    最近使用了flutter_rust_bridge,流程上非常好,极大程度简化了操作。

    使用方法如下

    提供Rust API

    为了方便管理和测试,对Flutter暴露的接口单独提供,放在api.rs中。里面提供的都是pub fn,出入参数如果内含其他结构体,加上Option,比如

    pub fn query_report(query: ReportQuery) -ReportPageDTO {
        APP.lock().unwrap().query_report(query)
    }
    
    pub struct ReportPageDTO {
        pub total: i32,
        pub list: Option<Vec<ReportDTO,
    }
    

    准备生成工具

    先安装工具和依赖

    dart pub global activate ffigen
    cargo install flutter_rust_bridge_codegen

    另外还需要LLVM,具体安装看操作系统有一些区别。

    最后运行脚本

    flutter_rust_bridge_codegen 
  • Rust工程的分发

    Rust项目直接用Cargo build就可以产出构建物,但是这个构建物不一定真能在部署端运行起来,常见问题有以下几个。

    Glibc版本

    一般我们的构建都是放linux平台的,Glibc是GUN发布的c库,真正的底层依赖。Rust在编译的时候绝大部分都是静态链接,但是对于C标准库,它使用的还是动态链接。Glibc广泛存在,本来也不是啥大问题,但是如果构建的时候使用了相对新的版本,而运行环境是在其他使用低版本Glibc的系统,那么就会有 version `GLIBC_2.18′ not found 这种问题。

    解决方案有很多种,最简单的就是换一个libc的实现,musl是一个相对合理的选择。只要在构建的时候选择x86_64-unknown-linux-musl就行了。

    cargo build --target x86_64-unknown-linux-musl

    构建环境还需要musl-tools环境。

    如果由于一些特殊原因没法用musl,还可以考虑降级Glibc。比如使用低版本的Ubuntu进行构建,也可以使用cross工具。

    外部文件

    如果追求单文件,那么各种配置文件和其他文件依赖就是一个问题。最直接的方法是使用include_str直接把内容放在构建物中,sqlx::migrate就是用的这种方法。如果是其他文件,也可以自己实现,或者使用第三方的库,比如rust-embed等。

    Docker镜像

    Docker镜像更多的问题是在大小上,一般来说采用多步构建就行了。构建镜像可以用rust镜像,运行环境用debian:buster-slim。如果有其他依赖,比如openssl、sqlite也可以自行安装。…

  • 部署Rust工程到阿里云函数计算FC

    本文首发于阿里云开发者社区:https://developer.aliyun.com/article/797279

    阿里云函数计算目前支持C#、Java、Python、Go等大部分语言,对于一些定时任务是非常适合的。最近有一些工程使用Rust重写了也想部署到阿里云函数计算上,这里来看可以使用Custom Runtime/Container。

    Custom Runtime/Container模式

    这个模式本质上是自定义脚本启动一个http服务,然后Serverless平台转发请求到你的http服务,这个请求header中有一些特殊信息,极端的将不考虑启动速度和性能等等,其实是可以无障碍迁移大部分服务的(存储和数据库可能需要改造)。

    这个运行环境默认包含了Python3,Java 8等常见依赖,对于Rust这种编译成二进制文件的就更简单了。最终产物是一个zip包,包含启动文件bootstrap和其他依赖即可。

    Custom Container模式可以理解成增强模式,如果Custom Runtime不可用,一般是由于特殊的本地依赖导致,Custom Container模式的产出是一个镜像,不过由于镜像的大小比代码包大,所以计费上要多一些,包含拉取镜像的费用。所以这两者需要根据情况来决策,尽可能使用Runtime模式。这样费用和速度上都好一些。

    代码改造

    这里使用的是funcraft工具专门用于部署,template.yml文件包含描述信息,代码放在code目录。

    在Cargo.toml中新增两个依赖

    tokio = { version = "1.12.0", features = ["full"] }
    warp = "0.3.0"

    修改main.rs用warp启动一个http服务

    #[tokio::main]
    async fn main() {
        pretty_env_logger::init();
        // POST /invoke
        let 
  • 移除Rust标准库依赖

    现代通用操作系统对于应用程序的支持很多,在硬件平台上操作系统做了管理,操作系统之上还有标准库对应用程序提供系统调用。然后如果只从跑起来的角度来说,只要有最上面和最下面的就行了。

    如下图:

    中间两层提供的都是抽象。而有些时候我们的应用程序需要运行在一个相对底层的情况下,我们就需要移除标准库的依赖,更进一步可能要减少对于部分操作系统的依赖。

    nothing程序

    如果编译一个空的rust程序,比如nothing.rs

    fn main(){}

    在Ubuntu下查看系统调用如下图

    我这里的芯片是riscv的,所以第一步是新增.cargo/config文件

    [build]
    target = "riscv64gc-unknown-none-elf"

    在代码头部取消标准库

    这里编译会失败,因为println!也是std提供的,而core中没有。

    移除后编译也是失败

    panic_handler是致命错误处理函数,默认也是std提供,这里我们加一个空实现

    由于我们的main函数在被调用前,其实有很多初始化工作被做了,现在移除了std也没有了。直接删除main方法,然后加上no_main配置就行了。现在编译可以通过。

    查看产出物的信息,可以看到入口是0,这个产出物本质上是没有用处的。还需要补充上后面需要的内容。

    这里需要加上_start函数

    #[no_mangle]
    extern "C" fn _start() {
        loop{};
    }
  • Flutter和Rust混合编程

    Rust语言本身就是跨平台的,加上很多性能上的优势,很多代码可以在一定程度上复用。

    Flutter的FFI支持可以让我们调用本地代码,也就是可以做到Flutter和Rust混合编程。

    Rust改造

    为了减少改动,我一般会引入一个新的项目,直接在原项目名基础上加上ffi。

    这个项目的主要用途在于定义对外暴露的接口,并在构建时输出so等文件。

    在Cargo.toml中新增两个依赖

    [dependencies]
    rust-project = { path = "../rust-project" }
    
    [build-dependencies]
    cbindgen = "0.14.3"
    dart-bindgen = "0.1.7"

    其中cbindgen帮助我们从Rust代码生成C/C++的头文件,dart-bindgen(现在用ffigen了)帮助我们从头文件生成dart绑定。

    Flutter插件

    有了上一步的so和dart文件后我们就可以调用了,但是出于隔离和复用的考虑,这里不直接在Flutter项目中使用,而是先包装成Plugin。

    按照设计,Flutter插件可以用于封装与平台专用代码进行的通信,由于上一步已经生成了绑定,所以这里只做最基础的封装和注册。

    import 'ffi.dart' as ffi;
    
    class Plugin {
      int functionName(int a, int b) {
        
  • reqwest交叉编译移除对于openssl的依赖

    对于有reqwest依赖的rust项目,如果使用的是cross进行交叉编译,比如

    cross build --target x86_64-unknown-linux-gnu --release

    会提示openssl找不到。

    虽然可以通过自定义镜像等方式提供openssl的依赖,让编译通过,但是更直接的方法是使用rustls。这是一个由纯rust编写TLS库。

    在Cargo.toml中修改reqwest依赖

    reqwest = { version = "0.11", features = ["json", "blocking", "rustls-tls"], default-features = false }

    在features中新增rustls-tls,同时禁用掉默认features。