#static #constructor #optimization #execution-time #no-std #performance

no-std static_init

安全可变静态和非 const 静态初始化,以及程序启动/退出时的代码执行

11 个版本 (3 个稳定版)

1.0.3 2022 年 8 月 18 日
1.0.2 2022 年 1 月 10 日
1.0.1 2021 年 4 月 14 日
0.5.2 2021 年 3 月 9 日

#62Rust 模式

Download history 43372/week @ 2024-03-14 49752/week @ 2024-03-21 48771/week @ 2024-03-28 53831/week @ 2024-04-04 49701/week @ 2024-04-11 47578/week @ 2024-04-18 41999/week @ 2024-04-25 39285/week @ 2024-05-02 39112/week @ 2024-05-09 39587/week @ 2024-05-16 50785/week @ 2024-05-23 48997/week @ 2024-05-30 49332/week @ 2024-06-06 53416/week @ 2024-06-13 57844/week @ 2024-06-20 44994/week @ 2024-06-27

214,586 每月下载量
215 包(101 个直接使用)中使用

MIT/Apache 协议

340KB
6.5K SLoC

LICENSE LICENSE Documentation Crates.io Version

安全非 const 初始化静态和具有无与伦比性能的安全可变静态。

还提供程序启动/退出时的代码执行。

为什么使用非 const 初始化静态和安全可变静态?因为所有执行都依赖于在整个程序执行期间维护的状态。因为要有一个易于使用、安全且性能优异的解决方案来维护此类状态非常困难。

特性

  • 非 const 初始化静态。
  • 程序退出时删除的静态。
  • 安全的可变懒静态(锁定)。
  • 所有特性均支持 no_std
  • 无与伦比的性能,可以比任何其他解决方案快一个数量级。
  • 程序退出时注册代码执行,无需分配(与 libc::at_exit 相比)。
  • 易于使用的语法。
  • 正确且安全。
  • 夜间,thread_locals 和安全可变 thread_locals,保证在线程退出时以与系统库线程支持或标准库相比最低的 overhead 释放!

最快的懒静态

此软件包为所有平台提供 懒静态

在 Unix 和 Windows 上,较慢的懒静态 在程序启动阶段(在调用 main 之前)是 懒的。一旦调用 main,这些静态变量都保证被初始化,并且对它们的访问几乎不会产生任何性能开销。

use static_init::{dynamic};

#[dynamic] 
static L1: Vec<i32> = vec![1,2,3,4,5,6];

#[dynamic(drop)] 
static mut L2: Vec<i32> = {let mut v = L1.clone(); v.push(43); v};

这些静态初始化和访问可以比标准库或其他软件包快 10 倍。

安全可变静态

只需添加 mut 关键字即可获得可变锁定静态。

use static_init::{dynamic};

#[dynamic] 
static mut L1: Vec<i32> = vec![1,2,3,4,5,6];

//Mutable statics are safe to access
#[dynamic] 
static mut L2: Vec<i32> = {
   //get a unique lock:
   let mut lock = L1.write(); 
   lock.push(42); 
   lock.clone()
   };

这些静态变量使用一个 适应性阶段锁,这使得它们具有令人惊讶的性能。

经典懒静态

默认情况下,在支持的平台中,使用dynamic声明的静态变量初始化将在主函数开始之前强制执行。如果需要延迟加载功能,可以使用属性参数lazy

use static_init::{dynamic};

#[dynamic(lazy)] 
static L1: Vec<i32> = vec![1,2,3,4,5,6];

#[dynamic(lazy,drop)] 
static mut L3: Vec<i32> =L1.clone(); 

即使静态变量不是可变的,被丢弃的静态变量总是被锁定。还有一个可以使用的finalize属性参数,可以在程序退出时执行“丢弃”等效操作,但不会更改静态变量的值。

这些延迟加载方案与其它解决方案相比提供了更优越的性能。

延迟静态变量的其他功能

dynamic宏的文档中,您可以找到如何

  • 声明在首次初始化时引发恐慌的静态变量。(默认情况下,初始化会重试)

  • 声明已终止或已丢弃的静态变量。

  • 声明可以容忍泄漏的已丢弃或已终止的静态变量。

  • 声明延迟加载的静态变量,这些变量也是const初始化的,并在程序/线程退出时释放资源时提供const回退。

no_std支持

在Linux或Reddox(待定)上,这个库是no_std。当需要时,库会直接使用futex系统调用来将线程放入等待队列。

在其他平台上,可以通过使用spin_loop功能来获得no_std支持。请注意,基于自旋循环的锁定策略不是系统公平的,会导致整个系统变慢。

高效

底层

使用dynamic属性声明的静态变量和可变静态变量使用我们称之为自适应阶段锁的东西。这是一种介于OnceRwLock之间的锁。它被精心实现为在parking_lot crate的RwLock算法上的一种变体,具有其他权衡和不同的能力。

它被称为自适应是因为在锁定尝试过程中执行获取读锁、写锁或不获取锁的决定,如果线程即将被放入等待队列,则线程可能会尝试获取写锁,但决定作为读锁的所有者唤醒。

需要注册自己在程序或线程退出时进行销毁的静态变量和线程局部变量被实现为侵入式列表的成员。这种实现避免了由系统库支持(如libc::at_exitglibc::__cxa_at_thread_exit、pthread...注册)引起的堆内存分配,并避免了系统库实现限制,这些限制可能导致使用std::thread_locals声明的thread_locals无法销毁。

最后但同样重要的是,在Windows和Unix系统上(但不是Mac)dynamic静态变量初始化会在主函数开始之前强制执行。这一事实使得所有静态变量可以使用单个布尔值进行双重检查,这比其他双重检查解决方案快得多。

