【问题标题】:How to initialize immutable globals with non-const initializer in Rust?如何在 Rust 中使用非常量初始化程序初始化不可变的全局变量?
【发布时间】:2020-12-03 05:37:30
【问题描述】:

我试图获得一个在运行时只初始化一次的变量。 在 C/C++ 中,static 是我要查找的关键字,但在 Rust 中,它必须由常量初始化。

static mut 不安全,我知道原因,但它在概念上并没有捕捉到我想要的东西,我想要一个不可变的变量。

以 tribonacci 函数为例:

static sqrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);

static tribonacci_constant: f64 = 1.0
+ (19.0 - sqrt_33_mul_3).powf(1.0 / 3.0)
+ (19.0 + sqrt_33_mul_3).powf(1.0 / 3.0);

fn tribonacci(n: f64) -> f64 {
    return (
        (tribonacci_constant / 3.0).powf(n)
        / (
            (4.0 / 3.0)
            * tribonacci_constant
            - (1.0 / 9.0)
            * tribonacci_constant.powf(2.0) - 1.0
        )
    ).round();
}

我希望函数外部的两个静态变量只初始化一次,并且每次运行函数时都不会调用 powf

我对 Rust 非常陌生,不知道对于普通的、有经验的用户来说什么是常识。

这可能吗,如果可以,怎么做?

【问题讨论】:

  • TL;DR,但您似乎正在寻找 lazy_static
  • 问题是f64::powf不是const函数。我已经找到了一些 RFC,用于将大多数/所有浮点函数设为 const。大多数 cmets 说这很“困难”,但我无法弄清楚为什么。 (我怀疑这与更改某些硬件浮点模式等导致这些函数在运行时的行为发生潜在变化有关)。我希望这样的事情最终会生锈,这样你的代码就会“正常工作”。
  • 我很确定您可以将变量定义插入到函数中。一般来说,函数中的静态变量应该只初始化一次。
  • @Adam 不管他们在哪里,他们都需要用一个常量来初始化。
  • @MichaelAnderson 那部分我明白了;这就是为什么我不得不来这里问的原因。

标签: rust static initialization


【解决方案1】:

如果 f64::powf 是一个 const 函数,那么编译器应将 3.0 * 33.0f64.powf(0.5) 之类的内容转换为单个固定值。

虽然lazy_static 可用于解决此问题,但使用lazy_statics 是有代价的,因为它们旨在支持的不仅仅是简单的浮点常量。

您可以通过使用 Criterion 对两个实现进行基准测试来查看此成本:

pub mod ls {
    use lazy_static::lazy_static; // 1.4.0

    lazy_static! {
        //TODO: Should this be a pow(1.0/3.0)?
        pub static ref cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
        
        pub static ref tribonacci_constant: f64 = 1.0
        + (19.0 - *cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + *cbrt_33_mul_3).powf(1.0 / 3.0);
    }

    pub fn tribonacci(n: f64) -> f64 {
        return (
            (*tribonacci_constant / 3.0).powf(n)
            / (
                (4.0 / 3.0)
                * *tribonacci_constant
                - (1.0 / 9.0)
                * tribonacci_constant.powf(2.0) - 1.0
            )
        ).round();
    }
}

