diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0eaf0a3..52a7733 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,9 +20,9 @@ jobs: steps: - uses: hecrj/setup-rust-action@v1 with: - rust-version: nightly + rust-version: stable - uses: actions/checkout@v3 - name: Build - run: cargo build --verbose --features nightly + run: cargo build --verbose - name: Run tests - run: cargo test --verbose --features nightly + run: cargo test --verbose diff --git a/.gitignore b/.gitignore index ea8c4bf..96ef6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 061d697..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,612 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - -[[package]] -name = "cast" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags", - "textwrap", - "unicode-width", -] - -[[package]] -name = "criterion" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" -dependencies = [ - "atty", - "cast", - "clap", - "criterion-plot", - "csv", - "itertools", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_cbor", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" -dependencies = [ - "cast", - "itertools", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "once_cell", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "fast-counter" -version = "0.0.2" -dependencies = [ - "criterion", - "rayon", -] - -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "js-sys" -version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" -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.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" - -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "plotters" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" - -[[package]] -name = "plotters-svg" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "proc-macro2" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rayon" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "regex" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - -[[package]] -name = "regex-syntax" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "ryu" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" - -[[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.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "semver" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" -dependencies = [ - "itoa 1.0.2", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" - -[[package]] -name = "web-sys" -version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 5000e47..2be67e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,6 @@ repository = "https://github.com/JackThomson2/fast-counter" rayon = "1.3" criterion = "0.3" -[features] -default = [] -nightly = [] - [[bench]] name = "incrementer" harness = false @@ -26,4 +22,4 @@ harness = false panic = "abort" opt-level = 3 codegen-units = 1 -lto = "fat" \ No newline at end of file +lto = "fat" diff --git a/README.md b/README.md index cf0db8c..dec477d 100644 --- a/README.md +++ b/README.md @@ -19,31 +19,17 @@ atomic_counter/16 time: [410.49 us 411.71 us 412.99 us] ------------------------------------------------------------------------------ -fast_counter_nightly/2 time: [264.99 us 267.24 us 269.14 us] - thrpt: [121.75 Melem/s 122.61 Melem/s 123.66 Melem/s] - -fast_counter_nightly/4 time: [252.67 us 255.30 us 257.85 us] - thrpt: [127.08 Melem/s 128.35 Melem/s 129.68 Melem/s] - -fast_counter_nightly/8 time: [193.19 us 197.00 us 200.92 us] - thrpt: [163.09 Melem/s 166.34 Melem/s 169.62 Melem/s] - -fast_counter_nightly/16 time: [154.45 us 159.36 us 164.75 us] - thrpt: [198.90 Melem/s 205.63 Melem/s 212.16 Melem/s] - ------------------------------------------------------------------------------- - -fast_counter_stable/2 time: [299.28 us 300.40 us 301.52 us] +fast_counter/2 time: [299.28 us 300.40 us 301.52 us] thrpt: [108.68 Melem/s 109.08 Melem/s 109.49 Melem/s] -fast_counter_stable/4 time: [276.78 us 278.90 us 281.02 us] +fast_counter/4 time: [276.78 us 278.90 us 281.02 us] thrpt: [116.61 Melem/s 117.49 Melem/s 118.39 Melem/s] -fast_counter_stable/8 time: [194.94 us 199.44 us 204.17 us] +fast_counter/8 time: [194.94 us 199.44 us 204.17 us] thrpt: [160.49 Melem/s 164.30 Melem/s 168.10 Melem/s] -fast_counter_stable/16 time: [152.30 us 155.98 us 159.92 us] +fast_counter/16 time: [152.30 us 155.98 us 159.92 us] thrpt: [204.91 Melem/s 210.08 Melem/s 215.16 Melem/s] ``` -Big shoutout to @jimvdl who put the core starting point for this together \ No newline at end of file +Big shoutout to @jimvdl who put the core starting point for this together diff --git a/benches/incrementer.rs b/benches/incrementer.rs index e69e801..a01f3a9 100644 --- a/benches/incrementer.rs +++ b/benches/incrementer.rs @@ -1,12 +1,10 @@ -#![feature(thread_local)] - use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use rayon; use rayon::prelude::*; use std::sync::atomic::{AtomicIsize, Ordering}; -const ITER: isize = 32 * 1024; -const CORES_TO_USE: [usize; 4] = [2, 4, 8, 16]; +const ITER: isize = 1024 * 1024; +const CORES_TO_USE: [usize; 5] = [1, 2, 4, 8, 16]; fn atomic_counter(c: &mut Criterion) { let mut group = c.benchmark_group("atomic_counter"); @@ -37,36 +35,7 @@ fn atomic_counter(c: &mut Criterion) { group.finish(); } -use fast_counter::ConcurrentCounter as JackCounter; - -fn fast_counter(c: &mut Criterion) { - let mut group = c.benchmark_group("fast_counter_nightly"); - group.throughput(Throughput::Elements(ITER as u64)); - - for threads in CORES_TO_USE { - group.bench_with_input( - BenchmarkId::from_parameter(threads), - &threads, - |b, &threads| { - let pool = rayon::ThreadPoolBuilder::new() - .num_threads(threads) - .build() - .unwrap(); - pool.install(|| { - b.iter(|| { - let counter = JackCounter::new(threads); - (0..ITER).into_par_iter().for_each(|_| { - counter.add(1); - }); - assert_eq!(ITER, counter.sum()); - }) - }); - }, - ); - } -} - -use fast_counter::default::ConcurrentCounter as ConcurrentCounterTLMacro; +use fast_counter::ConcurrentCounter as ConcurrentCounterTLMacro; fn fast_counter_stable(c: &mut Criterion) { let mut group = c.benchmark_group("fast_counter_stable"); @@ -98,7 +67,6 @@ fn fast_counter_stable(c: &mut Criterion) { criterion_group!( benches, atomic_counter, - fast_counter, fast_counter_stable, ); criterion_main!(benches); diff --git a/src/counter.rs b/src/counter.rs new file mode 100644 index 0000000..109da6a --- /dev/null +++ b/src/counter.rs @@ -0,0 +1,247 @@ +use std::fmt; +use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; +use std::cell::Cell; + +use crate::utils::{CachePadded, make_new_padded_counter}; +use crate::safe_getters::SafeGetters; + +static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(1); + +thread_local! { + static THREAD_ID: Cell = Cell::new(THREAD_COUNTER.fetch_add(1, Ordering::SeqCst)); +} + +/// A sharded atomic counter +/// +/// ConcurrentCounter shards cacheline aligned AtomicIsizes across a vector for faster updates in +/// a high contention scenarios. +pub struct ConcurrentCounter { + cells: Vec>, +} + +impl fmt::Debug for ConcurrentCounter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ConcurrentCounter") + .field("sum", &self.sum()) + .field("cells", &self.cells.len()) + .finish() + } +} + +impl ConcurrentCounter { + /// Creates a new ConcurrentCounter with a minimum of the `count` cells. Concurrent counter + /// will align the `count` to the next power of two for better speed when doing the modulus. + /// + /// # Examples + /// + /// ``` + /// use fast_counter::ConcurrentCounter; + /// + /// let counter = ConcurrentCounter::new(10); + /// ``` + #[inline] + pub fn new(count: usize) -> Self { + let count = count.next_power_of_two(); + Self { + cells: (0..count) + .into_iter() + .map(|_| make_new_padded_counter()) + .collect(), + } + } + + #[inline] + fn thread_id(&self) -> usize { + THREAD_ID.with(|id| { + id.get() + }) + } + + /// Adds the value to the counter, internally with is using `add_with_ordering` with a + /// `Ordering::Relaxed` and is mainly for convenience. + /// + /// ConcurrentCounter will identify a cell to add the `value` too with using a thread_local + /// which will try to aleviate the contention on a single number + /// + /// # Examples + /// + /// ``` + /// use fast_counter::ConcurrentCounter; + /// + /// let counter = ConcurrentCounter::new(10); + /// counter.add(1); + /// counter.add(-1); + /// ``` + #[inline] + pub fn add(&self, value: isize) { + self.add_with_ordering(value, Ordering::Relaxed) + } + + /// ConcurrentCounter will identify a cell to add the `value` too with using a thread_local + /// which will try to aleviate the contention on a single number. The cell will be updated + /// atomically using the ordering provided in `ordering` + /// + /// # Examples + /// + /// ``` + /// use fast_counter::ConcurrentCounter; + /// use std::sync::atomic::Ordering; + /// + /// let counter = ConcurrentCounter::new(10); + /// counter.add_with_ordering(1, Ordering::SeqCst); + /// counter.add_with_ordering(-1, Ordering::Relaxed); + /// ``` + #[inline] + pub fn add_with_ordering(&self, value: isize, ordering: Ordering) { + let c = self.cells.safely_get(self.thread_id() & (self.cells.len() - 1)); + c.value.fetch_add(value, ordering); + } + + /// This will fetch the sum of the concurrent counter be iterating through each of the cells + /// and loading the values. Internally this uses `sum_with_ordering` with a `Relaxed` ordering. + /// + /// Due to the fact the cells are sharded and the concurrent nature of the library this sum + /// may be slightly inaccurate. For example if used in a concurrent map and using + /// ConcurrentCounter to track the length, depending on the ordering the length may be returned + /// as a negative value. + /// + /// # Examples + /// + /// ```rust + /// use fast_counter::ConcurrentCounter; + /// + /// let counter = ConcurrentCounter::new(10); + /// + /// counter.add(1); + /// + /// let sum = counter.sum(); + /// + /// assert_eq!(sum, 1); + /// ``` + #[inline] + pub fn sum(&self) -> isize { + self.sum_with_ordering(Ordering::Relaxed) + } + + /// This will fetch the sum of the concurrent counter be iterating through each of the cells + /// and loading the values with the ordering defined by `ordering`. + /// + /// Due to the fact the cells are sharded and the concurrent nature of the library this sum + /// may be slightly inaccurate. For example if used in a concurrent map and using + /// ConcurrentCounter to track the length, depending on the ordering the length may be returned + /// as a negative value. + /// + /// # Examples + /// + /// ```rust + /// use std::sync::atomic::Ordering; + /// use fast_counter::ConcurrentCounter; + /// + /// let counter = ConcurrentCounter::new(10); + /// + /// counter.add(1); + /// + /// let sum = counter.sum_with_ordering(Ordering::SeqCst); + /// + /// assert_eq!(sum, 1); + /// ``` + #[inline] + pub fn sum_with_ordering(&self, ordering: Ordering) -> isize { + self.cells.iter().map(|c| c.value.load(ordering)).sum() + } +} + +#[cfg(test)] +mod tests { + use crate::ConcurrentCounter; + + #[test] + fn basic_test() { + let counter = ConcurrentCounter::new(1); + counter.add(1); + assert_eq!(counter.sum(), 1); + } + + #[test] + fn increment_multiple_times() { + let counter = ConcurrentCounter::new(1); + counter.add(1); + counter.add(1); + counter.add(1); + assert_eq!(counter.sum(), 3); + } + + #[test] + fn two_threads_incrementing_concurrently() { + // Spin up two threads that increment the counter concurrently + let counter = ConcurrentCounter::new(2); + + std::thread::scope(|s| { + for _ in 0..2 { + s.spawn(|| { + counter.add(1); + }); + } + }); + + + assert_eq!(counter.sum(), 2); + } + + #[test] + fn two_threads_incrementing_multiple_times_concurrently() { + const WRITE_COUNT: isize = 100_000; + // Spin up two threads that increment the counter concurrently + let counter = ConcurrentCounter::new(2); + + std::thread::scope(|s| { + for _ in 0..2 { + s.spawn(|| { + for _ in 0..WRITE_COUNT { + counter.add(1); + } + }); + } + }); + + + assert_eq!(counter.sum(), 2 * WRITE_COUNT); + } + + #[test] + fn multple_threads_incrementing_multiple_times_concurrently() { + const WRITE_COUNT: isize = 1_000_000; + const THREAD_COUNT: isize = 8; + // Spin up two threads that increment the counter concurrently + let counter = ConcurrentCounter::new(THREAD_COUNT as usize); + + std::thread::scope(|s| { + for _ in 0..THREAD_COUNT { + s.spawn(|| { + for _ in 0..WRITE_COUNT { + counter.add(1); + } + }); + } + }); + + + assert_eq!(counter.sum(), THREAD_COUNT * WRITE_COUNT); + } + + #[test] + fn debug_works_as_expected() { + const WRITE_COUNT: isize = 1_000_000; + const THREAD_COUNT: isize = 8; + // Spin up two threads that increment the counter concurrently + let counter = ConcurrentCounter::new(THREAD_COUNT as usize); + + for _ in 0..WRITE_COUNT { + counter.add(1); + } + + assert_eq!(counter.sum(), WRITE_COUNT); + + assert_eq!(format!("Counter is: {counter:?}"), "Counter is: ConcurrentCounter { sum: 1000000, cells: 8 }") + } +} diff --git a/src/default.rs b/src/default.rs deleted file mode 100644 index a46d41f..0000000 --- a/src/default.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; -use std::cell::UnsafeCell; - -use crate::safe_getters::SafeGetters; - -pub struct ConcurrentCounter { - cells: Vec, -} - -static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(1); - -thread_local! { - static THREAD_ID: UnsafeCell = UnsafeCell::new(0); -} - -impl ConcurrentCounter { - #[inline] - pub fn new(count: usize) -> Self { - let count = count.next_power_of_two(); - Self { - cells: (0..count) - .into_iter() - .map(|_| AtomicIsize::new(0)) - .collect(), - } - } - - #[inline] - fn thread_id(&self) -> usize { - unsafe { THREAD_ID.with(|id| { - let val = debug_unwrap!(id.get().as_mut()); - if *val == 0 { - *val = THREAD_COUNTER.fetch_add(1, Ordering::SeqCst); - } - *val - }) } - } - - #[inline] - pub fn add(&self, value: isize) { - let c = self.cells.safely_get(self.thread_id() & (self.cells.len() - 1)); - c.fetch_add(value, Ordering::Relaxed); - } - - #[inline] - pub fn sum(&self) -> isize { - self.cells.iter().map(|c| c.load(Ordering::Relaxed)).sum() - } -} - -#[cfg(test)] -mod tests { - use crate::default::ConcurrentCounter; - - #[test] - fn basic_test() { - let counter = ConcurrentCounter::new(1); - counter.add(1); - assert_eq!(counter.sum(), 1); - } - - #[test] - fn increment_multiple_times() { - let counter = ConcurrentCounter::new(1); - counter.add(1); - counter.add(1); - counter.add(1); - assert_eq!(counter.sum(), 3); - } - - #[test] - fn two_threads_incrementing_concurrently() { - // Spin up two threads that increment the counter concurrently - let counter = ConcurrentCounter::new(2); - - std::thread::scope(|s| { - for _ in 0..2 { - s.spawn(|| { - counter.add(1); - }); - } - }); - - - assert_eq!(counter.sum(), 2); - } - - #[test] - fn two_threads_incrementing_multiple_times_concurrently() { - const WRITE_COUNT: isize = 100_000; - // Spin up two threads that increment the counter concurrently - let counter = ConcurrentCounter::new(2); - - std::thread::scope(|s| { - for _ in 0..2 { - s.spawn(|| { - for _ in 0..WRITE_COUNT { - counter.add(1); - } - }); - } - }); - - - assert_eq!(counter.sum(), 2 * WRITE_COUNT); - } - - #[test] - fn multple_threads_incrementing_multiple_times_concurrently() { - const WRITE_COUNT: isize = 1_000_000; - const THREAD_COUNT: isize = 8; - // Spin up two threads that increment the counter concurrently - let counter = ConcurrentCounter::new(THREAD_COUNT as usize); - - std::thread::scope(|s| { - for _ in 0..THREAD_COUNT { - s.spawn(|| { - for _ in 0..WRITE_COUNT { - counter.add(1); - } - }); - } - }); - - - assert_eq!(counter.sum(), THREAD_COUNT * WRITE_COUNT); - } -} diff --git a/src/lib.rs b/src/lib.rs index 07d6831..a2ddaac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,44 @@ -#![cfg_attr(feature = "nightly", feature(thread_local))] +//! Fast-counter is a shareded concurrent atomic counter +//! +//! The library works by sharding the atomic numbers between multiple values, each thread will +//! attempt to read from a different cell. This helps with cache-thrashing and contention. This +//! benefit is seen when there is a greater number of threads competing over a single cell, on my +//! machine with 16 cores attempting to update the value it can be nearly 60x faster. The price you +//! pay for this is higher memory usage as we have multiple Atomic numbers which are cache-padded +//! so can be significantly higher. +//! +//! # Usage +//! +//! Usage of the library is simple, create a new ConcurrentCounter with then number of shards you +//! wish to have, internally the library will use the next power of two as the number of cells for +//! a faster modulus. +//! +//! ```rust +//! use fast_counter::ConcurrentCounter; +//! +//! let counter = ConcurrentCounter::new(10); +//! +//! counter.add(1); +//! +//! let sum = counter.sum(); +//! +//! assert_eq!(sum, 1); +//! ``` +//! +//! # Performance considerations +//! +//! The library will perform best when the threads are accessing their own cell consistently. This +//! can helped by making sure more than enough cells are allocated for the number of threads which +//! are going to be writing to the cell. +//! +//! Due to the sharding behaviour the time to call the `sum()` method does slow down with the +//! increase in shards, if this becomes a bottleneck it may be worth investigating running with a +//! lower shard counter +//! #[macro_use] mod safe_getters; +mod utils; -pub mod default; - -#[cfg(feature = "nightly")] -mod nightly; -#[cfg(feature = "nightly")] -pub use nightly::*; - -#[cfg(not(feature = "nightly"))] -pub use default::*; \ No newline at end of file +pub mod counter; +pub use counter::*; diff --git a/src/nightly.rs b/src/nightly.rs deleted file mode 100644 index 22b62d2..0000000 --- a/src/nightly.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; - -use crate::safe_getters::SafeGetters; - -pub struct ConcurrentCounter { - cells: Vec, -} - -static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(1); - -#[thread_local] -static mut THREAD_ID: usize = 0; - -impl ConcurrentCounter { - #[inline] - pub fn new(count: usize) -> Self { - let count = count.next_power_of_two(); - Self { - cells: (0..count) - .into_iter() - .map(|_| AtomicIsize::new(0)) - .collect(), - } - } - - #[inline] - fn thread_id(&self) -> usize { - unsafe { - if THREAD_ID == 0 { - THREAD_ID = THREAD_COUNTER.fetch_add(1, Ordering::SeqCst) - } - THREAD_ID - } - } - - #[inline] - pub fn add(&self, value: isize) { - let c = self.cells.safely_get(self.thread_id() & (self.cells.len() - 1)); - c.fetch_add(value, Ordering::Relaxed); - } - - #[inline] - pub fn sum(&self) -> isize { - self.cells.iter().map(|c| c.load(Ordering::Relaxed)).sum() - } -} - -#[cfg(test)] -mod tests { - use crate::ConcurrentCounter; - - #[test] - fn basic_test() { - let counter = ConcurrentCounter::new(1); - counter.add(1); - assert_eq!(counter.sum(), 1); - } - - #[test] - fn increment_multiple_times() { - let counter = ConcurrentCounter::new(1); - counter.add(1); - counter.add(1); - counter.add(1); - assert_eq!(counter.sum(), 3); - } - - #[test] - fn two_threads_incrementing_concurrently() { - // Spin up two threads that increment the counter concurrently - let counter = ConcurrentCounter::new(2); - - std::thread::scope(|s| { - for _ in 0..2 { - s.spawn(|| { - counter.add(1); - }); - } - }); - - - assert_eq!(counter.sum(), 2); - } - - #[test] - fn two_threads_incrementing_multiple_times_concurrently() { - const WRITE_COUNT: isize = 100_000; - // Spin up two threads that increment the counter concurrently - let counter = ConcurrentCounter::new(2); - - std::thread::scope(|s| { - for _ in 0..2 { - s.spawn(|| { - for _ in 0..WRITE_COUNT { - counter.add(1); - } - }); - } - }); - - - assert_eq!(counter.sum(), 2 * WRITE_COUNT); - } - - #[test] - fn multple_threads_incrementing_multiple_times_concurrently() { - const WRITE_COUNT: isize = 1_000_000; - const THREAD_COUNT: isize = 20; - // Spin up two threads that increment the counter concurrently - let counter = ConcurrentCounter::new(THREAD_COUNT as usize); - - std::thread::scope(|s| { - for _ in 0..THREAD_COUNT { - s.spawn(|| { - for _ in 0..WRITE_COUNT { - counter.add(1); - } - }); - } - }); - - assert_eq!(counter.sum(), THREAD_COUNT * WRITE_COUNT); - } -} diff --git a/src/safe_getters.rs b/src/safe_getters.rs index 3e8f8b4..a304c6d 100644 --- a/src/safe_getters.rs +++ b/src/safe_getters.rs @@ -8,17 +8,6 @@ macro_rules! safe_get { }; } -macro_rules! debug_unwrap { - ($arr:expr) => { - if cfg!(not(debug_assertions)) { - $arr.unwrap_unchecked() - } else { - $arr.unwrap() - } - }; -} - - pub trait SafeGetters { fn safely_get(&self, idx: usize) -> &T; } @@ -33,4 +22,4 @@ impl SafeGetters for [T; C] { fn safely_get(&self, idx:usize) -> &T { safe_get!(self, idx) } -} \ No newline at end of file +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..10f507d --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,45 @@ +use std::sync::atomic::AtomicIsize; + +/// Pads and aligns a value to the length of a cache line. +/// Code borrowed from https://github.com/ibraheemdev/seize/blob/master/src/utils.rs +#[cfg_attr( + any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "powerpc64", + ), + repr(align(128)) +)] +#[cfg_attr( + any( + target_arch = "arm", + target_arch = "mips", + target_arch = "mips64", + target_arch = "riscv64", + ), + repr(align(32)) +)] +#[cfg_attr(target_arch = "s390x", repr(align(256)))] +#[cfg_attr( + not(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "arm", + target_arch = "mips", + target_arch = "mips64", + target_arch = "riscv64", + target_arch = "s390x", + )), + repr(align(64)) +)] +pub struct CachePadded { + pub value: T, +} + +pub fn make_new_padded_counter() -> CachePadded:: { + CachePadded { + value: AtomicIsize::new(0) + } +} +