From 2a210ba6016f7b00438dad00247846bf9bf2962d Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Fri, 24 May 2024 23:01:57 +0200 Subject: [PATCH] a lot of updates --- config.toml | 2 + content/posts/icc/code/.cargo/config.toml | 4 + content/posts/icc/code/Cargo.lock | 1483 +++++++++++++++++++++ content/posts/icc/code/Cargo.toml | 21 + content/posts/icc/code/benches/seqlock.rs | 345 +++++ content/posts/icc/code/src/lib.rs | 129 ++ content/posts/icc/code/src/main.rs | 201 +++ content/posts/icc/index.md | 320 +++++ content/posts/icc/timekeeper_example.png | Bin 0 -> 96205 bytes sass/parts/_code.scss | 2 +- sass/parts/_misc.scss | 7 +- sass/parts/_table.scss | 6 +- 12 files changed, 2515 insertions(+), 5 deletions(-) create mode 100644 content/posts/icc/code/.cargo/config.toml create mode 100644 content/posts/icc/code/Cargo.lock create mode 100644 content/posts/icc/code/Cargo.toml create mode 100644 content/posts/icc/code/benches/seqlock.rs create mode 100644 content/posts/icc/code/src/lib.rs create mode 100644 content/posts/icc/code/src/main.rs create mode 100644 content/posts/icc/index.md create mode 100644 content/posts/icc/timekeeper_example.png diff --git a/config.toml b/config.toml index ae3d661..5940a30 100644 --- a/config.toml +++ b/config.toml @@ -9,6 +9,8 @@ compile_sass = true minify_html = true taxonomies = [{ name = "tags" }] +ignored_content = ["*/target/*"] + [markdown] highlight_code = true highlight_theme = "ayu-dark" diff --git a/content/posts/icc/code/.cargo/config.toml b/content/posts/icc/code/.cargo/config.toml new file mode 100644 index 0000000..4eedd94 --- /dev/null +++ b/content/posts/icc/code/.cargo/config.toml @@ -0,0 +1,4 @@ +[build] +# rustflags = ["-C", "target-cpu=native"] +# [target.arm-linux-androideabi] +# linker = "lld" diff --git a/content/posts/icc/code/Cargo.lock b/content/posts/icc/code/Cargo.lock new file mode 100644 index 0000000..a0a155e --- /dev/null +++ b/content/posts/icc/code/Cargo.lock @@ -0,0 +1,1483 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.5", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "code" +version = "0.1.0" +dependencies = [ + "core_affinity", + "criterion", + "ctrlc", + "ma_queues", + "ma_time", + "ma_timing", + "quanta", + "rand", +] + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core_affinity" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622892f5635ce1fc38c8f16dfc938553ed64af482edb5e150bf4caedbfcb2304" +dependencies = [ + "libc", + "num_cpus", + "winapi", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "ctrlc" +version = "3.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" +dependencies = [ + "nix 0.28.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "drawille" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e461c3f1e69d99372620640b3fd5f0309eeda2e26e4af69f6760c0e1df845" +dependencies = [ + "colored", + "fnv", +] + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "log", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "ma_queues" +version = "0.1.0" +dependencies = [ + "log", + "shared_memory", + "thiserror", +] + +[[package]] +name = "ma_time" +version = "0.1.0" +dependencies = [ + "once_cell", + "quanta", + "serde", + "web-time", +] + +[[package]] +name = "ma_timing" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "core_affinity", + "crossterm", + "fern", + "log", + "ma_queues", + "ma_time", + "ratatui", + "rgb", + "textplots", + "walkdir", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quanta" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ratatui" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "crossterm", + "indoc", + "itertools 0.11.0", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "raw-cpuid" +version = "11.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shared_memory" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8593196da75d9dc4f69349682bd4c2099f8cde114257d1ef7ef1b33d1aba54" +dependencies = [ + "cfg-if", + "libc", + "nix 0.23.2", + "rand", + "win-sys", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textplots" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59b64803118dbff62f92842b3154a2c802dfd8e18660132bbcbfb141c637ae3" +dependencies = [ + "drawille", + "rgb", +] + +[[package]] +name = "thiserror" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "win-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7b128a98c1cfa201b09eb49ba285887deb3cbe7466a98850eb1adabb452be5" +dependencies = [ + "windows", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/content/posts/icc/code/Cargo.toml b/content/posts/icc/code/Cargo.toml new file mode 100644 index 0000000..cf2df37 --- /dev/null +++ b/content/posts/icc/code/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "code" +version = "0.1.0" +edition = "2021" + +[dependencies] +core_affinity = "0.8.1" +rand = "0.8.5" +ma_timing = {path = "/home/ponet/Software/rustdev/ma_timing/crates/ma_timing"} +ma_time = {path = "/home/ponet/Software/rustdev/ma_timing/crates/ma_time"} +ma_queues= {path = "/home/ponet/Software/rustdev/ma_ipc/crates/ma_queues"} +ctrlc = "3.4.4" +[dev-dependencies] +criterion = "*" +quanta = "*" +[[bin]] +name = "main" +path = "src/main.rs" +[[bench]] +name = "seqlock" +harness = false diff --git a/content/posts/icc/code/benches/seqlock.rs b/content/posts/icc/code/benches/seqlock.rs new file mode 100644 index 0000000..e3714e1 --- /dev/null +++ b/content/posts/icc/code/benches/seqlock.rs @@ -0,0 +1,345 @@ +use std::{ + arch::x86_64::__rdtscp, + sync::{self, atomic::{AtomicUsize, Ordering}, Arc}, + time::Duration, +}; + +use core_affinity::CoreId; +use criterion::{criterion_group, criterion_main, Bencher, BenchmarkId, Criterion, SamplingMode}; +use rand::Rng; + +fn write_bench(b: &mut Bencher, n_contenders: usize) { + std::thread::scope(|s| { + let lock = Arc::new(code::SeqLock::default()); + for i in 0..n_contenders { + let lock2 = lock.clone(); + s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 2 * i + 3 }); + let mut m = code::Message::::default(); + loop { + lock2.read(&mut m); + if m[0] == 1 && m[1] == 2 { + break; + } + } + }); + } + std::thread::sleep(Duration::from_millis(1)); + s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 1 }); + let mut m = code::Message::::default(); + let mut c = 0usize; + b.iter(|| { + c = c.wrapping_add(1); + m[0] = c; + m[1] = c; + lock.write(&m) + }); + m[0] = 1; + for i in 1..N_BYTES { + m[i] = 2; + } + lock.write(&m); + }); + }); +} + +fn write(c: &mut Criterion) { + for n_readers in 0..8 { + let mut group = c.benchmark_group(format!("write_{}_readers", n_readers)); + for p in 4..=12 { + let size = 2usize.pow(p); + group.throughput(criterion::Throughput::Bytes(size as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(size), + &size, + |b, &size| match size { + 16 => { + write_bench::<2>(b, n_readers); + } + 32 => { + write_bench::<4>(b, n_readers); + } + 64 => { + write_bench::<8>(b, n_readers); + } + 128 => { + write_bench::<16>(b, n_readers); + } + 256 => { + write_bench::<32>(b, n_readers); + } + 512 => { + write_bench::<64>(b, n_readers); + } + 1024 => { + write_bench::<128>(b, n_readers); + } + 2048 => { + write_bench::<256>(b, n_readers); + } + 4096 => { + write_bench::<512>(b, n_readers); + } + _ => {} + }, + ); + } + group.finish(); + } +} + +fn read_bench(b: &mut Bencher, n_contenders: usize) { + + b.iter_custom(|iters| { + std::thread::scope(|s| { + let clock = quanta::Clock::new(); + clock.now(); + let lock = Arc::new(code::SeqLock::default()); + let done = Arc::new(sync::atomic::AtomicBool::new(false)); + let done1 = done.clone(); + let lock1 = lock.clone(); + let lock2 = lock.clone(); + for i in 1..n_contenders { + let lock2 = lock.clone(); + s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 2 * i + 3 }); + let mut m = code::Message::::default(); + loop { + lock2.read(&mut m); + if m[0] == 1 && m[1] == 2 { + break; + } + } + }); + } + let out = s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 3 }); + let lck = lock2.as_ref(); + let mut m = code::Message::::default(); + let mut avg_lat = 0; + let mut last = 0; + for i in 0..iters { + loop { + let curt = rdtscp(); + let c = lck.read(&mut m); + let delta = rdtscp() - curt; + if m.data[0] != last && c == 2{ + last = m.data[0]; + avg_lat += delta; + break; + } + } + } + done.store(true, Ordering::Relaxed); + Duration::from_nanos(avg_lat*10/33) + }); + s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 1 }); + let mut m = code::Message::::default(); + let mut c = 0usize; + let mut rng = rand::thread_rng(); + let lck = lock1.as_ref(); + loop { + lck.write(&m); + let last_write = rdtscp(); + c = c.wrapping_add(1); + m.data.fill(c); + if done1.load(sync::atomic::Ordering::Relaxed) { + break; + } + while rdtscp() - last_write < 330 {} + } + m[0] = 1; + m[1] = 2; + lck.write(&m); + }); + out.join().unwrap() + }) + }); +} + +fn read(c: &mut Criterion) { + for n_readers in 0..8 { + let mut group = c.benchmark_group(format!("read_{}_readers", n_readers)); + for p in 4..=12 { + let size = 2usize.pow(p); + group.throughput(criterion::Throughput::Bytes(size as u64)); + // group.sampling_mode(SamplingMode::Flat); + group.bench_with_input( + BenchmarkId::from_parameter(size), + &size, + |b, &size| match size { + 16 => { + read_bench::<2>(b, n_readers); + } + 32 => { + read_bench::<4>(b, n_readers); + } + 64 => { + read_bench::<8>(b, n_readers); + } + 128 => { + read_bench::<16>(b, n_readers); + } + 256 => { + read_bench::<32>(b, n_readers); + } + 512 => { + read_bench::<64>(b, n_readers); + } + 1024 => { + read_bench::<128>(b, n_readers); + } + 2048 => { + read_bench::<256>(b, n_readers); + } + 4096 => { + read_bench::<512>(b, n_readers); + } + _ => {} + }, + ); + } + group.finish(); + } +} + +#[derive(Clone, Copy)] +struct TimingMessage { + rdtscp: u64, + data: [u8; N], +} +fn rdtscp() -> u64 { + unsafe { __rdtscp(&mut 0u32 as *mut _) } +} +impl Default for TimingMessage { + fn default() -> Self { + Self { + rdtscp: 0, + data: [0; N], + } + } +} + +fn latency_bench(b: &mut Bencher, n_contenders: usize) { + b.iter_custom(|iters| { + std::thread::scope(|s| { + let clock = quanta::Clock::new(); + clock.now(); + + let lock = Arc::new(code::SeqLock::default()); + let done = Arc::new(sync::atomic::AtomicBool::new(false)); + let done1 = done.clone(); + let lock1 = lock.clone(); + let lock2 = lock.clone(); + let out = s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 3 }); + let lck = lock2.as_ref(); + let mut m = TimingMessage { + rdtscp: 0, + data: [0u8; N_BYTES], + }; + let mut last_t = 0; + let mut avg_lat = 0; + for i in 0..iters { + loop { + lck.read(&mut m); + let now = rdtscp(); + if m.rdtscp != last_t { + last_t = m.rdtscp; + avg_lat += now - m.rdtscp; + break; + } + } + } + Duration::from_nanos(clock.delta_as_nanos(0, avg_lat)) + }); + done.store(true, Ordering::Relaxed); + s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 1 }); + let mut m = TimingMessage { + rdtscp: 0, + data: [0u8; N_BYTES], + }; + let mut c = 0u8; + let lck = lock1.as_ref(); + loop { + m.data.fill(c); + m.rdtscp = rdtscp(); + lck.write(&m); + c = c.wrapping_add(1); + if done1.load(sync::atomic::Ordering::Relaxed) { + break; + } + // std::thread::sleep(Duration::from_micros(2)); + } + m.data[0] = 1; + for i in 1..N_BYTES { + m.data[i] = 2; + } + lck.write(&m); + }); + + for i in 1..n_contenders { + let lock2 = lock.clone(); + s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 2 * i + 3 }); + let mut m = TimingMessage { + rdtscp: 0, + data: [0u8; N_BYTES], + }; + loop { + lock2.read(&mut m); + if m.data[0] == 1 && m.data[i] == 2 { + break; + } + } + }); + } + out.join() + }) + .unwrap() + }); +} + +fn latency(c: &mut Criterion) { + for n_readers in 0..8 { + let mut group = c.benchmark_group(format!("latency_{}_readers", n_readers)); + for size in [8, 30, 32, 60, 124, 252, 508, 1020, 2044, 4092].iter() { + group.throughput(criterion::Throughput::Bytes(*size as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(size), + size, + |b, &size| match size { + 8 => { + latency_bench::<8>(b, n_readers); + } + 30 => { + latency_bench::<30>(b, n_readers); + } + 32 => { + latency_bench::<32>(b, n_readers); + } + 60 => { + latency_bench::<60>(b, n_readers); + } + 124 => { + latency_bench::<124>(b, n_readers); + } + 252 => { + latency_bench::<252>(b, n_readers); + } + _ => {} + }, + ); + } + group.finish(); + } +} +criterion_group! { + name=seqlock; + config=Criterion::default().sample_size(2000).measurement_time(std::time::Duration::from_secs(10)); + targets = write, read, latency +} +criterion_main!(seqlock); diff --git a/content/posts/icc/code/src/lib.rs b/content/posts/icc/code/src/lib.rs new file mode 100644 index 0000000..3538b13 --- /dev/null +++ b/content/posts/icc/code/src/lib.rs @@ -0,0 +1,129 @@ +use std::arch::x86_64::_mm_pause; +use std::slice::SliceIndex; +use std::cell::UnsafeCell; +use std::sync::atomic::{compiler_fence, fence, AtomicUsize, Ordering}; +#[inline] +#[cold] +fn cold() {} + +#[inline] +fn likely(b: bool) -> bool { + if !b { cold() } + b +} + +#[inline] +fn unlikely(b: bool) -> bool { + if b { cold() } + b +} +#[derive(Default)] +pub struct SeqLock { + version: AtomicUsize, + data: UnsafeCell, +} +unsafe impl Send for SeqLock {} +unsafe impl Sync for SeqLock {} + +impl SeqLock { + pub fn new(data: T) -> Self { + Self {version: AtomicUsize::new(0), data: UnsafeCell::new(data)} + } + #[inline(never)] + pub fn read(&self, result: &mut T) { + loop { + let v1 = self.version.load(Ordering::Acquire); + compiler_fence(Ordering::AcqRel); + *result = unsafe { *self.data.get() }; + compiler_fence(Ordering::AcqRel); + let v2 = self.version.load(Ordering::Acquire); + if v1 == v2 && v1 & 1 == 0 { + return; + } + } + } + + // #[inline(always)] + // pub fn write(&self, val: &T) { + // let v = self.version.fetch_add(1, Ordering::Relaxed).wrapping_add(2); + // unsafe { *self.data.get() = *val }; + // self.version.store(v, Ordering::Relaxed); + // } + // #[inline(never)] + // pub fn write(&self, val: &T) { + // let v = self.version.fetch_add(1, Ordering::Release); + // compiler_fence(Ordering::AcqRel); + // unsafe { *self.data.get() = *val }; + // compiler_fence(Ordering::AcqRel); + // self.version.store(v.wrapping_add(2), Ordering::Release); + // } + #[inline(never)] + pub fn write(&self, val: &T) { + let v = self.version.fetch_add(1, Ordering::Release); + compiler_fence(Ordering::AcqRel); + unsafe { *self.data.get() = *val }; + compiler_fence(Ordering::AcqRel); + self.version.store(v.wrapping_add(2), Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{sync::atomic::AtomicBool, time::{Duration, Instant}}; + + fn read_test() + { + let lock = SeqLock::new([0usize; N]); + let done = AtomicBool::new(false); + std::thread::scope(|s| { + s.spawn(|| { + let mut msg = [0usize; N]; + while !done.load(Ordering::Relaxed) { + lock.read(&mut msg); + let first = msg[0]; + for i in msg { + assert_eq!(first, i); // data consistency is verified here + } + } + assert_ne!(msg[0], 0) + }); + s.spawn(|| { + let curt = Instant::now(); + let mut count = 0; + let mut msg = [0usize; N]; + while curt.elapsed() < Duration::from_secs(1) { + msg.fill(count); + lock.write(&msg); + count = count.wrapping_add(1); + } + done.store(true, Ordering::Relaxed); + }); + + }); + } + + #[test] + fn read_16() { + read_test::<16>() + } + #[test] + fn read_32() { + read_test::<32>() + } + #[test] + fn read_64() { + read_test::<64>() + } + #[test] + fn read_128() { + read_test::<128>() + } + #[test] + fn read_large() { + read_test::<{2usize.pow(16)}>() + } +} + + + diff --git a/content/posts/icc/code/src/main.rs b/content/posts/icc/code/src/main.rs new file mode 100644 index 0000000..4baa101 --- /dev/null +++ b/content/posts/icc/code/src/main.rs @@ -0,0 +1,201 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Barrier}; +use std::arch::x86_64::{__rdtscp, _mm_lfence}; +use std::time::Duration; + +use code::SeqLock; +// use ma_queues::seqlock::SeqLock; +use core_affinity::CoreId; +use ma_time::{Instant, Nanos}; +use ma_timing::Timer; +use rand::Rng; + +#[derive(Clone, Copy)] +#[repr(C)] +struct TimingMessage { + rdtscp: Instant, + data: [u8;N] +} +impl Default for TimingMessage { + fn default() -> Self { + Self { + rdtscp: Instant::default(), + data: [0; N], + } + } +} +fn rdtscp() -> u64 { + unsafe { __rdtscp(&mut 0u32 as *mut _) } +} + + +// const N: usize = 1; +// #[inline(never)] +// fn reader(done: &AtomicBool, lock: &SeqLock<[usize;N]>) -> usize { +// core_affinity::set_for_current(CoreId { id: 1 }); +// let mut msg = [0usize; N]; +// let mut t = 0; +// while !done.load(Ordering::Relaxed) { +// lock.read(&mut msg); +// let first = msg[0]; +// for i in msg { +// if first != i { +// t += 1; +// }; // data consistency is verified here +// } +// } +// return t; +// } +// #[inline(never)] +// fn writer(done: &AtomicBool, lock: &SeqLock<[usize;N]>) { +// core_affinity::set_for_current(CoreId { id: 10 }); +// let curt = Instant::now(); +// let mut count = 0usize; +// let mut msg = [0usize; N]; +// let mut msg1 = [0usize; N]; + +// while curt.elapsed() < Nanos::from_secs(1) { +// lock.write(&msg); +// lock.write(&msg1); +// count = count.wrapping_add(1); +// msg.fill(count); +// count = count.wrapping_add(1); +// msg1.fill(count); +// std::thread::sleep(std::time::Duration::from_micros(10) ); +// } +// done.store(true, Ordering::Relaxed); +// } + +// pub fn main() { +// let lock = SeqLock::new([0usize; N]); +// let done = AtomicBool::new(false); +// std::thread::scope(|s| { +// s.spawn(|| { +// println!("{}", reader(&done, &lock)); +// }); +// s.spawn(|| { +// writer(&done, &lock) +// }); + +// }); +// } +fn consumer_latency(n_contenders: usize){ + std::thread::scope(|s| { + let ctrlcd = Arc::new(AtomicBool::new(false)); + let ctrlcd1 = ctrlcd.clone(); + ctrlc::set_handler(move || { + ctrlcd1.store(true, std::sync::atomic::Ordering::Relaxed); + }); + let lock = Arc::new(SeqLock::default()); + let lock1 = lock.clone(); + for i in 1..(n_contenders+1) { + let lock2 = lock.clone(); + let ctrlcd2 = ctrlcd.clone(); + s.spawn(move || { + core_affinity::set_for_current(CoreId { id: 2 * i + 3 }); + let mut m = TimingMessage::::default(); + while !ctrlcd2.load(Ordering::Relaxed) { + let m = lock2.read(&mut m); + } + }); + } + let ctrlcd3 = ctrlcd.clone(); + s.spawn(move || { + let mut timer = Timer::new("my_cool_timer"); + core_affinity::set_for_current(CoreId { id: 8 }); + let lck = lock.as_ref(); + let mut rng = rand::thread_rng(); + let mut m = TimingMessage::::default(); + let mut last = Instant::now(); + while !ctrlcd3.load(Ordering::Relaxed) { + let t = Instant::now(); + unsafe{_mm_lfence()}; + lck.read(&mut m); + let t2 = Instant::now(); + if m.rdtscp != last { + timer.set_start(t); + timer.set_stop(t2); + timer.send_cur(); + timer.latency_till_stop(m.rdtscp); + let t = rng.gen_range(1000..3300); + let curt = rdtscp(); + while rdtscp() - curt < t {} + } + last = m.rdtscp; + } + }); + s.spawn(move || { + let mut timer = Timer::new("write"); + core_affinity::set_for_current(CoreId { id: 6 }); + let mut m = TimingMessage::::default(); + let lck = lock1.as_ref(); + while !ctrlcd.load(Ordering::Relaxed) { + timer.start(); + m.rdtscp = Instant::now(); + lck.write(&m); + timer.stop(); + // let last_write = rdtscp(); + // let t = rng.gen_range(1000..3300); + let curt = rdtscp(); + while rdtscp() - curt < 33000 {} + } + }); + }) +} + +const PING: bool = false; +const PONG: bool = true; +fn cas_baseline(){ + let done = AtomicBool::new(false); + let flag = AtomicBool::new(PING); + let barrier = Barrier::new(2); + println!("starting"); + std::thread::scope(|s| { + s.spawn(|| { + core_affinity::set_for_current(CoreId { id: 8}); + + barrier.wait(); + while !done.load(Ordering::Relaxed){ + while flag.compare_exchange(PING, PONG, Ordering::Relaxed, Ordering::Relaxed).is_err(){ + if done.load(Ordering::Relaxed){break} + } + } + println!("pingpong done"); + }); + s.spawn(|| { + let mut timer = Timer::new("cas_round_trip"); + core_affinity::set_for_current(CoreId { id: 6}); + barrier.wait(); + while !done.load(Ordering::Relaxed) { + timer.start(); + let curt = std::time::Instant::now(); + for _ in 0..1000 { + while flag.compare_exchange(PONG, PING, Ordering::Relaxed, Ordering::Relaxed).is_err() { + if done.load(Ordering::Relaxed){ + break; + } + } + } + println!("{:?}", curt.elapsed()); + timer.stop(); + } + println!("pongping done"); + }); + std::thread::sleep(Duration::from_secs(2)); + done.store(true, Ordering::Relaxed); + }); +} + +pub fn main() { + // consumer_latency::<1>(0); + cas_baseline(); + std::thread::sleep(Duration::from_secs(2)); + cas_baseline(); + std::thread::sleep(Duration::from_secs(5)); + cas_baseline(); + std::thread::sleep(Duration::from_secs(6)); + cas_baseline(); + std::thread::sleep(Duration::from_secs(6)); + cas_baseline(); +} + diff --git a/content/posts/icc/index.md b/content/posts/icc/index.md new file mode 100644 index 0000000..36c6a6a --- /dev/null +++ b/content/posts/icc/index.md @@ -0,0 +1,320 @@ ++++ +title = "Inter Core Communication" +date = 2024-05-14 +description = "A deep dive on the components that comprise the inter core communication layer in Mantra: Seqlocks, message Queues and SeqlockedVectors" +[taxonomies] +tags = ["mantra", "icc", "seqlock"] ++++ + +In this first technical blog post we will take a closer look at some inter core communication and data synchronization components that form the most fundamental layer of [**Mantra**](@/posts/hello_world/index.md). + +I have taken a great deal of inspiration from: +- [Trading at light speed](https://www.youtube.com/watch?v=8uAW5FQtcvE) by David Gross +- An amazing set of references: [Awesome Lockfree](https://github.com/rigtorp/awesome-lockfree) by Erik Rigtorp +# Design Goals and Considerations + +- Achieve a close to the ideal ~30ns core-to-core latency (see e.g. [anandtech 13900k and 13600k review](https://www.anandtech.com/show/17601/intel-core-i9-13900k-and-i5-13600k-review/5) and the [fantastic core-to-core-latency tool](https://github.com/nviennot/core-to-core-latency)) +- _Producers_ are favored, i.e. they do not care about and are not impacted by data _Consumers_ +- _Consumers_ should not impact eachother, or the system as a whole +- Every _Consumer_ gets access to all data, in the case of message _Queues_ this is also known as *broadcast* mode + +We go into further detail on how each of these points impacted the implementation of the various components. + +# Seqlock +Considering the above goals, the `Seqlock` (see [Wikipedia](https://en.wikipedia.org/wiki/Seqlock), [seqlock in the linux kernel](https://docs.kernel.org/locking/seqlock.html), and [Erik Rigtorp's C++11 implementation](https://github.com/rigtorp/Seqlock)) + +Rather than regurgitating the same insights as in these stellar references, let me break it down to the essentials: +- A _Producer_ (or writer) is never blocked by _Consumers_ (readers) +- The _Producer_ atomically increments a counter (the _sequence_) once before and once after writing the data +- `counter & 1 == 0` (even) communicates to _Consumers_ that they can read data +- `counter_before_read == counter_after_read`: data was read consistently +- Compare and swap can be used on the counter to allow multiple _Producers_ to write to same _SeqLock_ (we won't use it, later more on why) +- Depending on the architecture and compiler, it's crucial to verify that the sequence of operations are not reordered: memory barriers/fences are usually required + +Let's try to design some tests to validate a _SeqLock_ implementation, focusing mainly on the last point. + +## Naive starting point +```rust,linenos +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::cell::UnsafeCell; + +pub struct SeqLock { + version: AtomicUsize, + data: UnsafeCell, +} + +impl SeqLock { + pub fn read(&self, result: &mut T) { + loop { + let v1 = self.version.load(Ordering::Relaxed); + *result = unsafe { *self.data.get() }; + let v2 = self.version.load(Ordering::Relaxed); + if v1 == v2 && v1 & 1 == 0 { + return; + } + } + } + + pub fn write(&self, val: &T) { + let v = self.version.load(Ordering::Relaxed).wrapping_add(1); + self.version.store(v, Ordering::Relaxed); + unsafe { *self.data.get() = *val }; + self.version.store(v.wrapping_add(1), Ordering::Relaxed); + } +} +``` +Before discussing what may or may not be wrong with this implementation, let's design some tests to verify that it does what we think it does. +We use a fixed size array `msg` of different sizes as our message. We have one `Consumer` thread who does the actual data verification, and one `Producer` thread that continuously writes new data by incrementing `count` and filling `msg` with it: +```rust,linenos,hl_lines=17 +#[cfg(test)] +mod tests { + use super::*; + use std::{sync::atomic::AtomicBool, time::{Duration, Instant}}; + + fn read_test() + { + let lock = SeqLock::new([0usize; N]); + let done = AtomicBool::new(false); + std::thread::scope(|s| { + s.spawn(|| { + let mut msg = [0usize; N]; + while !done.load(Ordering::Relaxed) { + lock.read(&mut msg); + let first = msg[0]; + for i in msg { + assert_eq!(first, i); // data consistency is verified here + } + } + }); + s.spawn(|| { + let curt = Instant::now(); + let mut count = 0; + let mut msg = [0usize; N]; + while curt.elapsed() < Duration::from_secs(1) { + msg.fill(count); + lock.write(&msg); + count = count.wrapping_add(1); + } + done.store(true, Ordering::Relaxed); + }); + + }); + } + + #[test] + fn read_16() { + read_test::<16>() + } + #[test] + fn read_32() { + read_test::<32>() + } + #[test] + fn read_64() { + read_test::<64>() + } + #[test] + fn read_128() { + read_test::<128>() + } + #[test] + fn read_large() { + read_test::<65536>() + } +} +``` +Data consistency (i.e. no partially written data was read) is tested by verifying that all elements in `msg` are the same as the first. +On my intel i9 14900k these tests fail for array sizes of 64 and up (512 bytes). This signals that either the compiler or the cpu did some reordering of operations. +In fact, on x86 it can only be due to reordering by the compiler (see the [Release-Acquire ordering paragraph in the c++ reference](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering)). + +In this case the issue is that we allow the compiler to inline our `read` function and it actually reorders the `let first = msg[0]` line to be executed before +the `read`, causing obvious problems. Just to highlight how fickle inlining is, adding `assert_ne!(msg[0], 0)` at the very end of the `Consumer` function makes all tests pass. + +One solution is to make either `self.version.load` functions use the `Ordering::Acquire` since that forces the compiler to not reorder the sequence of operations accross this barrier. +For now we just add `#[inline(never)]` to the `read` function, and it is best we do the same for the `write` function. This minimizes the "compiler-handholding" surface area. + +## Deeper dive using [`cargo asm`](https://crates.io/crates/cargo-show-asm/0.2.34) +This is for sure one of the best tools for this kind of analysis. I highly recommend adding `#[inline(never)]` to any function you are planning to analyze. + +### `SeqLock::<[usize; 1024]>::read` + +```asm, linenos, hl_lines=19 22 26 27 28 30 +code::SeqLock::read: + .cfi_startproc + push r15 + .cfi_def_cfa_offset 16 + push r14 + .cfi_def_cfa_offset 24 + push r12 + .cfi_def_cfa_offset 32 + push rbx + .cfi_def_cfa_offset 40 + push rax + .cfi_def_cfa_offset 48 + .cfi_offset rbx, -40 + .cfi_offset r12, -32 + .cfi_offset r14, -24 + .cfi_offset r15, -16 + mov rbx, rsi + mov r14, rdi + mov r15, qword ptr [rip + memcpy@GOTPCREL] + .p2align 4, 0x90 +.LBB6_1: + mov r12, qword ptr [r14 + 8192] + mov edx, 8192 + mov rdi, rbx + mov rsi, r14 + call r15 + mov rax, qword ptr [r14 + 8192] + test r12b, 1 + jne .LBB6_1 + cmp r12, rax + jne .LBB6_1 + add rsp, 8 + .cfi_def_cfa_offset 40 + pop rbx + .cfi_def_cfa_offset 32 + pop r12 + .cfi_def_cfa_offset 24 + pop r14 + .cfi_def_cfa_offset 16 + pop r15 + .cfi_def_cfa_offset 8 + ret +``` +First thing we can observe in lines (19, 22, 27) is that `rust` chose to not adhere to our ordering of fields in `SeqLock`, i.e. it moved `version` behind `data`. +For now this is fine, but in the future we will annotate the struct with `#[repr(C)]` to at least keep field order. + +From the point of view of the `SeqLock` implementation, the important lines are highlighted. These correspond pretty much exactly with the `read` function: +1. assign function pointer to `memcpy` to `r15` for faster future calling +2. move `version` at `SeqLock start (r14) + 8192 bytes` into `r12` +3. perform the `memcpy` +4. move `version` at `SeqLock start (r14) + 8192 bytes` into `rax` +5. check `r12 & 1 == 0` +6. check `r12 == rax` +7. Profit... + +### `SeqLock::<[usize; 1]>::read` +```asm, linenos +code::SeqLock::read: + .cfi_startproc + mov rax, qword ptr [rdi + 8] + .p2align 4, 0x90 +.LBB6_1: + mov rcx, qword ptr [rdi] + mov rdx, qword ptr [rdi] + test cl, 1 + jne .LBB6_1 + cmp rcx, rdx + jne .LBB6_1 + mov qword ptr [rsi], rax + ret +``` +Well at least it looks clean... I'm pretty sure I don't have to underline the issue with +1. Do the copy into `rax` +2. move the **exact same version** into `rcx` and `rdx` +3. test like before +4. copy from `rax` into the input +5. No Stonks... + +My earlier tests clearly didn't catch everything, in fact I never got them to fail after adding the `#[inline(never)]`. +I think the reason is that for small enough data, the `memcpy` is done **inline/in cache** with moves into and out of different registers (`rax` in this case) and +that it is extremely unlikely that the cache gets invalidated/overwritten during these operations. + +### Adding Memory Barriers +In any case, I guess it's time to add some barriers to stop the compiler from shooting us in the foot: +```rust, linenos +#[inline(never)] +pub fn read(&self, result: &mut T) { + loop { + let v1 = self.version.load(Ordering::Acquire); + compiler_fence(Ordering::AcqRel); + *result = unsafe { *self.data.get() }; + compiler_fence(Ordering::AcqRel); + let v2 = self.version.load(Ordering::Acquire); + if v1 == v2 && v1 & 1 == 0 { + return; + } + } +} +``` +```asm, linenos +code::SeqLock::read: + .cfi_startproc + .p2align 4, 0x90 +.LBB6_1: + mov rax, qword ptr [rdi] + #MEMBARRIER + mov rcx, qword ptr [rdi + 8] + mov qword ptr [rsi], rcx + #MEMBARRIER + mov rcx, qword ptr [rdi] + test al, 1 + jne .LBB6_1 + cmp rax, rcx + jne .LBB6_1 + ret +``` +It is funny that the compiler chooses to reuse `rcx` both for the data copy in lines `(5, 6)`, as well as the second version load in line `8`. + +With the current `rust` compiler (1.78.0), adding either `Ordering::Acquire` in lines `4` or `7` does the trick. +However, technically they only influence the ordering of loads of the `version` Atomic when combined with an `Ordering::Release` store in the `write` function. The compiler is still theoretically allowed to reorder the actual copying of the data. +The compiler fences should forever block this reordering, and on x86 that should be enough (again, see the [Release-Acquire ordering paragraph in the c++ reference](https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering)). + +Bearing these things in mind, we add the corresponding barriers to the `write` function: +```rust + #[inline(never)] + pub fn write(&self, val: &T) { + let v = self.version.load(Ordering::Relaxed).wrapping_add(1); + self.version.store(v, Ordering::Release); + compiler_fence(Ordering::AcqRel); + unsafe { *self.data.get() = *val }; + compiler_fence(Ordering::AcqRel); + self.version.store(v.wrapping_add(1), Ordering::Release); + } +``` +For our purposes this should do the trick, and we will now move on to timing and potentially optimizing our implementation. However, if memory models and barriers are really your schtick, live a little and marvel your way through [The Linux Kernel Docs on Memory Barriers](https://docs.kernel.org/core-api/wrappers/memory-barriers.html). + +# Performance +Now that the implementation behaves correctly, we can finally do some juicy performance measuring. + +## Timing 101 +The full details regarding the suite of timing and performance measurements tools I have developed to track the performance of **Mantra** will be divulged in a later post. +For the purposes here the main points are: + +**Use `rdtscp` to take timestamps**: the `rdtscp` hardware counter in most recent cpus is a monotonously increasing cpu cycle counter (at base frequency) which is reset upon startup. What's even better is that on recent cpus it is shared between all cores (look for `constant_tsc` in `/proc/cpuinfo`). It is the cheapest, at ~5ns overhead, and most precise way to take timestamps. An added benefit is that it partially orders operations (see discussion above), in that it will not execute until _"all previous instructions have executed and all previous loads are globally visible"_ see [this](https://www.felixcloutier.com/x86/rdtscp). Using an `_mm_lfence` after the initial `rdtscp` will also force executions to not begin before the timestamp is taken. This is **the only reasonable way** to time on really low latency scales. + +**use [`core_affinity`](https://docs.rs/core_affinity/latest/core_affinity/) and `isolcpus`**: The combination of the [`isolcpus`](https://wiki.linuxfoundation.org/realtime/documentation/howto/tools/cpu-partitioning/isolcpus) kernel parameter with binding a thread in `rust` to a specific core allows us to minimize jitter coming from whatever else is running on the computer. I have isolated performance cpus 0-9 for testing purposes. + +**Offload the actual timing**: To minimize the timing overhead we take the two `rdtscp` stamps and offload them to a `Queue` in shared memory (more on what a `Queue` is later). Another process can then read these messages, collect statistics and convert `rdtscp` stamp deltas to nanoseconds (in the i9 14900k case x3.2). For this last step we can actually reuse the [`nanos_from_raw_delta`](https://docs.rs/quanta/latest/quanta/struct.Clock.html#method.delta_as_nanos) from the `quanta` library. + + +Putting all of this together, timing a block of code behind the scenes essentially looks like: +```rust +let t1 = unsafe { __rdtscp(&mut 0u32 as *mut _) }; +unsafe { _mm_lfence() }; +// code to be timed +let t2 = unsafe { __rdtscp(&mut 0u32 as *mut _) }; +timer_queue.produce((t1, t2)); +``` +using my timing library, +```rust +let mut timer = Timer::new("my_cool_timer"); +timer.start(); +//code to be timed +timer.stop(); +``` +with an added functionality where you can use a previously taken `rdtscp` timestamp to measure a `latency`: +```rust +timer.latency(prev_rdtscp); +``` +This will come in handy in our performance measurements. The former will be called `Business` timing (for business logic), and the latter, you guessed it, `Latency` timing. + +The `timekeeper` tui then picks up on these timers and will display a continuously updated graph of timings (take a moment to familiarise yourself): + +![](timekeeper_example.png#noborder "ui") + +## Baseline Inter Core Latency + +# SPMC/MPMC Message Queues +# Seqlocked Buffers diff --git a/content/posts/icc/timekeeper_example.png b/content/posts/icc/timekeeper_example.png new file mode 100644 index 0000000000000000000000000000000000000000..062f149d784367cdf492a90541d3321f27286588 GIT binary patch literal 96205 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