基准测试结果

延迟静态变量访问

此图显示了懒静态初始化后的访问时间。测量包括来自crate的静态变量 double_checked_celllazy_static。在图例中,“ LesserLazy”表示使用#[dynamic]属性声明的懒静态,而“Lazy”则表示使用属性#[dynamic(lazy)]声明的静态。水平轴报告了几乎同时尝试访问懒静态的线程数,垂直轴则是所有线程的访问时间总和。

从这些crate访问懒静态的访问时间可以比其他解决方案快至10倍

懒静态初始化

初始化性能极短

此图显示了懒静态尚未初始化时的访问时间。测量包括来自crate的静态变量 double_checked_celllazy_static。在图例中,“ LesserLazy”表示使用#[dynamic]属性声明的懒静态,而“Lazy”则表示使用属性#[dynamic(lazy)]声明的静态。水平轴报告了几乎同时尝试访问和初始化懒静态的线程数,垂直轴是所有线程的访问时间加初始化时间开销的总和。初始化本身以皮秒计算。“ LesserLazy”(#[dynamic])在这里没有绘制,因为它们在主程序开始之前就已经初始化了,但无论如何,它们使用""Lazy" (#[dynamic(lazy)]`)静态相同的锁。

使用这些crate的静态变量时,初始化持续时间快3倍

大初始化性能

在这种情况下,所有线程尝试初始化一个初始化时间约为20µs的静态变量。这些crate的静态变量在高竞争条件下扩展性更好。在高竞争条件下,这些crate提供了10倍速度提升

可变锁定懒访问

这些crate的可变懒静态与使用parking-lot crate RwLock实现的实现进行了比较。实现可以在源文件 benches/multi_threaded/main.rs 中找到。

下图中,“Locked Lazy”图例表示使用 #[dynamic(lazy)] 属性声明的可变静态变量,“LesserLocked Lazy”表示使用 #[dynamic] 声明的静态变量,“LesserLocked LazyDrop”表示使用 #[dynamic(drop)] 声明的静态变量,而“Locked Lazy PkLot”则使用 parking-lot crate 的 RwLock 实现。

此crate中的可变锁定静态变量在通过尝试获取读锁进行初始化的情况下,速度接近于使用 parking-lot RwLock 解决方案的速度的 2倍。当通过尝试获取写锁进行初始化时,所有解决方案在全局上是等效的。

可变锁定延迟静态初始化

初始化性能极短

在这里,我们比较当延迟尚未初始化时访问时间。在高竞争情况下,当大量线程尝试在延迟尚未初始化时获取读锁,使用 #[dynamic(lazy)] 声明的延迟接近于 100倍RwLock。这是自适应锁的结果。另一方面,在低竞争情况下,当只有1个或2个线程同时尝试初始化静态变量时,这种自适应性质会导致初始化时间增加。尽管如此,这只是一个几纳秒的性能转移。

大的初始化时间

在这里,我们比较当延迟尚未初始化时和初始化时间约为20µs时的访问时间 + 初始化时间。当所有线程尝试在尝试获取写锁的同时初始化静态变量时,此crate的静态变量性能与使用 parking_lot RwLock 的静态变量相似。但如果通过尝试获取读锁执行此类初始化,则此crate的静态变量比 RwLock200倍。这也是自适应锁算法的结果。

线程本地支持

在nightly中,可以通过 thread_local 特性启用 thread_local 支持。与常规静态变量一样,可以使用 dynamic 属性与线程本地一起使用。在这种情况下,可变的 thread_local 将与具有相同语法的可变延迟静态变量类似的行为。

# #![cfg_attr(feature = "thread_local", feature(thread_local))]
# use static_init::{Finaly,dynamic};
# #[cfg(feature = "thread_local")]
# mod m{
# use static_init::{dynamic};

#[dynamic(drop)] //guaranteed to be drop: no leak contrarily to std::thread_local
#[thread_local]
static V: Vec<i32> = vec![1,1,2,3,5];

#[dynamic]
#[thread_local]
static mut W: Vec<i32> = V.clone();
# fn main() { 
assert_ne!(W.read().len(), 0);
assert_ne!(W.try_read().unwrap().len(), 0);
# }
# }

不安全低级

在程序启动时初始化的不检查静态变量

该库还提供不检查的静态变量,其初始化在main启动之前运行。这些静态变量不涉及任何内存开销或执行时间开销。确保在它们初始化之前不访问这些静态变量是程序员的职责。

use static_init::dynamic;

#[dynamic(10)]
static A: Vec<i32> = vec![1,2,3];

#[dynamic(0,drop)]
static mut B: Vec<i32> = unsafe {A.clone()};

即使A没有声明为可变的,属性宏也会将其转换为可变静态变量,以确保对其的每次访问都是不安全的。

数字表示优先级,数字越大,静态变量初始化越早。

这些静态变量也可以使用 drop 属性参数在程序退出时丢弃。

程序构造函数/析构函数

可以注册在main启动之前/返回之后执行的功能。

use static_init::{constructor, destructor};

#[constructor(10)]
extern "C" fn run_first() {}

#[constructor(0)]
extern "C" fn then_run() {}

#[destructor(0)]
extern "C" fn pre_finish() {}

#[destructor(10)]
extern "C" fn finaly() {}

调试支持

可以通过激活 debug_order 特性来检测原始静态变量的初始化顺序问题或由于延迟初始化而导致的死锁。

依赖关系

~1.4–2.2MB
~45K SLoC