#static #constructor #optimization #preformance

no-std staticinit

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

1 个稳定版本

1.0.0 2021 年 8 月 2 日

#2002Rust 模式

MIT/Apache

335KB
6K SLoC

LICENSE LICENSE Documentation Crates.io Version

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

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

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

特性

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

最快的 Lazy 静态

此 crate 在所有平台都提供 lazy 静态

在 Unix 和 Windows 上,较少的 lazy 静态 在程序启动阶段(在调用 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};

这些静态初始化和访问可以比标准库或其他 crate 提供的快 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()
   };

这些静态使用一个 适应性阶段锁器,使其具有惊人的性能。

经典的 Lazy 静态

默认情况下,在支持它的平台上,使用 dynamic 声明的静态在 main 开始之前强制初始化。如果需要 延迟性,则可以使用属性参数 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 属性参数,可以在程序退出时运行“释放”等效操作,但不会改变静态。

这些延迟静态也提供了比其他解决方案更好的性能。

Lazy 静态的其他特性

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

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

  • 声明已释放或已删除的静态。

  • 声明可以泄漏的已删除或已终止的静态变量。

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

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 静态变量初始化在 main 开始之前强制进行。这一事实使得所有静态变量都可以用一个布尔值进行双重检查,这比其他双重检查方案要快得多。

基准测试结果

懒静态访问

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

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

懒静态初始化

极短的初始化性能

此图显示了在懒加载尚未初始化时访问懒静态的时间。测量包括来自crate double_checked_cellstatic_lazy 的静态。图例中,“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将表现得类似于具有与可变懒静态相同的语法的RefCell。

# #![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
~46K SLoC