A`$C(niL8ngEVBx1Ig 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 II###-ALWxNZ2$lO literal 0 HcmV?d00001 diff --git a/sass/parts/_code.scss b/sass/parts/_code.scss index 7c5758e..feb0985 100644 --- a/sass/parts/_code.scss +++ b/sass/parts/_code.scss @@ -110,4 +110,4 @@ pre code[class*="language-rs"]::before { content: "rust"; background: #fff8f6; color: #ff4647; -} \ No newline at end of file +} diff --git a/sass/parts/_misc.scss b/sass/parts/_misc.scss index 77ffeab..53220da 100644 --- a/sass/parts/_misc.scss +++ b/sass/parts/_misc.scss @@ -26,6 +26,11 @@ p { line-height: 1.5; } +mark { + background: #26343b!important; + color: #fff8f6; +} + hr { border: 0; border-top: 3px solid var(--border-color); @@ -112,4 +117,4 @@ time { &:hover { background-color: transparent; } -} \ No newline at end of file +} diff --git a/sass/parts/_table.scss b/sass/parts/_table.scss index 247db0f..0319836 100644 --- a/sass/parts/_table.scss +++ b/sass/parts/_table.scss @@ -10,6 +10,6 @@ table th { } table td { - padding: 6px 13px; - border: 1px solid #dfe2e5; -} \ No newline at end of file + padding: 2px 4px; + border: 0px solid #dfe2e5; +}