pub mod hc {
    pub fn tribonacci(n: f64) -> f64 {
        let p = 1.839286755214161;
        let s = 0.3362281169949411;
        return (s * p.powf(n)).round();
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("trib 5.1 ls", |b| b.iter(|| ls::tribonacci(black_box(5.1))));
    c.bench_function("trib 5.1 hc", |b| b.iter(|| hc::tribonacci(black_box(5.1))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

成本很小,但如果这是在您的核心循环中,则可能会很重要。 在我的机器上,我得到(在删除不相关的行之后)

trib 5.1 ls             time:   [47.946 ns 48.832 ns 49.796 ns]                         
trib 5.1 hc             time:   [38.828 ns 39.898 ns 41.266 ns]                         

这大约是 20% 的差异。

如果您不喜欢在代码中使用硬编码常量,您实际上可以在构建时使用 build.rs 脚​​本生成这些常量。

我的完整基准测试示例如下所示:

build.rs

use std::env;
use std::fs;
use std::path::Path;

fn main() {
    let out_dir = env::var_os("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("constants.rs");

    //TODO: Should this be a pow(1.0/3.0)?
    let cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
    
    let tribonacci_constant: f64 = 1.0 
        + (19.0 - cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + cbrt_33_mul_3).powf(1.0 / 3.0);

    let p = tribonacci_constant / 3.0;
    let s = 1.0 / (
        (4.0 / 3.0)
        * tribonacci_constant
        - (1.0 / 9.0)
        * tribonacci_constant.powf(2.0) - 1.0
    );

    fs::write(
        &dest_path,
        format!("\
        pub mod tribonacci {{\n\
            pub const P: f64 = {:.32};\n\
            pub const S: f64 = {:.32};\n\
        }}\n", p, s)
    ).unwrap();
    println!("cargo:rerun-if-changed=build.rs");
}

src/lib.rs

pub mod constants {
    include!(concat!(env!("OUT_DIR"), "/constants.rs"));
}

pub mod ls {
    use lazy_static::lazy_static; // 1.4.0

    lazy_static! {
        //TODO: Should this be a pow(1.0/3.0)?
        pub static ref cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
        
        pub static ref tribonacci_constant: f64 = 1.0
        + (19.0 - *cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + *cbrt_33_mul_3).powf(1.0 / 3.0);
    }

    pub fn tribonacci(n: f64) -> f64 {
        return (
            (*tribonacci_constant / 3.0).powf(n)
            / (
                (4.0 / 3.0)
                * *tribonacci_constant
                - (1.0 / 9.0)
                * tribonacci_constant.powf(2.0) - 1.0
            )
        ).round();
    }

}

pub mod hc {
    pub fn tribonacci(n: f64) -> f64 {
        let p = super::constants::tribonacci::P;
        let s = super::constants::tribonacci::S;
        return (s * p.powf(n)).round();
    }
}

benches/my_benchmark.rs

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rust_gen_const_vs_lazy_static::ls;
use rust_gen_const_vs_lazy_static::hc;

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("trib 5.1 ls", |b| b.iter(|| ls::tribonacci(black_box(5.1))));
    c.bench_function("trib 5.1 hc", |b| b.iter(|| hc::tribonacci(black_box(5.1))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

Cargo.toml

[package]
name = "rust_gen_const_vs_lazy_static"
version = "0.1.0"
edition = "2018"

[dependencies]
"lazy_static" = "1.4.0"

[dev-dependencies]
criterion = "0.3"

[[bench]]
name = "my_benchmark"
harness = false

$OUTDIR/constants.rs(生成)

pub mod tribonacci {
pub const P: f64 = 1.83928675521416096216853475198150;
pub const S: f64 = 0.33622811699494109527464047459944;
}

正如 Dilshod Tadjibaev 所建议的,使用 proc-macros 可以获得类似的结果,尽管在这种情况下需要做更多的工作。这提供了与构建时生成完全相同的速度。

为了进行设置,我为宏 trib_macros 创建了一个新的 crate,因为 proc-macros 需要在它们自己的 crate 中。这个新的 crate 只包含两个文件 Cargo.tomlsrc/lib.rs

Cargo.toml

[package]
name = "trib_macros"
version = "0.1.0"
edition = "2018"


[lib]
proc-macro = true

src/lib.rs

extern crate proc_macro;
use proc_macro::TokenStream;

#[proc_macro]
pub fn tp(_item: TokenStream) -> TokenStream {
    let cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
    
    let tribonacci_constant: f64 = 1.0 
        + (19.0 - cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + cbrt_33_mul_3).powf(1.0 / 3.0);

    let p = tribonacci_constant / 3.0;
    format!("{}f64",p).parse().unwrap()
}

#[proc_macro]
pub fn ts(_item: TokenStream) -> TokenStream {
    let cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
    
    let tribonacci_constant: f64 = 1.0 
        + (19.0 - cbrt_33_mul_3).powf(1.0 / 3.0)
        + (19.0 + cbrt_33_mul_3).powf(1.0 / 3.0);

    let s = 1.0 / (
        (4.0 / 3.0)
        * tribonacci_constant
        - (1.0 / 9.0)
        * tribonacci_constant.powf(2.0) - 1.0
    );
    format!("{}f64",s).parse().unwrap()
}

那我们需要调整原来 crate 的Cargo.toml 把这个拉进去。

[dependencies]
...
trib_macros = { path = "path/to/trib_macros" }

而且最后用起来比较干净:

pub mod mc {
    use trib_macros::{ts,tp};

    pub fn tribonacci(n: f64) -> f64 {
        return (ts!() * tp!().powf(n)).round();
    }
}

肯定有一种更简洁的方式来输出浮点文字标记,但我找不到。


您可以在 https://github.com/mikeando/rust_code_gen_example 找到这些测试的完整存储库

【讨论】:

  • 首先,你发现了我的小错误,我仔细检查了数学,它是一个 sqrt,而不是一个 cbrt。修正了变量命名。实际上,我在测试过lazy_static 后回来报告说:检查输出代码后,数字都是在编译时生成的,代码只包含常量、对 powf 的调用和除法运算。因此,lazy_static 和取消引用实际上导致了更糟糕的代码。 @Netwave
  • 我正在寻找一种方法来实现性能,无论编译器是否可以将它们优化为常量,而不牺牲代码的可读性、开发人员的工作量等。我很惊讶没有一种简单、惯用的方法来做到这一点。我想我太习惯在 C++ 中使用命名空间静态常量了。
  • 我同意设置它需要付出更多的努力(并且常量浮点函数会更好并解决所有这些问题),但是一旦部件到位,维护和扩展就很容易.
  • @MichaelAnderson 你有没有想过用宏来解决这个问题?
  • 我确实让它与 proc-macro 一起工作,并将其添加为进一步的示例。
【解决方案2】:

你可以使用lazy_static:

use lazy_static::lazy_static; // 1.4.0

lazy_static! {
    static ref cbrt_33_mul_3: f64 = 3.0 * 33.0f64.powf(0.5);
    
    static ref tribonacci_constant: f64 = 1.0
    + (19.0 - *cbrt_33_mul_3).powf(1.0 / 3.0)
    + (19.0 + *cbrt_33_mul_3).powf(1.0 / 3.0);
}

fn tribonacci(n: f64) -> f64 {
    return (
        (*tribonacci_constant / 3.0).powf(n)
        / (
            (4.0 / 3.0)
            * *tribonacci_constant
            - (1.0 / 9.0)
            * tribonacci_constant.powf(2.0) - 1.0
        )
    ).round();
}

fn main() {
    println!("Hello, world!");
}

注意ref 的用法和tribonacci_constant 所需的取消引用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    相关资源
    最近更新 更多