Rustのlazystra静的!使用法ベンチマークとコード深い潜水


lazy_static さびの生態系の基礎的な木箱の一つです.
これは、明示的な初期化コールなしで静的変数を使用することができます.
私はそれを多くの時間を使用してその性能の意味を与えることなく考えた.すべてのその怠惰な静的な魔法が若干の隠れたコストを持っているならば、私を心配させました.
The crate's docs 力学を説明するlazy_static! マクロとして:

The Deref implementation uses a hidden static variable that is guarded by an atomic check on each access.


それは無邪気に聞こえるが、まだ質問がある.
  • 各々のアクセスに関するアトミックチェックによって引き起こされる顕著なパフォーマンスコストはありますか?
  • If lazy_static サブモジュールで使用されますが、モジュールからの関数へのすべての呼び出しで再初期化されますか?
  • 変数を手動で初期化したり、パラメータとして他の関数に渡すのは遅いですか?
  • 実装の詳細を理解しないでくださいlazy_static 私は、それを通して掘るより、それがベンチマークにより簡単であるとわかりましたsource code .

    ハウツー動画

  • プロジェクトのソースを取得git clone https://github.com/rimutaka/empirical.git
  • ベンチマークcargo +nightly bench
  • テストcargo +nightly test --benches
  • 結果


    $ cargo +nightly bench
    
    test bad_rust_local           ... bench:      40,608 ns/iter (+/- 9,239)
    test lazy_static_backref      ... bench:          27 ns/iter (+/- 1)
    test lazy_static_external_mod ... bench:          27 ns/iter (+/- 0)
    test lazy_static_inner        ... bench:          27 ns/iter (+/- 1)
    test lazy_static_local        ... bench:          27 ns/iter (+/- 5)
    test lazy_static_reinit       ... bench:          26 ns/iter (+/- 1)
    test once_cell_lazy           ... bench:          26 ns/iter (+/- 2)
    test vanilla_rust_local       ... bench:          27 ns/iter (+/- 0)
    
    結果はかなりきれいに見えた.唯一の外れは、私がベースラインをセットするために意図的にベンチに入れた悪いコードの部分でした.

    TL博士:lazystra静的!良いですが、OnceLIGNセルは、新しいプロジェクトの方が良いかもしれません。


    ベンチマーク詳細


    BadHeight Rustchen local ()


    このベンチは明らかに愚かなことをします-それはループ内の正規表現を再コンパイルします.
    b.iter(|| {
        let compiled_regex = regex::Regex::new(LONG_REGEX).unwrap(); // <-- don't place this inside a loop
        let is_match = compiled_regex.is_match(TEST_EMAIL);
        test::black_box(is_match);
    });
    
    40608 ns/iterで、それは我々に再コンパイルコストのベースラインを与えます.

    Vanillaum rustchen local ()


    各アクセスについてアトミックチェックによる顕著な性能コストはなかった.vanilla_rust_local ベンチは、一度正規表現をコンパイルし、正確に同じ27 ns/iterlazy_static .
    let compiled_regex = regex::Regex::new(LONG_REGEX).unwrap(); // <-- compiled once only
    b.iter(|| {
        let is_match = compiled_regex.is_match(TEST_EMAIL);
        test::black_box(is_match);
    });
    

    <高橋潤子>


    これらのベンチはlazy_static 宣言された場所の唯一の違いがあります.

  • ローカルレベルで:ローカル

  • LazyHard StaticCherm :サブモジュールレベル(同じファイル)で

  • 別のファイルに配置されたモジュールで

  • ルートレベルで、サブモジュールで使用されます
  • The lazy_static 宣言は全てのケースで同一であった.
    lazy_static! {
        pub(crate) static ref COMPILED_REGEX: regex::Regex = regex::Regex::new(LONG_REGEX).unwrap();
    }
    
    の配置lazy_static! { ... } 宣言は全く違いました.
  • static変数は一度だけ初期化されました
  • すべてのこれらのベンチは、それぞれ27 ns/iterを取った.
  • LazyHand StaticCharity ()


    最初の使用の前または後に静的変数を初期化することが可能です
    lazy_static::initialize(&STATIC_VAR_NAME);
    
    呼び出しには追加のパフォーマンスコストはなかったinitialize 初めて、または何度もそれ以降.
    これは以下のような状態になります.

    Takes a shared reference to a lazy static and initializes it if it has not been already.


    オンセル細胞


    once_cell と同じくらいエレガントですlazy_static! で、約20 %速くなります32-thread test . それは50 %と同等に行われたlazy_static! 私の基本テストで.
    静的宣言は1行のコードです.
    static COMPILED_REGEX_ONCE_CELL: once_cell::sync::Lazy<regex::Regex> =
        once_cell::sync::Lazy::new(|| regex::Regex::new(LONG_REGEX).unwrap());
    
    と静的変数の使用法はlazy_static! :
        b.iter(|| {
            let is_match = COMPILED_REGEX_ONCE_CELL.is_match(TEST_EMAIL);
            test::black_box(is_match);
        });
    
    があるRFC to merge once_cell into std::lazy 標準ライブラリの一部を作る.あなたが新しいプロジェクトを始めているならば、それはより将来の証拠選択であるかもしれません.

    LazyRange静的代替


    静的変数の宣言


    pub(crate) static STATIC_REGEX: regex::Regex = regex::Regex::new(LONG_REGEX).unwrap();
    

    ERROR: calls in statics are limited to constant functions, tuple structs and tuple variants rustc E0015


    const関数の宣言


    const fn static_regex() -> regex::Regex {
        regex::Regex::new(LONG_REGEX).unwrap()
    }
    

    ERROR: calls in constant functions are limited to constant functions, tuple structs and tuple variants rustc E0015


    深さの静的



    このセクションは、lazy_static 実際にどのように動作を理解する.
    このプロジェクトのデモコードはいくつかの部分に分割されます.

  • ベンチ:このポストのベンチマークのための源

  • 例/拡張性ベース.RS:拡張コードを取得する最小の実装lazy_static! マクロ

  • 例/拡張.RS :生成された拡張コードlazy_static! マクロから拡張マクロ.RS

  • src/mainRS:拡張コードに基づく自己完結型実装
  • あなたが安定したチャンネルにいるならば、あなたのIDEはコードの一部で不満です.IDEの警告を取り除くために、これらのコマンドを毎時から/へ切り替えます.
    rustup default nightly
    rustup default stable
    

    マクロコード


    走るcargo expand --example expansion_base 木枠のコードを出力します.あなたは完全な出力を見つけることができますexamples/expanded.rs ファイル.
    つまり、以下のようになります.
    #![feature(prelude_import)]
    #[prelude_import]
    use std::prelude::rust_2021::*;
    #[macro_use]
    extern crate std;
    extern crate lazy_static;
    use lazy_static::lazy_static;
    #[allow(missing_copy_implementations)]
    #[allow(non_camel_case_types)]
    #[allow(dead_code)]
    struct COMPILED_REGEX {
        __private_field: (),
    }
    #[doc(hidden)]
    static COMPILED_REGEX: COMPILED_REGEX = COMPILED_REGEX {
        __private_field: (),
    };
    impl ::lazy_static::__Deref for COMPILED_REGEX {
        type Target = regex::Regex;
        fn deref(&self) -> &regex::Regex {
            #[inline(always)]
            fn __static_ref_initialize() -> regex::Regex {
                regex::Regex::new(".*").unwrap()
            }
            #[inline(always)]
            fn __stability() -> &'static regex::Regex {
                static LAZY: ::lazy_static::lazy::Lazy<regex::Regex> = ::lazy_static::lazy::Lazy::INIT;
                LAZY.get(__static_ref_initialize)
            }
            __stability()
        }
    }
    impl ::lazy_static::LazyStatic for COMPILED_REGEX {
        fn initialize(lazy: &Self) {
            let _ = &**lazy;
        }
    }
    fn main() {
        let _x = COMPILED_REGEX.is_match("abc");
    }
    
    上のスニペットは、lazy_static そして、それは魔法の大部分が起こるところです.
    私はそれを持っていない簡単なバージョンに蒸留lazy_static すべての依存関係として.それを通してスキムし、以下の説明に進みます.
    struct Lazy<T: Sync>(Cell<Option<T>>, Once);
    unsafe impl<T: Sync> Sync for Lazy<T> {}
    
    struct CompiledRegex {
        __private_field: (),
    }
    
    static COMPILED_REGEX: CompiledRegex = CompiledRegex {
        __private_field: (),
    };
    
    impl Deref for CompiledRegex {
        type Target = regex::Regex;
        fn deref(&self) -> &regex::Regex {
            static LAZY: Lazy<regex::Regex> = Lazy(Cell::new(None), Once::new());
    
            LAZY.1.call_once(|| {
                LAZY.0.set(Some(regex::Regex::new(LONG_REGEX).unwrap()));
            });
    
            unsafe {
                match *LAZY.0.as_ptr() {
                    Some(ref x) => x,
                    None => {
                        panic!("attempted to dereference an uninitialized lazy static. This is a bug");
                    }
                }
            }
        }
    }
    
    最後のスニペットでいくつかの重要な機能があります.
  • struct Lazy 総称するstruct CompiledRegex としてインスタンス化されるstatic COMPILED_REGEX これは実際にコンパイルされた正規表現を保持します.
  • その長い鎖は内部で解明されているimpl Deref for CompiledRegex 静的変数としてコンパイルされた正規表現を与えるには
  • std::sync::Once::call_once() は正規表現を初期化するのに使われる
  • match *LAZY.0.as_ptr() 構造体の鎖の中の深さから初期化された正規表現を取得する
  • 上記のコードがまだ少し混乱しているならsrc/main.rs 詳細なコメントをフルバージョン.
    プログラムの実行cargo run このデモコードをsrc/main.rs 怠惰な静的な部分のために上記のスニペットを使用すること
    fn main() {
        println!("Program started");
        println!("{TEST_EMAIL} is valid: {}", COMPILED_REGEX.is_match(TEST_EMAIL));
        println!("{TEST_NOT_EMAIL} is valid: {}", COMPILED_REGEX.is_match(TEST_NOT_EMAIL));
    }
    
    を返します.
    Program started
    
    Derefencing CompiledRegex
    
    CompiledRegex initialized
    
    [email protected] is valid: true
    
    Derefencing CompiledRegex
    
    Hello world! is valid: false
    
    ご覧の通り.COMPILED_REGEX は1回だけ初期化され、COMPILED_REGEX 変数が使用されます.
    Q . E . D .?)